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
JavaScript
/**
* 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;