UNPKG

myaidev-method

Version:

Comprehensive development framework with SPARC methodology for AI-assisted software development, multi-platform publishing (WordPress, PayloadCMS, Astro, Docusaurus, Mintlify), and Coolify deployment

395 lines (344 loc) 9.86 kB
/** * PayloadCMS API Utilities * Reusable functions for PayloadCMS content publishing and management * Optimized for Claude Code 2.0 agent integration */ import fetch from 'node-fetch'; import { readFileSync } from 'fs'; import { parse } from 'dotenv'; import { marked } from 'marked'; export class PayloadCMSUtils { constructor(config = {}) { // Load config from .env if not provided if (!config.url || (!config.email && !config.apiKey)) { const envConfig = this.loadEnvConfig(); config = { ...envConfig, ...config }; } this.url = config.url?.replace(/\/$/, ''); this.email = config.email; this.password = config.password; this.apiKey = config.apiKey; this.token = null; } loadEnvConfig() { try { const envPath = process.env.ENV_PATH || '.env'; const envContent = readFileSync(envPath, 'utf8'); const parsed = parse(envContent); return { url: parsed.PAYLOADCMS_URL, email: parsed.PAYLOADCMS_EMAIL, password: parsed.PAYLOADCMS_PASSWORD, apiKey: parsed.PAYLOADCMS_API_KEY }; } catch (error) { throw new Error(`Failed to load PayloadCMS configuration: ${error.message}`); } } /** * Authenticate with PayloadCMS and get JWT token */ async authenticate() { // If API key is provided, use that instead if (this.apiKey) { this.token = this.apiKey; return { success: true, method: 'api-key' }; } if (!this.email || !this.password) { throw new Error('Email and password required for authentication'); } try { const response = await fetch(`${this.url}/api/users/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: this.email, password: this.password }) }); if (!response.ok) { const error = await response.text(); throw new Error(`Authentication failed: ${error}`); } const data = await response.json(); this.token = data.token; return { success: true, method: 'jwt', user: data.user, token: this.token }; } catch (error) { throw new Error(`PayloadCMS authentication failed: ${error.message}`); } } /** * Make authenticated API request */ async request(endpoint, options = {}) { if (!this.token) { await this.authenticate(); } const url = `${this.url}${endpoint}`; const headers = { 'Content-Type': 'application/json', 'Authorization': `JWT ${this.token}`, ...options.headers }; try { const response = await fetch(url, { ...options, headers }); const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${data.message || response.statusText}`); } return data; } catch (error) { throw new Error(`PayloadCMS API request failed: ${error.message}`); } } /** * Convert markdown to Lexical rich text format * PayloadCMS uses Lexical editor by default */ convertMarkdownToLexical(markdown) { const tokens = marked.lexer(markdown); const convertToken = (token) => { switch (token.type) { case 'heading': return { type: 'heading', tag: `h${token.depth}`, children: [{ type: 'text', text: token.text, format: 0 }], direction: 'ltr', format: '', indent: 0, version: 1 }; case 'paragraph': return { type: 'paragraph', children: this.parseInlineText(token.text), direction: 'ltr', format: '', indent: 0, version: 1 }; case 'list': return { type: token.ordered ? 'number' : 'bullet', listType: token.ordered ? 'number' : 'bullet', start: token.start || 1, tag: token.ordered ? 'ol' : 'ul', children: token.items.map(item => ({ type: 'listitem', value: 1, children: [{ type: 'paragraph', children: this.parseInlineText(item.text), direction: 'ltr', format: '', indent: 0 }], direction: 'ltr', format: '', indent: 0 })), direction: 'ltr', format: '', indent: 0, version: 1 }; case 'code': return { type: 'code', language: token.lang || 'plaintext', children: [{ type: 'text', text: token.text, format: 0 }], direction: 'ltr', format: '', indent: 0, version: 1 }; case 'blockquote': return { type: 'quote', children: [{ type: 'paragraph', children: this.parseInlineText(token.text), direction: 'ltr', format: '', indent: 0 }], direction: 'ltr', format: '', indent: 0, version: 1 }; case 'hr': return { type: 'horizontalrule', version: 1 }; default: return { type: 'paragraph', children: [{ type: 'text', text: token.raw || '', format: 0 }], direction: 'ltr', format: '', indent: 0, version: 1 }; } }; const children = tokens.map(convertToken); return { root: { type: 'root', format: '', indent: 0, version: 1, children, direction: 'ltr' } }; } /** * Parse inline text with formatting (bold, italic, code, links) */ parseInlineText(text) { // Simple inline parser for bold, italic, code, links const children = []; // For now, return simple text node // TODO: Enhance with full inline formatting support children.push({ type: 'text', text: text.replace(/<[^>]+>/g, ''), // Strip HTML tags format: 0, mode: 'normal', style: '', detail: 0, version: 1 }); return children; } /** * List all collections */ async listCollections() { return await this.request('/api'); } /** * Get documents from a collection */ async getDocuments(collection, options = {}) { const params = new URLSearchParams(); if (options.limit) params.append('limit', options.limit); if (options.page) params.append('page', options.page); if (options.where) params.append('where', JSON.stringify(options.where)); const query = params.toString() ? `?${params.toString()}` : ''; return await this.request(`/api/${collection}${query}`); } /** * Get a single document by ID */ async getDocument(collection, id) { return await this.request(`/api/${collection}/${id}`); } /** * Create a new document in a collection */ async createDocument(collection, data) { return await this.request(`/api/${collection}`, { method: 'POST', body: JSON.stringify(data) }); } /** * Update an existing document */ async updateDocument(collection, id, data) { return await this.request(`/api/${collection}/${id}`, { method: 'PATCH', body: JSON.stringify(data) }); } /** * Delete a document */ async deleteDocument(collection, id) { return await this.request(`/api/${collection}/${id}`, { method: 'DELETE' }); } /** * Upload media file */ async uploadMedia(filePath, altText = '') { if (!this.token) { await this.authenticate(); } const FormData = (await import('form-data')).default; const fs = await import('fs'); const formData = new FormData(); formData.append('file', fs.createReadStream(filePath)); if (altText) formData.append('alt', altText); try { const response = await fetch(`${this.url}/api/media`, { method: 'POST', headers: { 'Authorization': `JWT ${this.token}`, ...formData.getHeaders() }, body: formData }); if (!response.ok) { const error = await response.text(); throw new Error(`Upload failed: ${error}`); } return await response.json(); } catch (error) { throw new Error(`Media upload failed: ${error.message}`); } } /** * Publish markdown content to PayloadCMS */ async publishContent(markdownFile, collection, options = {}) { const fs = await import('fs'); const grayMatter = (await import('gray-matter')).default; // Read and parse markdown file const fileContent = fs.readFileSync(markdownFile, 'utf8'); const { data: frontmatter, content } = grayMatter(fileContent); // Convert markdown to Lexical format const richText = this.convertMarkdownToLexical(content); // Prepare document data const documentData = { ...frontmatter, content: richText, status: options.status || frontmatter.status || 'draft', ...options.additionalFields }; // Create or update document if (options.id) { return await this.updateDocument(collection, options.id, documentData); } else { return await this.createDocument(collection, documentData); } } /** * Health check */ async healthCheck() { try { const response = await fetch(`${this.url}/api`); return response.ok; } catch (error) { return false; } } } export default PayloadCMSUtils;