|
| 1 | +import { Command } from 'commander'; |
| 2 | +import chalk from 'chalk'; |
| 3 | +import fs from 'fs'; |
| 4 | +import path from 'path'; |
| 5 | +import { execSync } from 'child_process'; |
| 6 | + |
| 7 | +const templates = { |
| 8 | + plugin: { |
| 9 | + description: 'Create a new ObjectStack plugin', |
| 10 | + files: { |
| 11 | + 'package.json': (name: string) => ({ |
| 12 | + name: `@objectstack/plugin-${name}`, |
| 13 | + version: '0.1.0', |
| 14 | + description: `ObjectStack Plugin: ${name}`, |
| 15 | + main: 'dist/index.js', |
| 16 | + types: 'dist/index.d.ts', |
| 17 | + scripts: { |
| 18 | + build: 'tsc', |
| 19 | + dev: 'tsc --watch', |
| 20 | + test: 'vitest', |
| 21 | + }, |
| 22 | + keywords: ['objectstack', 'plugin', name], |
| 23 | + author: '', |
| 24 | + license: 'MIT', |
| 25 | + dependencies: { |
| 26 | + '@objectstack/spec': 'workspace:*', |
| 27 | + zod: '^3.22.4', |
| 28 | + }, |
| 29 | + devDependencies: { |
| 30 | + '@types/node': '^20.10.0', |
| 31 | + typescript: '^5.3.0', |
| 32 | + vitest: '^2.1.8', |
| 33 | + }, |
| 34 | + }), |
| 35 | + 'tsconfig.json': () => ({ |
| 36 | + extends: '../../tsconfig.json', |
| 37 | + compilerOptions: { |
| 38 | + outDir: 'dist', |
| 39 | + rootDir: 'src', |
| 40 | + }, |
| 41 | + include: ['src/**/*'], |
| 42 | + }), |
| 43 | + 'src/index.ts': (name: string) => `import type { Plugin } from '@objectstack/spec'; |
| 44 | +
|
| 45 | +/** |
| 46 | + * ${name} Plugin for ObjectStack |
| 47 | + */ |
| 48 | +export const ${toCamelCase(name)}Plugin: Plugin = { |
| 49 | + name: '${name}', |
| 50 | + version: '0.1.0', |
| 51 | + |
| 52 | + async initialize(context) { |
| 53 | + console.log('Initializing ${name} plugin...'); |
| 54 | + // Plugin initialization logic |
| 55 | + }, |
| 56 | + |
| 57 | + async destroy() { |
| 58 | + console.log('Destroying ${name} plugin...'); |
| 59 | + // Plugin cleanup logic |
| 60 | + }, |
| 61 | +}; |
| 62 | +
|
| 63 | +export default ${toCamelCase(name)}Plugin; |
| 64 | +`, |
| 65 | + 'README.md': (name: string) => `# @objectstack/plugin-${name} |
| 66 | +
|
| 67 | +ObjectStack Plugin: ${name} |
| 68 | +
|
| 69 | +## Installation |
| 70 | +
|
| 71 | +\`\`\`bash |
| 72 | +pnpm add @objectstack/plugin-${name} |
| 73 | +\`\`\` |
| 74 | +
|
| 75 | +## Usage |
| 76 | +
|
| 77 | +\`\`\`typescript |
| 78 | +import { ${toCamelCase(name)}Plugin } from '@objectstack/plugin-${name}'; |
| 79 | +
|
| 80 | +// Use the plugin in your ObjectStack configuration |
| 81 | +export default { |
| 82 | + plugins: [ |
| 83 | + ${toCamelCase(name)}Plugin, |
| 84 | + ], |
| 85 | +}; |
| 86 | +\`\`\` |
| 87 | +
|
| 88 | +## License |
| 89 | +
|
| 90 | +MIT |
| 91 | +`, |
| 92 | + }, |
| 93 | + }, |
| 94 | + |
| 95 | + example: { |
| 96 | + description: 'Create a new ObjectStack example application', |
| 97 | + files: { |
| 98 | + 'package.json': (name: string) => ({ |
| 99 | + name: `@objectstack/example-${name}`, |
| 100 | + version: '0.1.0', |
| 101 | + private: true, |
| 102 | + description: `ObjectStack Example: ${name}`, |
| 103 | + scripts: { |
| 104 | + build: 'objectstack compile', |
| 105 | + dev: 'tsx watch objectstack.config.ts', |
| 106 | + test: 'vitest', |
| 107 | + }, |
| 108 | + dependencies: { |
| 109 | + '@objectstack/spec': 'workspace:*', |
| 110 | + '@objectstack/cli': 'workspace:*', |
| 111 | + zod: '^3.22.4', |
| 112 | + }, |
| 113 | + devDependencies: { |
| 114 | + '@types/node': '^20.10.0', |
| 115 | + tsx: '^4.21.0', |
| 116 | + typescript: '^5.3.0', |
| 117 | + vitest: '^2.1.8', |
| 118 | + }, |
| 119 | + }), |
| 120 | + 'objectstack.config.ts': (name: string) => `import { defineStack } from '@objectstack/spec'; |
| 121 | +
|
| 122 | +export default defineStack({ |
| 123 | + metadata: { |
| 124 | + name: '${name}', |
| 125 | + version: '0.1.0', |
| 126 | + description: '${name} example application', |
| 127 | + }, |
| 128 | + |
| 129 | + objects: { |
| 130 | + // Define your data objects here |
| 131 | + }, |
| 132 | + |
| 133 | + ui: { |
| 134 | + apps: [], |
| 135 | + views: [], |
| 136 | + }, |
| 137 | +}); |
| 138 | +`, |
| 139 | + 'README.md': (name: string) => `# ${name} Example |
| 140 | +
|
| 141 | +ObjectStack example application: ${name} |
| 142 | +
|
| 143 | +## Quick Start |
| 144 | +
|
| 145 | +\`\`\`bash |
| 146 | +# Build the configuration |
| 147 | +pnpm build |
| 148 | +
|
| 149 | +# Run in development mode |
| 150 | +pnpm dev |
| 151 | +\`\`\` |
| 152 | +
|
| 153 | +## Structure |
| 154 | +
|
| 155 | +- \`objectstack.config.ts\` - Main configuration file |
| 156 | +- \`dist/objectstack.json\` - Compiled artifact |
| 157 | +
|
| 158 | +## Learn More |
| 159 | +
|
| 160 | +- [ObjectStack Documentation](../../content/docs) |
| 161 | +- [Examples](../) |
| 162 | +`, |
| 163 | + 'tsconfig.json': () => ({ |
| 164 | + extends: '../../tsconfig.json', |
| 165 | + compilerOptions: { |
| 166 | + outDir: 'dist', |
| 167 | + rootDir: '.', |
| 168 | + }, |
| 169 | + include: ['*.ts', 'src/**/*'], |
| 170 | + }), |
| 171 | + }, |
| 172 | + }, |
| 173 | +}; |
| 174 | + |
| 175 | +function toCamelCase(str: string): string { |
| 176 | + return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); |
| 177 | +} |
| 178 | + |
| 179 | +export const createCommand = new Command('create') |
| 180 | + .description('Create a new package, plugin, or example from template') |
| 181 | + .argument('<type>', 'Type of project to create (plugin, example)') |
| 182 | + .argument('[name]', 'Name of the project') |
| 183 | + .option('-d, --dir <directory>', 'Target directory') |
| 184 | + .action(async (type: string, name?: string, options?: { dir?: string }) => { |
| 185 | + console.log(chalk.bold(`\n📦 ObjectStack Project Creator`)); |
| 186 | + console.log(chalk.dim(`-------------------------------`)); |
| 187 | + |
| 188 | + if (!templates[type as keyof typeof templates]) { |
| 189 | + console.error(chalk.red(`\n❌ Unknown type: ${type}`)); |
| 190 | + console.log(chalk.dim('Available types: plugin, example')); |
| 191 | + process.exit(1); |
| 192 | + } |
| 193 | + |
| 194 | + if (!name) { |
| 195 | + console.error(chalk.red('\n❌ Project name is required')); |
| 196 | + console.log(chalk.dim(`Usage: objectstack create ${type} <name>`)); |
| 197 | + process.exit(1); |
| 198 | + } |
| 199 | + |
| 200 | + const template = templates[type as keyof typeof templates]; |
| 201 | + const cwd = process.cwd(); |
| 202 | + |
| 203 | + // Determine target directory |
| 204 | + let targetDir: string; |
| 205 | + if (options?.dir) { |
| 206 | + targetDir = path.resolve(cwd, options.dir); |
| 207 | + } else { |
| 208 | + const baseDir = type === 'plugin' ? 'packages/plugins' : 'examples'; |
| 209 | + const projectName = type === 'plugin' ? `plugin-${name}` : name; |
| 210 | + targetDir = path.join(cwd, baseDir, projectName); |
| 211 | + } |
| 212 | + |
| 213 | + // Check if directory already exists |
| 214 | + if (fs.existsSync(targetDir)) { |
| 215 | + console.error(chalk.red(`\n❌ Directory already exists: ${targetDir}`)); |
| 216 | + process.exit(1); |
| 217 | + } |
| 218 | + |
| 219 | + console.log(`📁 Creating ${type}: ${chalk.blue(name)}`); |
| 220 | + console.log(`📂 Location: ${chalk.dim(targetDir)}`); |
| 221 | + console.log(''); |
| 222 | + |
| 223 | + try { |
| 224 | + // Create directory |
| 225 | + fs.mkdirSync(targetDir, { recursive: true }); |
| 226 | + |
| 227 | + // Create files from template |
| 228 | + for (const [filePath, contentFn] of Object.entries(template.files)) { |
| 229 | + const fullPath = path.join(targetDir, filePath); |
| 230 | + const dir = path.dirname(fullPath); |
| 231 | + |
| 232 | + if (!fs.existsSync(dir)) { |
| 233 | + fs.mkdirSync(dir, { recursive: true }); |
| 234 | + } |
| 235 | + |
| 236 | + const content = contentFn(name); |
| 237 | + const fileContent = typeof content === 'string' |
| 238 | + ? content |
| 239 | + : JSON.stringify(content, null, 2); |
| 240 | + |
| 241 | + fs.writeFileSync(fullPath, fileContent); |
| 242 | + console.log(chalk.green(`✓ Created ${filePath}`)); |
| 243 | + } |
| 244 | + |
| 245 | + console.log(''); |
| 246 | + console.log(chalk.green('✅ Project created successfully!')); |
| 247 | + console.log(''); |
| 248 | + console.log(chalk.bold('Next steps:')); |
| 249 | + console.log(chalk.dim(` cd ${path.relative(cwd, targetDir)}`)); |
| 250 | + console.log(chalk.dim(' pnpm install')); |
| 251 | + console.log(chalk.dim(' pnpm build')); |
| 252 | + console.log(''); |
| 253 | + |
| 254 | + } catch (error: any) { |
| 255 | + console.error(chalk.red('\n❌ Failed to create project:')); |
| 256 | + console.error(error.message || error); |
| 257 | + |
| 258 | + // Clean up on error |
| 259 | + if (fs.existsSync(targetDir)) { |
| 260 | + fs.rmSync(targetDir, { recursive: true }); |
| 261 | + } |
| 262 | + |
| 263 | + process.exit(1); |
| 264 | + } |
| 265 | + }); |
0 commit comments