|
5 | 5 | import { getLogger } from '../logger.ts'; |
6 | 6 | import { execSync } from 'node:child_process'; |
7 | 7 | import { readFileSync, existsSync, readdirSync } from 'node:fs'; |
8 | | -import { join } from 'node:path'; |
| 8 | +import { join, dirname } from 'node:path'; |
| 9 | +import { fileURLToPath } from 'node:url'; |
9 | 10 | import { clearLoadedPlugins } from '../plugin-loader.ts'; |
10 | 11 | import { ensurePluginsDir } from '../config.ts'; |
11 | 12 | import { |
12 | 13 | addPluginToRegistry, |
13 | 14 | removePluginFromRegistry, |
14 | 15 | getRegisteredPlugins, |
15 | 16 | isPluginRegistered, |
16 | | - getPluginEntry, |
17 | | - type PluginEntry, |
| 17 | + getPluginEntry |
18 | 18 | } from '../plugin-registry.ts'; |
19 | 19 |
|
| 20 | +const __filename = fileURLToPath(import.meta.url); |
| 21 | +const __dirname = dirname(__filename); |
| 22 | + |
| 23 | +function getTemplate(templateFileName: string): string { |
| 24 | + const templatePath = join(__dirname, '..', 'templates', templateFileName); |
| 25 | + return readFileSync(templatePath, 'utf-8'); |
| 26 | +} |
| 27 | + |
| 28 | +function renderTemplate(templateFileName: string, replacements: Record<string, string> = {}): string { |
| 29 | + let content = getTemplate(templateFileName); |
| 30 | + for (const [key, value] of Object.entries(replacements)) { |
| 31 | + content = content.replaceAll(`{{${key}}}`, value); |
| 32 | + } |
| 33 | + return content; |
| 34 | +} |
| 35 | + |
20 | 36 | /** |
21 | 37 | * Load a plugin (npm install wrapper) |
22 | 38 | * Supports either package name or --from flag with URL |
@@ -625,191 +641,51 @@ export async function initPlugin(pluginName?: string): Promise<void> { |
625 | 641 |
|
626 | 642 | try { |
627 | 643 | logger.info(`Creating plugin: ${dirName}...`); |
| 644 | + |
| 645 | + const templateVars = { PLUGIN_NAME: dirName }; |
628 | 646 |
|
629 | 647 | // Create plugin directory |
630 | 648 | mkdirSync(pluginDir, { recursive: true }); |
631 | 649 |
|
632 | 650 | // Create package.json |
633 | | - const packageJson = { |
634 | | - name: dirName, |
635 | | - version: '1.0.0', |
636 | | - type: 'module', |
637 | | - description: `A c8ctl plugin`, |
638 | | - keywords: ['c8ctl', 'c8ctl-plugin'], |
639 | | - main: 'c8ctl-plugin.js', |
640 | | - scripts: { |
641 | | - build: 'tsc', |
642 | | - watch: 'tsc --watch', |
643 | | - }, |
644 | | - devDependencies: { |
645 | | - typescript: '^5.0.0', |
646 | | - '@types/node': '^22.0.0', |
647 | | - }, |
648 | | - }; |
649 | | - |
650 | | - writeFileSync( |
651 | | - join(pluginDir, 'package.json'), |
652 | | - JSON.stringify(packageJson, null, 2) |
653 | | - ); |
654 | | - |
| 651 | + writeFileSync(join(pluginDir, 'package.json'), renderTemplate('package.json', templateVars)); |
| 652 | + |
655 | 653 | // Create tsconfig.json |
656 | | - const tsConfig = { |
657 | | - compilerOptions: { |
658 | | - target: 'ES2022', |
659 | | - module: 'ES2022', |
660 | | - moduleResolution: 'node', |
661 | | - outDir: '.', |
662 | | - rootDir: './src', |
663 | | - strict: true, |
664 | | - esModuleInterop: true, |
665 | | - skipLibCheck: true, |
666 | | - forceConsistentCasingInFileNames: true, |
667 | | - }, |
668 | | - include: ['src/**/*'], |
669 | | - exclude: ['node_modules'], |
670 | | - }; |
671 | | - |
672 | | - writeFileSync( |
673 | | - join(pluginDir, 'tsconfig.json'), |
674 | | - JSON.stringify(tsConfig, null, 2) |
675 | | - ); |
| 654 | + writeFileSync(join(pluginDir, 'tsconfig.json'), getTemplate('tsconfig.json')); |
676 | 655 |
|
677 | 656 | // Create src directory |
678 | 657 | mkdirSync(join(pluginDir, 'src'), { recursive: true }); |
679 | | - |
680 | | - // Create c8ctl-plugin.ts |
681 | | - const pluginTemplate = `/** |
682 | | - * ${dirName} - A c8ctl plugin |
683 | | - */ |
684 | 658 |
|
685 | | -// The c8ctl runtime is available globally |
686 | | -declare const c8ctl: { |
687 | | - version: string; |
688 | | - nodeVersion: string; |
689 | | - platform: string; |
690 | | - arch: string; |
691 | | - cwd: string; |
692 | | - outputMode: 'text' | 'json'; |
693 | | - activeProfile?: string; |
694 | | - activeTenant?: string; |
695 | | -}; |
696 | | -
|
697 | | -// Optional metadata for help text |
698 | | -export const metadata = { |
699 | | - name: '${dirName}', |
700 | | - description: 'A c8ctl plugin', |
701 | | - commands: { |
702 | | - hello: { |
703 | | - description: 'Say hello from the plugin', |
704 | | - }, |
705 | | - }, |
706 | | -}; |
707 | | -
|
708 | | -// Required commands export |
709 | | -export const commands = { |
710 | | - hello: async (args: string[]) => { |
711 | | - console.log('Hello from ${dirName}!'); |
712 | | - console.log('c8ctl version:', c8ctl.version); |
713 | | - console.log('Node version:', c8ctl.nodeVersion); |
714 | | - |
715 | | - if (args.length > 0) { |
716 | | - console.log('Arguments:', args.join(', ')); |
717 | | - } |
| 659 | + // Create root c8ctl-plugin.js entry point |
| 660 | + writeFileSync(join(pluginDir, 'c8ctl-plugin.js'), getTemplate('c8ctl-plugin.js')); |
718 | 661 |
|
719 | | - // Example: Access c8ctl runtime |
720 | | - console.log('Current directory:', c8ctl.cwd); |
721 | | - console.log('Output mode:', c8ctl.outputMode); |
| 662 | + // Create c8ctl-plugin.ts |
| 663 | + writeFileSync(join(pluginDir, 'src', 'c8ctl-plugin.ts'), renderTemplate('c8ctl-plugin.ts', templateVars)); |
722 | 664 |
|
723 | | - if (c8ctl.activeProfile) { |
724 | | - console.log('Active profile:', c8ctl.activeProfile); |
725 | | - } |
726 | | - }, |
727 | | -}; |
728 | | -`; |
| 665 | + // Create README.md |
| 666 | + const readme = renderTemplate('README.md', templateVars); |
729 | 667 |
|
730 | 668 | writeFileSync( |
731 | | - join(pluginDir, 'src', 'c8ctl-plugin.ts'), |
732 | | - pluginTemplate |
| 669 | + join(pluginDir, 'README.md'), |
| 670 | + readme |
733 | 671 | ); |
734 | | - |
735 | | - // Create README.md |
736 | | - const readme = `# ${dirName} |
737 | | -
|
738 | | -A c8ctl plugin. |
739 | | -
|
740 | | -## Development |
741 | | -
|
742 | | -1. Install dependencies: |
743 | | -\`\`\`bash |
744 | | -npm install |
745 | | -\`\`\` |
746 | | -
|
747 | | -2. Build the plugin: |
748 | | -\`\`\`bash |
749 | | -npm run build |
750 | | -\`\`\` |
751 | | -
|
752 | | -3. Load the plugin for testing: |
753 | | -\`\`\`bash |
754 | | -c8ctl load plugin --from file://\${PWD} |
755 | | -\`\`\` |
756 | | -
|
757 | | -4. Test the plugin command: |
758 | | -\`\`\`bash |
759 | | -c8ctl hello |
760 | | -\`\`\` |
761 | | -
|
762 | | -## Plugin Structure |
763 | | -
|
764 | | -- \`src/c8ctl-plugin.ts\` - Plugin source code (TypeScript) |
765 | | -- \`c8ctl-plugin.js\` - Compiled plugin file (JavaScript) |
766 | | -- \`package.json\` - Package metadata with c8ctl keywords |
767 | 672 |
|
768 | | -## Publishing |
| 673 | + // Create AGENTS.md |
| 674 | + const agents = getTemplate('AGENTS.md'); |
769 | 675 |
|
770 | | -Before publishing, ensure: |
771 | | -- The plugin is built (\`npm run build\`) |
772 | | -- The package.json has correct metadata |
773 | | -- Keywords include 'c8ctl' or 'c8ctl-plugin' |
774 | | -
|
775 | | -Then publish to npm: |
776 | | -\`\`\`bash |
777 | | -npm publish |
778 | | -\`\`\` |
779 | | -
|
780 | | -Users can install your plugin with: |
781 | | -\`\`\`bash |
782 | | -c8ctl load plugin ${dirName} |
783 | | -\`\`\` |
784 | | -`; |
785 | | - |
786 | 676 | writeFileSync( |
787 | | - join(pluginDir, 'README.md'), |
788 | | - readme |
| 677 | + join(pluginDir, 'AGENTS.md'), |
| 678 | + agents |
789 | 679 | ); |
790 | 680 |
|
791 | 681 | // Create .gitignore |
792 | | - const gitignore = `node_modules/ |
793 | | -*.js |
794 | | -*.js.map |
795 | | -!c8ctl-plugin.js |
796 | | -`; |
797 | | - |
798 | | - writeFileSync( |
799 | | - join(pluginDir, '.gitignore'), |
800 | | - gitignore |
801 | | - ); |
| 682 | + writeFileSync(join(pluginDir, '.gitignore'), getTemplate('.gitignore')); |
802 | 683 |
|
803 | 684 | logger.success('Plugin scaffolding created successfully!'); |
804 | | - logger.info(''); |
805 | | - logger.info(`Next steps:`); |
806 | | - logger.info(` 1. cd ${dirName}`); |
807 | | - logger.info(` 2. npm install`); |
808 | | - logger.info(` 3. npm run build`); |
809 | | - logger.info(` 4. c8ctl load plugin --from file://\${PWD}`); |
810 | | - logger.info(` 5. c8ctl hello`); |
811 | | - logger.info(''); |
812 | | - logger.info(`Edit src/c8ctl-plugin.ts to add your plugin logic.`); |
| 685 | + const nextSteps = renderTemplate('init-plugin-next-steps.txt', templateVars); |
| 686 | + for (const line of nextSteps.split('\n')) { |
| 687 | + logger.info(line); |
| 688 | + } |
813 | 689 | } catch (error) { |
814 | 690 | logger.error('Failed to create plugin', error as Error); |
815 | 691 | process.exit(1); |
|
0 commit comments