gatsby-source-guru
Version:
A Gatsby source plugin for fetching content from GetGuru knowledge base and creating pages from your cards
138 lines (119 loc) • 4.88 kB
JavaScript
// Utility functions for gatsby-source-guru
const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
// Configuration constants
const FILE_EXTENSIONS = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'txt', 'csv']
const IMAGE_SIGNATURES = {
JPEG: [0xFF, 0xD8, 0xFF],
PNG: [0x89, 0x50, 0x4E, 0x47],
GIF: [0x47, 0x49, 0x46]
}
const CONTENT_TYPE_EXTENSIONS = {
'image/jpeg': '.jpg',
'image/png': '.png',
'image/gif': '.gif',
'image/svg': '.svg',
'image/webp': '.webp',
'application/pdf': '.pdf'
}
// Regex patterns for Guru links
const GURU_LINK_PATTERNS = [
/data-ghq-guru-card-id=["']([a-f0-9-]+)["']/gi,
/https:\/\/app\.getguru\.com\/card\/([a-f0-9-]+)/gi,
/https:\/\/getguru\.com\/card\/([a-f0-9-]+)/gi,
/guru:\/\/card\/([a-f0-9-]+)/gi
]
// Helper function to create URL-safe slugs from titles
const createSlugFromTitle = (title) => {
if (!title) return ''
// Normalize unicode characters and handle special cases
const slug = title
.trim() // Trim first to remove leading/trailing spaces
.toLowerCase()
.normalize('NFD') // Decompose accented characters
.replace(/[\u0300-\u036f]/g, '') // Remove diacritical marks
.replace(/\./g, '-') // Replace dots with hyphens (for hierarchical numbering like 03.01)
.replace(/[^\w\s-]/g, '') // Remove special characters and emojis
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Replace multiple hyphens with single
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
// If slug is empty after processing, return empty string (caller should provide fallback)
return slug
}
// Utility functions
const getFileExtensionFromContentType = (contentType) => {
return CONTENT_TYPE_EXTENSIONS[contentType] || ''
}
const isImageBySignature = (buffer) => {
return (buffer[0] === IMAGE_SIGNATURES.JPEG[0] && buffer[1] === IMAGE_SIGNATURES.JPEG[1] && buffer[2] === IMAGE_SIGNATURES.JPEG[2]) ||
(buffer[0] === IMAGE_SIGNATURES.PNG[0] && buffer[1] === IMAGE_SIGNATURES.PNG[1] && buffer[2] === IMAGE_SIGNATURES.PNG[2] && buffer[3] === IMAGE_SIGNATURES.PNG[3]) ||
(buffer[0] === IMAGE_SIGNATURES.GIF[0] && buffer[1] === IMAGE_SIGNATURES.GIF[1] && buffer[2] === IMAGE_SIGNATURES.GIF[2]) ||
(buffer.toString('utf8', 0, 5) === '<?xml' || buffer.toString('utf8', 0, 4) === '<svg')
}
const ensureDirectoryExists = (dir) => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
// Extract all file URLs from content
const extractFileUrls = (content) => {
const imageUrlRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi
const imageUrls = [...content.matchAll(imageUrlRegex)].map(match => match[1])
const fileUrlRegex = new RegExp(`https:\\/\\/[^"\\s<>]+\\.(${FILE_EXTENSIONS.join('|')})`, 'gi')
const fileUrls = content.match(fileUrlRegex) || []
const guruAttachmentRegex = /https:\/\/api\.getguru\.com\/api\/v1\/cards\/[^"]+\/attachments\/[^"\s<>]+/gi
const guruFileUrls = content.match(guruAttachmentRegex) || []
return {
imageUrls,
otherFileUrls: [...new Set([...fileUrls, ...guruFileUrls])]
}
}
// Helper function to format user names
const formatUserName = (user) => {
if (typeof user === 'object' && user) {
return `${user.firstName || ''} ${user.lastName || ''}`.trim() || 'Unknown'
}
return user || 'Unknown'
}
// Validate plugin options
const validateOptions = (pluginOptions) => {
const { authMode = 'user', collectionId, collectionToken, apiUsername, apiPassword, teamName } = pluginOptions
if (authMode === 'collection') {
if (!collectionId || !collectionToken) {
throw new Error('gatsby-source-guru in collection mode requires collectionId and collectionToken options')
}
} else {
if (!apiUsername || !apiPassword || !teamName) {
throw new Error('gatsby-source-guru in user mode requires apiUsername, apiPassword, and teamName options')
}
}
}
// Create authentication headers
const createAuthHeaders = (pluginOptions) => {
const { authMode = 'user', collectionId, collectionToken, apiUsername, apiPassword } = pluginOptions
let authString
if (authMode === 'collection') {
authString = Buffer.from(`${collectionId}:${collectionToken}`).toString('base64')
} else {
authString = Buffer.from(`${apiUsername}:${apiPassword}`).toString('base64')
}
return {
'Authorization': `Basic ${authString}`,
'Accept': 'application/json'
}
}
module.exports = {
FILE_EXTENSIONS,
IMAGE_SIGNATURES,
CONTENT_TYPE_EXTENSIONS,
GURU_LINK_PATTERNS,
createSlugFromTitle,
getFileExtensionFromContentType,
isImageBySignature,
ensureDirectoryExists,
extractFileUrls,
formatUserName,
validateOptions,
createAuthHeaders
}