myaidev-method 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Static Site Publishing Utilities
3
+ * Unified utilities for publishing to static site generators
4
+ * Supports: Docusaurus, Mintlify, Astro
5
+ * Optimized for Claude Code 2.0 agent integration
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
9
+ import { join, dirname, basename } from 'path';
10
+ import { parse as parseEnv } from 'dotenv';
11
+ import grayMatter from 'gray-matter';
12
+ import simpleGit from 'simple-git';
13
+
14
+ // Platform configurations
15
+ const platformConfigs = {
16
+ docusaurus: {
17
+ contentDirs: {
18
+ docs: 'docs',
19
+ blog: 'blog',
20
+ pages: 'src/pages'
21
+ },
22
+ frontmatterFields: ['id', 'title', 'sidebar_label', 'sidebar_position', 'description', 'tags'],
23
+ configFile: 'docusaurus.config.js',
24
+ sidebarFile: 'sidebars.js',
25
+ fileExtensions: ['.md', '.mdx'],
26
+ requiresNavUpdate: false
27
+ },
28
+ mintlify: {
29
+ contentDirs: {
30
+ default: '.'
31
+ },
32
+ frontmatterFields: ['title', 'description', 'icon', 'iconType'],
33
+ configFile: 'mint.json',
34
+ requiresNavUpdate: true,
35
+ fileExtensions: ['.mdx'],
36
+ defaultExtension: '.mdx'
37
+ },
38
+ astro: {
39
+ contentDirs: {
40
+ content: 'src/content',
41
+ pages: 'src/pages'
42
+ },
43
+ frontmatterFields: ['title', 'description', 'pubDate', 'author', 'image', 'tags'],
44
+ configFile: 'astro.config.mjs',
45
+ collectionSchema: true,
46
+ fileExtensions: ['.md', '.mdx'],
47
+ requiresNavUpdate: false
48
+ }
49
+ };
50
+
51
+ export class StaticSiteUtils {
52
+ constructor(config = {}) {
53
+ this.platform = config.platform;
54
+
55
+ // Load platform-specific configuration
56
+ if (!this.platform || !platformConfigs[this.platform]) {
57
+ throw new Error(`Invalid platform. Supported: ${Object.keys(platformConfigs).join(', ')}`);
58
+ }
59
+
60
+ this.platformConfig = platformConfigs[this.platform];
61
+
62
+ // Load project path from config or .env
63
+ this.projectPath = config.projectPath || this.loadProjectPath();
64
+
65
+ // Load git configuration
66
+ const envConfig = this.loadEnvConfig();
67
+ this.gitUserName = config.gitUserName || envConfig.GIT_USER_NAME;
68
+ this.gitUserEmail = config.gitUserEmail || envConfig.GIT_USER_EMAIL;
69
+ this.gitRemote = config.gitRemote || envConfig.GIT_REMOTE_URL || 'origin';
70
+
71
+ // Initialize git
72
+ this.git = simpleGit(this.projectPath);
73
+ }
74
+
75
+ loadEnvConfig() {
76
+ try {
77
+ const envPath = process.env.ENV_PATH || '.env';
78
+ if (existsSync(envPath)) {
79
+ const envContent = readFileSync(envPath, 'utf8');
80
+ return parseEnv(envContent);
81
+ }
82
+ return {};
83
+ } catch (error) {
84
+ return {};
85
+ }
86
+ }
87
+
88
+ loadProjectPath() {
89
+ const envConfig = this.loadEnvConfig();
90
+ const envKey = `${this.platform.toUpperCase()}_PROJECT_PATH`;
91
+ return envConfig[envKey] || process.cwd();
92
+ }
93
+
94
+ /**
95
+ * Validate project structure
96
+ */
97
+ validateProject() {
98
+ const configPath = join(this.projectPath, this.platformConfig.configFile);
99
+
100
+ if (!existsSync(configPath)) {
101
+ throw new Error(
102
+ `${this.platform} project not found. Missing ${this.platformConfig.configFile} in ${this.projectPath}`
103
+ );
104
+ }
105
+
106
+ // Check if git repository
107
+ const gitPath = join(this.projectPath, '.git');
108
+ if (!existsSync(gitPath)) {
109
+ throw new Error(`Not a git repository: ${this.projectPath}`);
110
+ }
111
+
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Transform frontmatter for platform-specific requirements
117
+ */
118
+ transformFrontmatter(frontmatter) {
119
+ const allowedFields = this.platformConfig.frontmatterFields;
120
+ const transformed = {};
121
+
122
+ // Only include allowed fields
123
+ for (const field of allowedFields) {
124
+ if (frontmatter[field] !== undefined) {
125
+ transformed[field] = frontmatter[field];
126
+ }
127
+ }
128
+
129
+ // Platform-specific transformations
130
+ switch (this.platform) {
131
+ case 'docusaurus':
132
+ // Generate id from title if not provided
133
+ if (!transformed.id && transformed.title) {
134
+ transformed.id = transformed.title
135
+ .toLowerCase()
136
+ .replace(/[^a-z0-9]+/g, '-')
137
+ .replace(/^-|-$/g, '');
138
+ }
139
+ break;
140
+
141
+ case 'mintlify':
142
+ // Mintlify requires title
143
+ if (!transformed.title) {
144
+ throw new Error('title is required in frontmatter for Mintlify');
145
+ }
146
+ break;
147
+
148
+ case 'astro':
149
+ // Add pubDate if not present
150
+ if (!transformed.pubDate) {
151
+ transformed.pubDate = new Date().toISOString();
152
+ }
153
+ break;
154
+ }
155
+
156
+ return transformed;
157
+ }
158
+
159
+ /**
160
+ * Determine target directory based on content type
161
+ */
162
+ getTargetDirectory(options = {}) {
163
+ const contentType = options.type || 'docs';
164
+ const dirs = this.platformConfig.contentDirs;
165
+
166
+ const baseDir = dirs[contentType] || dirs.default || dirs.docs;
167
+
168
+ if (!baseDir) {
169
+ throw new Error(`Unknown content type: ${contentType}`);
170
+ }
171
+
172
+ const targetDir = join(this.projectPath, baseDir);
173
+
174
+ // Create directory if it doesn't exist
175
+ if (!existsSync(targetDir)) {
176
+ mkdirSync(targetDir, { recursive: true });
177
+ }
178
+
179
+ return targetDir;
180
+ }
181
+
182
+ /**
183
+ * Generate filename with appropriate extension
184
+ */
185
+ generateFilename(title, originalFile) {
186
+ const ext = this.platformConfig.defaultExtension ||
187
+ this.platformConfig.fileExtensions[0] || '.md';
188
+
189
+ // If original file has allowed extension, use it
190
+ if (originalFile) {
191
+ const originalExt = originalFile.substring(originalFile.lastIndexOf('.'));
192
+ if (this.platformConfig.fileExtensions.includes(originalExt)) {
193
+ return basename(originalFile);
194
+ }
195
+ }
196
+
197
+ // Generate from title
198
+ const slug = title
199
+ .toLowerCase()
200
+ .replace(/[^a-z0-9]+/g, '-')
201
+ .replace(/^-|-$/g, '');
202
+
203
+ return `${slug}${ext}`;
204
+ }
205
+
206
+ /**
207
+ * Publish markdown content to static site
208
+ */
209
+ async publishContent(markdownFile, options = {}) {
210
+ // Validate project
211
+ this.validateProject();
212
+
213
+ // Read and parse markdown
214
+ const fileContent = readFileSync(markdownFile, 'utf8');
215
+ const { data: frontmatter, content } = grayMatter(fileContent);
216
+
217
+ // Transform frontmatter
218
+ const transformedFrontmatter = this.transformFrontmatter(frontmatter);
219
+
220
+ // Get target directory
221
+ const targetDir = this.getTargetDirectory(options);
222
+
223
+ // Generate filename
224
+ const filename = options.filename ||
225
+ this.generateFilename(transformedFrontmatter.title, markdownFile);
226
+
227
+ const targetPath = join(targetDir, filename);
228
+
229
+ // Combine frontmatter and content
230
+ const output = grayMatter.stringify(content, transformedFrontmatter);
231
+
232
+ // Write file
233
+ writeFileSync(targetPath, output, 'utf8');
234
+
235
+ // Update navigation if required
236
+ if (this.platformConfig.requiresNavUpdate) {
237
+ await this.updateNavigation(filename, transformedFrontmatter, options);
238
+ }
239
+
240
+ // Git operations
241
+ const gitResult = await this.gitCommitAndPush(targetPath, options);
242
+
243
+ return {
244
+ success: true,
245
+ file: targetPath,
246
+ platform: this.platform,
247
+ git: gitResult
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Update navigation configuration (Mintlify)
253
+ */
254
+ async updateNavigation(filename, frontmatter, options = {}) {
255
+ if (this.platform !== 'mintlify') {
256
+ return;
257
+ }
258
+
259
+ const mintJsonPath = join(this.projectPath, 'mint.json');
260
+
261
+ if (!existsSync(mintJsonPath)) {
262
+ console.warn('mint.json not found, skipping navigation update');
263
+ return;
264
+ }
265
+
266
+ try {
267
+ const mintConfig = JSON.parse(readFileSync(mintJsonPath, 'utf8'));
268
+
269
+ // Add to navigation if specified
270
+ if (options.navSection) {
271
+ const section = mintConfig.navigation?.find(nav => nav.group === options.navSection);
272
+
273
+ if (section) {
274
+ const pagePath = filename.replace(/\.mdx?$/, '');
275
+
276
+ if (!section.pages.includes(pagePath)) {
277
+ section.pages.push(pagePath);
278
+ writeFileSync(mintJsonPath, JSON.stringify(mintConfig, null, 2), 'utf8');
279
+ }
280
+ }
281
+ }
282
+ } catch (error) {
283
+ console.warn(`Failed to update mint.json: ${error.message}`);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Git commit and push changes
289
+ */
290
+ async gitCommitAndPush(filePath, options = {}) {
291
+ try {
292
+ // Configure git if credentials provided
293
+ if (this.gitUserName && this.gitUserEmail) {
294
+ await this.git.addConfig('user.name', this.gitUserName, false);
295
+ await this.git.addConfig('user.email', this.gitUserEmail, false);
296
+ }
297
+
298
+ // Stage file
299
+ await this.git.add(filePath);
300
+
301
+ // Commit
302
+ const commitMessage = options.commitMessage ||
303
+ `Add/Update ${basename(filePath)} via myaidev-method`;
304
+
305
+ await this.git.commit(commitMessage);
306
+
307
+ // Push if not in dry-run mode
308
+ if (!options.dryRun && !options.noPush) {
309
+ const branch = options.branch || 'main';
310
+ await this.git.push(this.gitRemote, branch);
311
+
312
+ return {
313
+ committed: true,
314
+ pushed: true,
315
+ branch,
316
+ message: commitMessage
317
+ };
318
+ }
319
+
320
+ return {
321
+ committed: true,
322
+ pushed: false,
323
+ message: commitMessage
324
+ };
325
+ } catch (error) {
326
+ throw new Error(`Git operation failed: ${error.message}`);
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Get git status
332
+ */
333
+ async getGitStatus() {
334
+ try {
335
+ return await this.git.status();
336
+ } catch (error) {
337
+ throw new Error(`Failed to get git status: ${error.message}`);
338
+ }
339
+ }
340
+
341
+ /**
342
+ * List content files in project
343
+ */
344
+ listContent(contentType = 'docs') {
345
+ const targetDir = this.getTargetDirectory({ type: contentType });
346
+ const fs = await import('fs');
347
+
348
+ const files = fs.readdirSync(targetDir)
349
+ .filter(file => {
350
+ const ext = file.substring(file.lastIndexOf('.'));
351
+ return this.platformConfig.fileExtensions.includes(ext);
352
+ });
353
+
354
+ return files.map(file => join(targetDir, file));
355
+ }
356
+ }
357
+
358
+ export default StaticSiteUtils;
359
+
360
+ /**
361
+ * Helper function to detect platform from project structure
362
+ */
363
+ export function detectPlatform(projectPath = process.cwd()) {
364
+ const checks = {
365
+ docusaurus: join(projectPath, 'docusaurus.config.js'),
366
+ mintlify: join(projectPath, 'mint.json'),
367
+ astro: join(projectPath, 'astro.config.mjs')
368
+ };
369
+
370
+ for (const [platform, configPath] of Object.entries(checks)) {
371
+ if (existsSync(configPath)) {
372
+ return platform;
373
+ }
374
+ }
375
+
376
+ return null;
377
+ }
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Astro Publishing Script
5
+ * Publish markdown content to Astro site
6
+ */
7
+
8
+ import { StaticSiteUtils } from '../lib/static-site-utils.js';
9
+
10
+ const args = process.argv.slice(2);
11
+
12
+ const options = {
13
+ file: null,
14
+ type: 'content',
15
+ projectPath: null,
16
+ branch: 'main',
17
+ noPush: false,
18
+ commitMessage: null,
19
+ json: args.includes('--json'),
20
+ verbose: args.includes('--verbose') || args.includes('-v'),
21
+ dryRun: args.includes('--dry-run')
22
+ };
23
+
24
+ // Parse arguments
25
+ for (let i = 0; i < args.length; i++) {
26
+ switch (args[i]) {
27
+ case '--file':
28
+ case '-f':
29
+ options.file = args[++i];
30
+ break;
31
+ case '--type':
32
+ case '-t':
33
+ options.type = args[++i];
34
+ break;
35
+ case '--project':
36
+ case '-p':
37
+ options.projectPath = args[++i];
38
+ break;
39
+ case '--branch':
40
+ case '-b':
41
+ options.branch = args[++i];
42
+ break;
43
+ case '--no-push':
44
+ options.noPush = true;
45
+ break;
46
+ case '--message':
47
+ case '-m':
48
+ options.commitMessage = args[++i];
49
+ break;
50
+ case '--help':
51
+ case '-h':
52
+ printHelp();
53
+ process.exit(0);
54
+ }
55
+ }
56
+
57
+ // If no file specified, check positional argument
58
+ if (!options.file && args.length > 0 && !args[0].startsWith('-')) {
59
+ options.file = args[0];
60
+ }
61
+
62
+ function printHelp() {
63
+ console.log(`
64
+ Astro Publishing Script
65
+
66
+ Usage: astro-publish [file] [options]
67
+
68
+ Arguments:
69
+ file Markdown file to publish (required)
70
+
71
+ Options:
72
+ --type, -t <type> Content type: content|pages (default: content)
73
+ --project, -p <path> Astro project path
74
+ --branch, -b <branch> Git branch to push to (default: main)
75
+ --no-push Commit but don't push to remote
76
+ --message, -m <msg> Custom commit message
77
+ --dry-run Validate without committing
78
+ --verbose, -v Show detailed progress
79
+ --json Output in JSON format
80
+ --help, -h Show this help
81
+
82
+ Examples:
83
+ # Publish to content collection
84
+ astro-publish article.md
85
+
86
+ # Publish to pages
87
+ astro-publish page.md --type pages
88
+
89
+ # Custom project path and branch
90
+ astro-publish article.md --project ./my-astro-site --branch develop
91
+
92
+ # Commit only (no push)
93
+ astro-publish article.md --no-push
94
+ `);
95
+ }
96
+
97
+ async function publishContent() {
98
+ try {
99
+ // Validate required options
100
+ if (!options.file) {
101
+ console.error('Error: Markdown file is required');
102
+ console.error('Run with --help for usage information');
103
+ process.exit(1);
104
+ }
105
+
106
+ if (options.verbose) {
107
+ console.error('Initializing Astro publishing...');
108
+ }
109
+
110
+ const site = new StaticSiteUtils({
111
+ platform: 'astro',
112
+ projectPath: options.projectPath
113
+ });
114
+
115
+ if (options.verbose) {
116
+ console.error('✓ Astro project detected');
117
+ console.error(`Publishing to: ${options.type}`);
118
+ }
119
+
120
+ // Validate project
121
+ site.validateProject();
122
+
123
+ if (options.verbose) {
124
+ console.error('✓ Project structure validated');
125
+ }
126
+
127
+ // Dry run - validate only
128
+ if (options.dryRun) {
129
+ if (options.verbose) {
130
+ console.error('✓ Dry run - validation successful');
131
+ }
132
+
133
+ const output = {
134
+ success: true,
135
+ dryRun: true,
136
+ file: options.file,
137
+ platform: 'astro',
138
+ type: options.type
139
+ };
140
+
141
+ if (options.json) {
142
+ console.log(JSON.stringify(output, null, 2));
143
+ } else {
144
+ console.log('Dry run successful - ready to publish');
145
+ }
146
+
147
+ process.exit(0);
148
+ }
149
+
150
+ // Publish content
151
+ const result = await site.publishContent(options.file, {
152
+ type: options.type,
153
+ branch: options.branch,
154
+ noPush: options.noPush,
155
+ commitMessage: options.commitMessage
156
+ });
157
+
158
+ if (options.verbose) {
159
+ console.error(`✓ Content published successfully`);
160
+ }
161
+
162
+ // Prepare output
163
+ const output = {
164
+ success: true,
165
+ file: result.file,
166
+ platform: result.platform,
167
+ git: result.git,
168
+ timestamp: new Date().toISOString()
169
+ };
170
+
171
+ if (options.json) {
172
+ console.log(JSON.stringify(output, null, 2));
173
+ } else {
174
+ console.log('\\n' + '='.repeat(60));
175
+ console.log('Astro Publishing Result');
176
+ console.log('='.repeat(60));
177
+ console.log(`Status: ✓ Success`);
178
+ console.log(`File: ${result.file}`);
179
+ console.log(`Committed: ${result.git.committed ? 'Yes' : 'No'}`);
180
+ console.log(`Pushed: ${result.git.pushed ? 'Yes' : 'No'}`);
181
+ if (result.git.branch) {
182
+ console.log(`Branch: ${result.git.branch}`);
183
+ }
184
+ console.log('='.repeat(60));
185
+ }
186
+
187
+ process.exit(0);
188
+ } catch (error) {
189
+ if (options.json) {
190
+ console.log(JSON.stringify({
191
+ success: false,
192
+ error: error.message,
193
+ timestamp: new Date().toISOString()
194
+ }, null, 2));
195
+ } else {
196
+ console.error(`\\nError: ${error.message}`);
197
+ if (options.verbose) {
198
+ console.error(`Stack: ${error.stack}`);
199
+ }
200
+ }
201
+ process.exit(1);
202
+ }
203
+ }
204
+
205
+ if (import.meta.url === `file://${process.argv[1]}`) {
206
+ publishContent();
207
+ }
208
+
209
+ export { publishContent };