stackkit-cli 0.1.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.
Files changed (64) hide show
  1. package/README.md +119 -0
  2. package/bin/stackkit.js +2 -0
  3. package/dist/commands/add.d.ts +9 -0
  4. package/dist/commands/add.d.ts.map +1 -0
  5. package/dist/commands/add.js +201 -0
  6. package/dist/commands/add.js.map +1 -0
  7. package/dist/commands/init.d.ts +11 -0
  8. package/dist/commands/init.d.ts.map +1 -0
  9. package/dist/commands/init.js +153 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/list.d.ts +7 -0
  12. package/dist/commands/list.d.ts.map +1 -0
  13. package/dist/commands/list.js +107 -0
  14. package/dist/commands/list.js.map +1 -0
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +50 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/types/index.d.ts +65 -0
  20. package/dist/types/index.d.ts.map +1 -0
  21. package/dist/types/index.js +3 -0
  22. package/dist/types/index.js.map +1 -0
  23. package/dist/utils/code-inject.d.ts +15 -0
  24. package/dist/utils/code-inject.d.ts.map +1 -0
  25. package/dist/utils/code-inject.js +71 -0
  26. package/dist/utils/code-inject.js.map +1 -0
  27. package/dist/utils/detect.d.ts +5 -0
  28. package/dist/utils/detect.d.ts.map +1 -0
  29. package/dist/utils/detect.js +79 -0
  30. package/dist/utils/detect.js.map +1 -0
  31. package/dist/utils/env-editor.d.ts +11 -0
  32. package/dist/utils/env-editor.d.ts.map +1 -0
  33. package/dist/utils/env-editor.js +92 -0
  34. package/dist/utils/env-editor.js.map +1 -0
  35. package/dist/utils/files.d.ts +7 -0
  36. package/dist/utils/files.d.ts.map +1 -0
  37. package/dist/utils/files.js +51 -0
  38. package/dist/utils/files.js.map +1 -0
  39. package/dist/utils/json-editor.d.ts +9 -0
  40. package/dist/utils/json-editor.d.ts.map +1 -0
  41. package/dist/utils/json-editor.js +50 -0
  42. package/dist/utils/json-editor.js.map +1 -0
  43. package/dist/utils/logger.d.ts +17 -0
  44. package/dist/utils/logger.d.ts.map +1 -0
  45. package/dist/utils/logger.js +58 -0
  46. package/dist/utils/logger.js.map +1 -0
  47. package/dist/utils/package-manager.d.ts +6 -0
  48. package/dist/utils/package-manager.d.ts.map +1 -0
  49. package/dist/utils/package-manager.js +79 -0
  50. package/dist/utils/package-manager.js.map +1 -0
  51. package/package.json +51 -0
  52. package/src/commands/add.ts +261 -0
  53. package/src/commands/init.ts +182 -0
  54. package/src/commands/list.ts +124 -0
  55. package/src/index.ts +53 -0
  56. package/src/types/index.ts +71 -0
  57. package/src/utils/code-inject.ts +85 -0
  58. package/src/utils/detect.ts +89 -0
  59. package/src/utils/env-editor.ts +127 -0
  60. package/src/utils/files.ts +59 -0
  61. package/src/utils/json-editor.ts +64 -0
  62. package/src/utils/logger.ts +62 -0
  63. package/src/utils/package-manager.ts +85 -0
  64. package/tsconfig.json +9 -0
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = exports.Logger = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ class Logger {
10
+ spinner = null;
11
+ info(message) {
12
+ console.log(chalk_1.default.blue('ℹ'), message);
13
+ }
14
+ success(message) {
15
+ console.log(chalk_1.default.green('✔'), message);
16
+ }
17
+ error(message) {
18
+ console.log(chalk_1.default.red('✖'), message);
19
+ }
20
+ warn(message) {
21
+ console.log(chalk_1.default.yellow('⚠'), message);
22
+ }
23
+ log(message) {
24
+ console.log(message);
25
+ }
26
+ newLine() {
27
+ console.log();
28
+ }
29
+ startSpinner(text) {
30
+ this.spinner = (0, ora_1.default)(text).start();
31
+ return this.spinner;
32
+ }
33
+ stopSpinner(success = true, text) {
34
+ if (this.spinner) {
35
+ if (success) {
36
+ this.spinner.succeed(text);
37
+ }
38
+ else {
39
+ this.spinner.fail(text);
40
+ }
41
+ this.spinner = null;
42
+ }
43
+ }
44
+ updateSpinner(text) {
45
+ if (this.spinner) {
46
+ this.spinner.text = text;
47
+ }
48
+ }
49
+ header(text) {
50
+ console.log(chalk_1.default.bold.cyan(text));
51
+ }
52
+ footer() {
53
+ console.log();
54
+ }
55
+ }
56
+ exports.Logger = Logger;
57
+ exports.logger = new Logger();
58
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAC1B,8CAA+B;AAE/B,MAAa,MAAM;IACT,OAAO,GAAe,IAAI,CAAC;IAEnC,IAAI,CAAC,OAAe;QAClB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,CAAC,OAAe;QACrB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,CAAC,OAAe;QAClB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,GAAG,CAAC,OAAe;QACjB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,OAAO;QACL,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,IAAI,CAAC,OAAO,GAAG,IAAA,aAAG,EAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,WAAW,CAAC,OAAO,GAAG,IAAI,EAAE,IAAa;QACvC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,aAAa,CAAC,IAAY;QACxB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,MAAM;QACJ,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;CACF;AAxDD,wBAwDC;AAEY,QAAA,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type PackageManager = 'npm' | 'yarn' | 'pnpm';
2
+ export declare function detectPackageManager(cwd: string): Promise<PackageManager>;
3
+ export declare function installDependencies(cwd: string, pm: PackageManager, dev?: boolean): Promise<void>;
4
+ export declare function addDependencies(cwd: string, pm: PackageManager, packages: string[], dev?: boolean): Promise<void>;
5
+ export declare function initGit(cwd: string): Promise<void>;
6
+ //# sourceMappingURL=package-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../src/utils/package-manager.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAO/E;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,cAAc,EAClB,GAAG,UAAQ,GACV,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,cAAc,EAClB,QAAQ,EAAE,MAAM,EAAE,EAClB,GAAG,UAAQ,GACV,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYxD"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectPackageManager = detectPackageManager;
7
+ exports.installDependencies = installDependencies;
8
+ exports.addDependencies = addDependencies;
9
+ exports.initGit = initGit;
10
+ const detect_package_manager_1 = require("detect-package-manager");
11
+ const execa_1 = __importDefault(require("execa"));
12
+ const logger_1 = require("./logger");
13
+ async function detectPackageManager(cwd) {
14
+ try {
15
+ const pm = await (0, detect_package_manager_1.detect)({ cwd });
16
+ return pm;
17
+ }
18
+ catch {
19
+ return 'npm';
20
+ }
21
+ }
22
+ async function installDependencies(cwd, pm, dev = false) {
23
+ const spinner = logger_1.logger.startSpinner(`Installing dependencies with ${pm}...`);
24
+ try {
25
+ const args = [];
26
+ if (pm === 'npm') {
27
+ args.push('install');
28
+ }
29
+ else if (pm === 'yarn') {
30
+ args.push('install');
31
+ }
32
+ else if (pm === 'pnpm') {
33
+ args.push('install');
34
+ }
35
+ await (0, execa_1.default)(pm, args, { cwd, stdio: 'pipe' });
36
+ spinner.succeed(`Dependencies installed successfully`);
37
+ }
38
+ catch (error) {
39
+ spinner.fail(`Failed to install dependencies`);
40
+ throw error;
41
+ }
42
+ }
43
+ async function addDependencies(cwd, pm, packages, dev = false) {
44
+ if (packages.length === 0)
45
+ return;
46
+ const spinner = logger_1.logger.startSpinner(`Adding ${dev ? 'dev ' : ''}dependencies: ${packages.join(', ')}...`);
47
+ try {
48
+ const args = [];
49
+ if (pm === 'npm') {
50
+ args.push('install', dev ? '--save-dev' : '--save', ...packages);
51
+ }
52
+ else if (pm === 'yarn') {
53
+ args.push('add', dev ? '--dev' : '', ...packages);
54
+ }
55
+ else if (pm === 'pnpm') {
56
+ args.push('add', dev ? '-D' : '', ...packages);
57
+ }
58
+ await (0, execa_1.default)(pm, args.filter(Boolean), { cwd, stdio: 'pipe' });
59
+ spinner.succeed(`Dependencies added successfully`);
60
+ }
61
+ catch (error) {
62
+ spinner.fail(`Failed to add dependencies`);
63
+ throw error;
64
+ }
65
+ }
66
+ async function initGit(cwd) {
67
+ const spinner = logger_1.logger.startSpinner('Initializing git repository...');
68
+ try {
69
+ await (0, execa_1.default)('git', ['init'], { cwd });
70
+ await (0, execa_1.default)('git', ['add', '.'], { cwd });
71
+ await (0, execa_1.default)('git', ['commit', '-m', 'Initial commit from StackKit'], { cwd });
72
+ spinner.succeed('Git repository initialized');
73
+ }
74
+ catch (error) {
75
+ spinner.fail('Failed to initialize git repository');
76
+ // Don't throw - git init is optional
77
+ }
78
+ }
79
+ //# sourceMappingURL=package-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-manager.js","sourceRoot":"","sources":["../../src/utils/package-manager.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "stackkit-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for StackKit - Production-ready project generator and module system",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "stackkit": "./bin/stackkit.js"
8
+ },
9
+ "type": "commonjs",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/tariqul420/stackkit.git",
13
+ "directory": "apps/stackkit-cli"
14
+ },
15
+ "homepage": "https://github.com/tariqul420/stackkit#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/tariqul420/stackkit/issues"
18
+ },
19
+ "scripts": {
20
+ "dev": "tsc --watch",
21
+ "build": "tsc",
22
+ "clean": "rm -rf dist",
23
+ "typecheck": "tsc --noEmit",
24
+ "lint": "eslint src --ext .ts"
25
+ },
26
+ "keywords": [
27
+ "cli",
28
+ "generator",
29
+ "nextjs",
30
+ "template"
31
+ ],
32
+ "author": "Your Name",
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "commander": "^12.0.0",
36
+ "inquirer": "^9.2.12",
37
+ "chalk": "^4.1.2",
38
+ "ora": "^5.4.1",
39
+ "fs-extra": "^11.2.0",
40
+ "execa": "^5.1.1",
41
+ "validate-npm-package-name": "^5.0.0",
42
+ "detect-package-manager": "^3.0.1"
43
+ },
44
+ "devDependencies": {
45
+ "@types/fs-extra": "^11.0.4",
46
+ "@types/inquirer": "^9.0.7",
47
+ "@types/node": "^20.11.0",
48
+ "@types/validate-npm-package-name": "^4.0.2",
49
+ "typescript": "^5.3.3"
50
+ }
51
+ }
@@ -0,0 +1,261 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import inquirer from 'inquirer';
4
+ import path from 'path';
5
+ import { CreateFilePatch, ModuleMetadata } from '../types';
6
+ import { detectProjectInfo, getLibPath, getRouterBasePath } from '../utils/detect';
7
+ import { addEnvVariables } from '../utils/env-editor';
8
+ import { createFile, fileExists } from '../utils/files';
9
+ import { logger } from '../utils/logger';
10
+ import { addDependencies } from '../utils/package-manager';
11
+
12
+ interface AddOptions {
13
+ provider?: string;
14
+ force?: boolean;
15
+ dryRun?: boolean;
16
+ install?: boolean;
17
+ }
18
+
19
+ export async function addCommand(module: string, options: AddOptions): Promise<void> {
20
+ try {
21
+ const projectRoot = process.cwd();
22
+
23
+ // Detect project info
24
+ const spinner = logger.startSpinner('Detecting project...');
25
+ const projectInfo = await detectProjectInfo(projectRoot);
26
+ spinner.succeed(
27
+ `Detected ${projectInfo.framework} (${projectInfo.router} router, ${projectInfo.language})`
28
+ );
29
+
30
+ // Load module metadata
31
+ const modulesDir = path.join(__dirname, '..', '..', '..', '..', 'modules');
32
+ const moduleMetadata = await loadModuleMetadata(modulesDir, module, options.provider);
33
+
34
+ if (!moduleMetadata) {
35
+ logger.error(`Module "${module}" not found`);
36
+ process.exit(1);
37
+ }
38
+
39
+ // Check if framework is supported
40
+ if (!moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
41
+ logger.error(
42
+ `Module "${module}" does not support ${projectInfo.framework}. Supported: ${moduleMetadata.supportedFrameworks.join(', ')}`
43
+ );
44
+ process.exit(1);
45
+ }
46
+
47
+ // Check for conflicts
48
+ if (module === 'auth' && projectInfo.hasAuth && !options.force) {
49
+ logger.warn('Auth library already detected in this project');
50
+ const { proceed } = await inquirer.prompt([
51
+ {
52
+ type: 'confirm',
53
+ name: 'proceed',
54
+ message: 'Continue anyway? (use --force to skip this prompt)',
55
+ default: false,
56
+ },
57
+ ]);
58
+
59
+ if (!proceed) {
60
+ logger.info('Cancelled');
61
+ process.exit(0);
62
+ }
63
+ }
64
+
65
+ if (options.dryRun) {
66
+ logger.warn('Dry run mode - no changes will be made');
67
+ logger.newLine();
68
+ }
69
+
70
+ // Apply module patches
71
+ await applyModulePatches(projectRoot, projectInfo, moduleMetadata, modulesDir, module, options);
72
+
73
+ // Add dependencies
74
+ if (Object.keys(moduleMetadata.dependencies).length > 0 && options.install !== false) {
75
+ const deps = Object.entries(moduleMetadata.dependencies).map(
76
+ ([name, version]) => `${name}@${version}`
77
+ );
78
+
79
+ if (!options.dryRun) {
80
+ await addDependencies(projectRoot, projectInfo.packageManager, deps, false);
81
+ } else {
82
+ logger.info(`Would add dependencies: ${deps.join(', ')}`);
83
+ }
84
+ }
85
+
86
+ // Add dev dependencies
87
+ if (
88
+ moduleMetadata.devDependencies &&
89
+ Object.keys(moduleMetadata.devDependencies).length > 0 &&
90
+ options.install !== false
91
+ ) {
92
+ const devDeps = Object.entries(moduleMetadata.devDependencies).map(
93
+ ([name, version]) => `${name}@${version}`
94
+ );
95
+
96
+ if (!options.dryRun) {
97
+ await addDependencies(projectRoot, projectInfo.packageManager, devDeps, true);
98
+ } else {
99
+ logger.info(`Would add dev dependencies: ${devDeps.join(', ')}`);
100
+ }
101
+ }
102
+
103
+ // Add environment variables
104
+ if (moduleMetadata.envVars.length > 0) {
105
+ if (!options.dryRun) {
106
+ await addEnvVariables(projectRoot, moduleMetadata.envVars, { force: options.force });
107
+ } else {
108
+ logger.log(` ${chalk.dim('~')} .env.example`);
109
+ }
110
+ }
111
+
112
+ logger.newLine();
113
+ logger.success(`Added ${chalk.bold(moduleMetadata.displayName)}`);
114
+ logger.newLine();
115
+
116
+ // Print next steps
117
+ if (moduleMetadata.envVars.some((v) => v.required)) {
118
+ logger.log('Next: Fill in environment variables in .env');
119
+ }
120
+ logger.newLine();
121
+ } catch (error) {
122
+ logger.error(`Failed to add module: ${(error as Error).message}`);
123
+ if (error instanceof Error && error.stack) {
124
+ logger.log(chalk.gray(error.stack));
125
+ }
126
+ process.exit(1);
127
+ }
128
+ }
129
+
130
+ async function loadModuleMetadata(
131
+ modulesDir: string,
132
+ moduleName: string,
133
+ provider?: string
134
+ ): Promise<ModuleMetadata | null> {
135
+ if (!(await fs.pathExists(modulesDir))) {
136
+ return null;
137
+ }
138
+
139
+ // Try to find module in any category
140
+ const categories = await fs.readdir(modulesDir);
141
+
142
+ for (const category of categories) {
143
+ const categoryPath = path.join(modulesDir, category);
144
+ const stat = await fs.stat(categoryPath);
145
+
146
+ if (!stat.isDirectory()) continue;
147
+
148
+ // Get all modules in this category
149
+ const moduleDirs = await fs.readdir(categoryPath);
150
+
151
+ for (const moduleDir of moduleDirs) {
152
+ const modulePath = path.join(categoryPath, moduleDir);
153
+ const moduleStat = await fs.stat(modulePath);
154
+
155
+ if (!moduleStat.isDirectory()) continue;
156
+
157
+ const metadataPath = path.join(modulePath, 'module.json');
158
+
159
+ if (await fs.pathExists(metadataPath)) {
160
+ const metadata = await fs.readJSON(metadataPath);
161
+
162
+ // Match by module name or provider
163
+ if (metadata.name === moduleName || (provider && moduleDir === provider)) {
164
+ return metadata;
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ return null;
171
+ }
172
+
173
+ async function applyModulePatches(
174
+ projectRoot: string,
175
+ projectInfo: any,
176
+ moduleMetadata: ModuleMetadata,
177
+ modulesDir: string,
178
+ moduleName: string,
179
+ options: AddOptions
180
+ ): Promise<void> {
181
+ const moduleBasePath = await findModulePath(modulesDir, moduleName, options.provider);
182
+
183
+ if (!moduleBasePath) {
184
+ throw new Error('Module files not found');
185
+ }
186
+
187
+ for (const patch of moduleMetadata.patches) {
188
+ if (patch.type === 'create-file') {
189
+ const filePatch = patch as CreateFilePatch;
190
+
191
+ // Check conditions
192
+ if (filePatch.condition) {
193
+ if (filePatch.condition.router && filePatch.condition.router !== projectInfo.router) {
194
+ continue; // Skip this patch
195
+ }
196
+ if (filePatch.condition.language && filePatch.condition.language !== projectInfo.language) {
197
+ continue; // Skip this patch
198
+ }
199
+ }
200
+
201
+ const sourceFile = path.join(moduleBasePath, 'files', filePatch.source);
202
+ let destFile = path.join(projectRoot, filePatch.destination);
203
+
204
+ // Replace placeholders in destination
205
+ destFile = destFile
206
+ .replace('{{router}}', getRouterBasePath(projectInfo))
207
+ .replace('{{lib}}', getLibPath(projectInfo));
208
+
209
+ if (!options.dryRun) {
210
+ if (await fileExists(sourceFile)) {
211
+ const content = await fs.readFile(sourceFile, 'utf-8');
212
+ await createFile(destFile, content, { force: options.force });
213
+ const relativePath = path.relative(projectRoot, destFile);
214
+ logger.log(` ${chalk.green('+')} ${relativePath}`);
215
+ } else {
216
+ logger.warn(`Source file not found: ${filePatch.source}`);
217
+ }
218
+ } else {
219
+ const relativePath = path.relative(projectRoot, destFile);
220
+ logger.log(` ${chalk.dim('+')} ${relativePath}`);
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ async function findModulePath(
227
+ modulesDir: string,
228
+ moduleName: string,
229
+ provider?: string
230
+ ): Promise<string | null> {
231
+ const categories = await fs.readdir(modulesDir);
232
+
233
+ for (const category of categories) {
234
+ const categoryPath = path.join(modulesDir, category);
235
+ const stat = await fs.stat(categoryPath);
236
+
237
+ if (!stat.isDirectory()) continue;
238
+
239
+ const moduleDirs = await fs.readdir(categoryPath);
240
+
241
+ for (const moduleDir of moduleDirs) {
242
+ const modulePath = path.join(categoryPath, moduleDir);
243
+ const moduleStat = await fs.stat(modulePath);
244
+
245
+ if (!moduleStat.isDirectory()) continue;
246
+
247
+ const metadataPath = path.join(modulePath, 'module.json');
248
+
249
+ if (await fs.pathExists(metadataPath)) {
250
+ const metadata = await fs.readJSON(metadataPath);
251
+
252
+ // Match by module name or provider
253
+ if (metadata.name === moduleName || (provider && moduleDir === provider)) {
254
+ return modulePath;
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ return null;
261
+ }
@@ -0,0 +1,182 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import inquirer from 'inquirer';
4
+ import path from 'path';
5
+ import validateNpmPackageName from 'validate-npm-package-name';
6
+ import { TemplateMetadata } from '../types';
7
+ import { copyTemplate } from '../utils/files';
8
+ import { logger } from '../utils/logger';
9
+ import { initGit, installDependencies, PackageManager } from '../utils/package-manager';
10
+
11
+ interface InitOptions {
12
+ template?: string;
13
+ pm?: PackageManager;
14
+ install?: boolean;
15
+ git?: boolean;
16
+ yes?: boolean;
17
+ }
18
+
19
+ export async function initCommand(
20
+ projectName: string | undefined,
21
+ options: InitOptions
22
+ ): Promise<void> {
23
+ try {
24
+ // Validate package manager option
25
+ if (options.pm && !['npm', 'yarn', 'pnpm'].includes(options.pm)) {
26
+ logger.error(`Invalid package manager: ${options.pm}. Use npm, yarn, or pnpm.`);
27
+ process.exit(1);
28
+ }
29
+
30
+ // Get available templates
31
+ const templatesDir = path.join(__dirname, '..', '..', '..', '..', 'templates');
32
+ const templates = await getAvailableTemplates(templatesDir);
33
+
34
+ if (templates.length === 0) {
35
+ logger.error('No templates found');
36
+ process.exit(1);
37
+ }
38
+
39
+ // Prompt for project details if not using --yes
40
+ let answers: {
41
+ projectName: string;
42
+ template: string;
43
+ packageManager: PackageManager;
44
+ install: boolean;
45
+ git: boolean;
46
+ };
47
+
48
+ if (options.yes) {
49
+ answers = {
50
+ projectName: projectName || 'my-app',
51
+ template: options.template || templates[0].name,
52
+ packageManager: options.pm || 'pnpm',
53
+ install: options.install !== false,
54
+ git: options.git !== false,
55
+ };
56
+ } else {
57
+ const prompted = await inquirer.prompt([
58
+ {
59
+ type: 'input',
60
+ name: 'projectName',
61
+ message: 'Project name:',
62
+ default: projectName || 'my-app',
63
+ when: !projectName,
64
+ validate: (input: string) => {
65
+ const validation = validateNpmPackageName(input);
66
+ if (!validation.validForNewPackages) {
67
+ return validation.errors?.[0] || 'Invalid package name';
68
+ }
69
+ return true;
70
+ },
71
+ },
72
+ {
73
+ type: 'list',
74
+ name: 'template',
75
+ message: 'Select a template:',
76
+ choices: templates.map((t) => ({
77
+ name: `${t.displayName} - ${t.description}`,
78
+ value: t.name,
79
+ })),
80
+ when: !options.template,
81
+ },
82
+ {
83
+ type: 'list',
84
+ name: 'packageManager',
85
+ message: 'Select a package manager:',
86
+ choices: ['pnpm', 'npm', 'yarn'],
87
+ default: 'pnpm',
88
+ when: !options.pm,
89
+ },
90
+ {
91
+ type: 'confirm',
92
+ name: 'install',
93
+ message: 'Install dependencies?',
94
+ default: true,
95
+ when: options.install !== false,
96
+ },
97
+ {
98
+ type: 'confirm',
99
+ name: 'git',
100
+ message: 'Initialize git repository?',
101
+ default: true,
102
+ when: options.git !== false,
103
+ },
104
+ ]);
105
+
106
+ answers = {
107
+ projectName: projectName || prompted.projectName,
108
+ template: options.template || prompted.template,
109
+ packageManager: options.pm || prompted.packageManager,
110
+ install: options.install !== false && (prompted.install ?? true),
111
+ git: options.git !== false && (prompted.git ?? true),
112
+ };
113
+ }
114
+
115
+ const targetDir = path.join(process.cwd(), answers.projectName);
116
+
117
+ // Check if directory exists
118
+ if (await fs.pathExists(targetDir)) {
119
+ logger.error(`Directory "${answers.projectName}" already exists`);
120
+ logger.info('Please choose a different name or remove the existing directory.');
121
+ process.exit(1);
122
+ }
123
+
124
+ // Validate template exists
125
+ const selectedTemplate = templates.find((t) => t.name === answers.template);
126
+ if (!selectedTemplate) {
127
+ logger.error(`Template "${answers.template}" not found`);
128
+ process.exit(1);
129
+ }
130
+
131
+ logger.newLine();
132
+
133
+ // Copy template
134
+ const templatePath = path.join(templatesDir, answers.template);
135
+ await copyTemplate(templatePath, targetDir, answers.projectName);
136
+
137
+ // Install dependencies
138
+ if (answers.install) {
139
+ await installDependencies(targetDir, answers.packageManager);
140
+ }
141
+
142
+ // Initialize git
143
+ if (answers.git) {
144
+ await initGit(targetDir);
145
+ }
146
+
147
+ logger.newLine();
148
+ logger.success(`Created ${chalk.bold(answers.projectName)}`);
149
+ logger.newLine();
150
+ logger.log(`Next steps:`);
151
+ logger.log(` ${chalk.cyan('cd')} ${answers.projectName}`);
152
+ if (!answers.install) {
153
+ logger.log(` ${chalk.cyan(answers.packageManager)} install`);
154
+ }
155
+ logger.log(
156
+ ` ${chalk.cyan(answers.packageManager)} ${answers.packageManager === 'npm' ? 'run ' : ''}dev`
157
+ );
158
+ logger.newLine();
159
+ } catch (error) {
160
+ logger.error(`Failed to create project: ${(error as Error).message}`);
161
+ process.exit(1);
162
+ }
163
+ }
164
+
165
+ async function getAvailableTemplates(templatesDir: string): Promise<TemplateMetadata[]> {
166
+ if (!(await fs.pathExists(templatesDir))) {
167
+ return [];
168
+ }
169
+
170
+ const templateDirs = await fs.readdir(templatesDir);
171
+ const templates: TemplateMetadata[] = [];
172
+
173
+ for (const dir of templateDirs) {
174
+ const metadataPath = path.join(templatesDir, dir, 'template.json');
175
+ if (await fs.pathExists(metadataPath)) {
176
+ const metadata = await fs.readJSON(metadataPath);
177
+ templates.push(metadata);
178
+ }
179
+ }
180
+
181
+ return templates;
182
+ }