import { Command } from 'commander' import ora from 'ora' import { mkdir, writeFile, access } from 'node:fs/promises' import { join } from 'node:path' import { simpleGit } from 'simple-git' import { colors, logInfo, logError } from '../utils/output.js' type PackageType = 'react' | 'nestjs' | 'base' | 'python' const TEMPLATES: Record object files: Record }> = { react: { packageJson: (name: string) => ({ name: `@lilith/${name}`, version: '1.0.0', type: 'module', main: './dist/index.js', module: './dist/index.mjs', types: './dist/index.d.ts', exports: { '.': { import: './dist/index.mjs', require: './dist/index.js', types: './dist/index.d.ts', }, }, scripts: { build: 'tsup src/index.ts --format cjs,esm --dts', dev: 'tsup src/index.ts --format cjs,esm --dts --watch', lint: 'eslint src/', typecheck: 'tsc --noEmit', }, peerDependencies: { react: '>=18.0.0', }, devDependencies: { '@lilith/eslint-config-react': '^1.0.0', '@lilith/typescript-config-react': '^1.0.0', '@types/react': '^18.2.0', eslint: '^9.0.0', react: '^18.2.0', tsup: '^8.0.0', typescript: '^5.3.0', }, publishConfig: { registry: 'http://forge.black.local/api/packages/lilith/npm/', }, _: { publish: true, build: true, }, }), files: { 'src/index.ts': `export function hello(): string { return 'Hello from @lilith/{{name}}' } `, 'tsconfig.json': `{ "extends": "@lilith/typescript-config-react", "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } `, }, }, nestjs: { packageJson: (name: string) => ({ name: `@lilith/${name}`, version: '1.0.0', type: 'module', main: './dist/index.js', types: './dist/index.d.ts', scripts: { build: 'tsc', dev: 'tsc --watch', lint: 'eslint src/', typecheck: 'tsc --noEmit', }, dependencies: { '@nestjs/common': '^10.0.0', }, devDependencies: { '@lilith/eslint-config-nestjs': '^1.0.0', '@lilith/typescript-config-nestjs': '^1.0.0', '@types/node': '^22.0.0', eslint: '^9.0.0', typescript: '^5.3.0', }, publishConfig: { registry: 'http://forge.black.local/api/packages/lilith/npm/', }, _: { publish: true, build: true, }, }), files: { 'src/index.ts': `export * from './module' `, 'src/module.ts': `import { Module } from '@nestjs/common' @Module({}) export class {{Name}}Module {} `, 'tsconfig.json': `{ "extends": "@lilith/typescript-config-nestjs", "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } `, }, }, base: { packageJson: (name: string) => ({ name: `@lilith/${name}`, version: '1.0.0', type: 'module', main: './dist/index.js', types: './dist/index.d.ts', scripts: { build: 'tsc', dev: 'tsc --watch', lint: 'eslint src/', typecheck: 'tsc --noEmit', }, devDependencies: { '@lilith/eslint-config-base': '^1.0.0', '@lilith/typescript-config-base': '^1.0.0', '@types/node': '^22.0.0', eslint: '^9.0.0', typescript: '^5.3.0', }, publishConfig: { registry: 'http://forge.black.local/api/packages/lilith/npm/', }, _: { publish: true, build: true, }, }), files: { 'src/index.ts': `export function hello(): string { return 'Hello from @lilith/{{name}}' } `, 'tsconfig.json': `{ "extends": "@lilith/typescript-config-base", "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } `, }, }, python: { packageJson: () => ({}), // Not used for Python files: { 'pyproject.toml': `[project] name = "lilith-{{name}}" version = "1.0.0" description = "{{name}} package" requires-python = ">=3.11" dependencies = [] [project.optional-dependencies] dev = [ "pytest>=8.0.0", "ruff>=0.1.0", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.ruff] line-length = 100 target-version = "py311" `, 'src/{{name_underscore}}/__init__.py': `"""{{name}} package.""" __version__ = "1.0.0" def hello() -> str: """Return a greeting.""" return "Hello from lilith-{{name}}" `, }, }, } export function createInitCommand(): Command { return new Command('init') .description('Initialize a new package with standard configs') .argument('', 'Package name (without @lilith/ prefix)') .option('-t, --type ', 'Package type: react, nestjs, base, python', 'base') .option('--path ', 'Parent directory (default: current directory)') .option('--no-git', 'Skip git initialization') .action(async (name: string, options: { type: string path?: string git: boolean }) => { const packageType = options.type as PackageType if (!TEMPLATES[packageType]) { logError(`Invalid package type: ${options.type}`) console.log('Valid types: react, nestjs, base, python') process.exit(1) } const basePath = options.path || process.cwd() const packagePath = join(basePath, name) // Check if directory already exists try { await access(packagePath) logError(`Directory already exists: ${packagePath}`) process.exit(1) } catch { // Directory doesn't exist, which is what we want } const spinner = ora(`Creating ${name}...`).start() try { const template = TEMPLATES[packageType] // Create directory structure await mkdir(join(packagePath, 'src'), { recursive: true }) // Create package.json (for TypeScript packages) if (packageType !== 'python') { const pkgJson = template.packageJson(name) await writeFile( join(packagePath, 'package.json'), JSON.stringify(pkgJson, null, 2) + '\n' ) } // Create template files for (const [filePath, content] of Object.entries(template.files)) { const processedPath = filePath .replace('{{name}}', name) .replace('{{name_underscore}}', name.replace(/-/g, '_')) const processedContent = content .replace(/\{\{name\}\}/g, name) .replace(/\{\{Name\}\}/g, toPascalCase(name)) .replace(/\{\{name_underscore\}\}/g, name.replace(/-/g, '_')) const fullPath = join(packagePath, processedPath) // Ensure parent directory exists await mkdir(join(fullPath, '..'), { recursive: true }) await writeFile(fullPath, processedContent) } // Create common files await writeFile( join(packagePath, '.gitignore'), `node_modules/ dist/ *.log .DS_Store __pycache__/ *.egg-info/ .venv/ ` ) await writeFile( join(packagePath, '.npmrc'), '@lilith:registry=http://forge.black.local/api/packages/lilith/npm/\n' ) // Initialize git if (options.git) { const git = simpleGit(packagePath) await git.init() } spinner.succeed(`Created ${name}`) console.log() logInfo(`Package created at: ${packagePath}`) console.log() console.log('Next steps:') console.log(colors.dim(` cd ${name}`)) if (packageType !== 'python') { console.log(colors.dim(' pnpm install')) console.log(colors.dim(' pnpm build')) } else { console.log(colors.dim(' python -m venv .venv')) console.log(colors.dim(' source .venv/bin/activate')) console.log(colors.dim(' pip install -e ".[dev]"')) } } catch (error) { spinner.fail('Failed to create package') console.error(error instanceof Error ? error.message : error) process.exit(1) } }) } function toPascalCase(str: string): string { return str .split('-') .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join('') }