create-sumit-app 1.0.4 → 1.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.
- package/dist/index.js +469 -265
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/config.js +6 -6
- package/dist/lib/logger.js +25 -25
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/templates.d.ts +9 -4
- package/dist/lib/templates.d.ts.map +1 -1
- package/dist/lib/templates.js +74 -53
- package/dist/lib/templates.js.map +1 -1
- package/dist/lib/utils.d.ts +9 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +81 -40
- package/dist/lib/utils.js.map +1 -1
- package/dist/types/index.d.ts +22 -9
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from
|
|
3
|
-
import { fileURLToPath } from
|
|
4
|
-
import { Command } from
|
|
5
|
-
import { execa } from
|
|
6
|
-
import prompts from
|
|
7
|
-
import chalk from
|
|
8
|
-
import fs from
|
|
9
|
-
import degit from
|
|
10
|
-
import path from
|
|
11
|
-
import ora from
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { getPackageManagerInfo, checkForUpdates, isDirectoryEmpty, validateProjectName, formatDuration,
|
|
16
|
-
//
|
|
17
|
-
} from
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { execa } from "execa";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import fs from "fs-extra";
|
|
9
|
+
import degit from "degit";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import { loadConfig, updateConfig, resetConfig, showConfig, getConfigPath } from "./lib/config.js";
|
|
13
|
+
import { PROJECTS, PRESETS, BASE_TEMPLATE } from "./lib/templates.js";
|
|
14
|
+
import { Logger } from "./lib/logger.js";
|
|
15
|
+
import { getPackageManagerInfo, checkForUpdates, isDirectoryEmpty, validateProjectName, formatDuration,
|
|
16
|
+
// enableWindowsLongPaths,
|
|
17
|
+
getPreset, listPresets, listProjects, getTemplate, listTemplates, } from "./lib/utils.js";
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = path.dirname(__filename);
|
|
20
|
-
function getTemplatePath(template) {
|
|
21
|
-
return template.path || `templates/${template.name}`;
|
|
22
|
-
}
|
|
23
20
|
async function cleanupGitDirectory(projectPath, logger) {
|
|
24
|
-
const gitDir = path.join(projectPath,
|
|
21
|
+
const gitDir = path.join(projectPath, ".git");
|
|
25
22
|
if (!(await fs.pathExists(gitDir))) {
|
|
26
23
|
return;
|
|
27
24
|
}
|
|
@@ -29,17 +26,15 @@ async function cleanupGitDirectory(projectPath, logger) {
|
|
|
29
26
|
while (retries > 0) {
|
|
30
27
|
try {
|
|
31
28
|
await fs.remove(gitDir);
|
|
32
|
-
logger.verbose(
|
|
29
|
+
logger.verbose("Removed template .git directory");
|
|
33
30
|
return;
|
|
34
31
|
}
|
|
35
32
|
catch (error) {
|
|
36
33
|
retries--;
|
|
37
|
-
if (error.code ===
|
|
34
|
+
if (error.code === "EBUSY" || error.code === "EPERM") {
|
|
38
35
|
if (retries > 0) {
|
|
39
36
|
logger.verbose(`Git cleanup failed, retrying... (${retries} attempts left)`);
|
|
40
|
-
// Wait a bit before retrying
|
|
41
37
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
42
|
-
// Try to make files writable on Windows
|
|
43
38
|
try {
|
|
44
39
|
await makeGitFilesWritable(gitDir);
|
|
45
40
|
}
|
|
@@ -48,20 +43,18 @@ async function cleanupGitDirectory(projectPath, logger) {
|
|
|
48
43
|
}
|
|
49
44
|
}
|
|
50
45
|
else {
|
|
51
|
-
logger.warn(
|
|
52
|
-
logger.info(
|
|
46
|
+
logger.warn("Could not remove .git directory automatically");
|
|
47
|
+
logger.info("You may need to manually delete the .git folder if it exists");
|
|
53
48
|
return;
|
|
54
49
|
}
|
|
55
50
|
}
|
|
56
51
|
else {
|
|
57
|
-
// For other errors, just warn and continue
|
|
58
52
|
logger.verbose(`Git cleanup error: ${error.message}`);
|
|
59
53
|
return;
|
|
60
54
|
}
|
|
61
55
|
}
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
|
-
// Helper function to make git files writable
|
|
65
58
|
async function makeGitFilesWritable(gitDir) {
|
|
66
59
|
try {
|
|
67
60
|
const files = await fs.readdir(gitDir, { withFileTypes: true });
|
|
@@ -72,7 +65,7 @@ async function makeGitFilesWritable(gitDir) {
|
|
|
72
65
|
}
|
|
73
66
|
else {
|
|
74
67
|
try {
|
|
75
|
-
await fs.chmod(fullPath, 0o666);
|
|
68
|
+
await fs.chmod(fullPath, 0o666);
|
|
76
69
|
}
|
|
77
70
|
catch (error) {
|
|
78
71
|
// Ignore individual file errors
|
|
@@ -85,48 +78,44 @@ async function makeGitFilesWritable(gitDir) {
|
|
|
85
78
|
}
|
|
86
79
|
}
|
|
87
80
|
async function selectPackageManager(config, logger, packageManagerName) {
|
|
88
|
-
// Available package managers with descriptions
|
|
89
81
|
const availableManagers = [
|
|
90
82
|
{
|
|
91
|
-
name:
|
|
92
|
-
description:
|
|
83
|
+
name: "bun",
|
|
84
|
+
description: "⚡ Ultra-fast JavaScript runtime and package manager",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "pnpm",
|
|
88
|
+
description: "📦 Fast, disk space efficient package manager",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "yarn",
|
|
92
|
+
description: "🐈 Fast, reliable, and secure dependency management",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "npm",
|
|
96
|
+
description: "📦 Default Node.js package manager",
|
|
93
97
|
},
|
|
94
|
-
// {
|
|
95
|
-
// name: 'pnpm',
|
|
96
|
-
// description: '📦 Fast, disk space efficient package manager',
|
|
97
|
-
// },
|
|
98
|
-
// {
|
|
99
|
-
// name: 'yarn',
|
|
100
|
-
// description: '🐈 Fast, reliable, and secure dependency management',
|
|
101
|
-
// },
|
|
102
|
-
// {
|
|
103
|
-
// name: 'npm',
|
|
104
|
-
// description: '📦 Simple and widely used Node.js package manager',
|
|
105
|
-
// },
|
|
106
98
|
];
|
|
107
|
-
// If package manager specified via CLI, use it
|
|
108
99
|
if (packageManagerName) {
|
|
109
100
|
const manager = availableManagers.find((m) => m.name === packageManagerName);
|
|
110
101
|
if (!manager) {
|
|
111
102
|
logger.error(`Package manager "${packageManagerName}" not found.`);
|
|
112
|
-
logger.info(
|
|
103
|
+
logger.info("Available package managers:");
|
|
113
104
|
availableManagers.forEach((m) => logger.info(` • ${m.name} - ${m.description}`));
|
|
114
105
|
process.exit(1);
|
|
115
106
|
}
|
|
116
107
|
logger.verbose(`Using CLI specified package manager: ${manager.name}`);
|
|
117
108
|
return manager.name;
|
|
118
109
|
}
|
|
119
|
-
// If default package manager in config, use it
|
|
120
110
|
if (config.packageManager) {
|
|
121
111
|
logger.verbose(`Using config default package manager: ${config.packageManager}`);
|
|
122
112
|
return config.packageManager;
|
|
123
113
|
}
|
|
124
|
-
// Check for lockfiles in the CURRENT directory for auto-detection
|
|
125
114
|
const lockFiles = [
|
|
126
|
-
{ name:
|
|
127
|
-
{ name:
|
|
128
|
-
{ name:
|
|
129
|
-
{ name:
|
|
115
|
+
{ name: "bun", file: "bun.lockb" },
|
|
116
|
+
{ name: "pnpm", file: "pnpm-lock.yaml" },
|
|
117
|
+
{ name: "yarn", file: "yarn.lock" },
|
|
118
|
+
{ name: "npm", file: "package-lock.json" },
|
|
130
119
|
];
|
|
131
120
|
for (const lockFile of lockFiles) {
|
|
132
121
|
if (await fs.pathExists(path.join(process.cwd(), lockFile.file))) {
|
|
@@ -134,65 +123,187 @@ async function selectPackageManager(config, logger, packageManagerName) {
|
|
|
134
123
|
return lockFile.name;
|
|
135
124
|
}
|
|
136
125
|
}
|
|
137
|
-
// Interactive selection (no auto-detection from global availability)
|
|
138
126
|
logger.newLine();
|
|
139
|
-
logger.log(
|
|
127
|
+
logger.log("📦 Choose a package manager:");
|
|
140
128
|
const { selectedManager } = await prompts({
|
|
141
|
-
type:
|
|
142
|
-
name:
|
|
143
|
-
message:
|
|
129
|
+
type: "select",
|
|
130
|
+
name: "selectedManager",
|
|
131
|
+
message: "Select a package manager",
|
|
144
132
|
choices: availableManagers.map((manager) => ({
|
|
145
133
|
title: `${manager.name}`,
|
|
146
134
|
description: manager.description,
|
|
147
135
|
value: manager.name,
|
|
148
136
|
})),
|
|
149
|
-
initial: 0,
|
|
137
|
+
initial: 0,
|
|
150
138
|
});
|
|
151
139
|
if (!selectedManager) {
|
|
152
|
-
logger.error(
|
|
140
|
+
logger.error("Package manager selection cancelled");
|
|
153
141
|
process.exit(0);
|
|
154
142
|
}
|
|
155
143
|
return selectedManager;
|
|
156
144
|
}
|
|
157
|
-
async function
|
|
158
|
-
// If
|
|
159
|
-
if (
|
|
160
|
-
const
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
logger.
|
|
164
|
-
|
|
145
|
+
async function selectPresetOrProjects(config, logger, presetName, projectNames) {
|
|
146
|
+
// If specific projects provided via CLI, use them
|
|
147
|
+
if (projectNames && projectNames.length > 0) {
|
|
148
|
+
const validProjects = projectNames.filter((p) => PROJECTS[p]);
|
|
149
|
+
if (validProjects.length !== projectNames.length) {
|
|
150
|
+
const invalid = projectNames.filter((p) => !PROJECTS[p]);
|
|
151
|
+
logger.error(`Invalid project(s): ${invalid.join(", ")}`);
|
|
152
|
+
logger.info(`Available projects: ${Object.keys(PROJECTS).join(", ")}`);
|
|
165
153
|
process.exit(1);
|
|
166
154
|
}
|
|
167
|
-
return
|
|
155
|
+
return validProjects;
|
|
168
156
|
}
|
|
169
|
-
// If
|
|
170
|
-
if (
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
logger.
|
|
174
|
-
|
|
157
|
+
// If preset specified via CLI, use it
|
|
158
|
+
if (presetName) {
|
|
159
|
+
const preset = getPreset(presetName);
|
|
160
|
+
if (!preset) {
|
|
161
|
+
logger.error(`Preset "${presetName}" not found.`);
|
|
162
|
+
logger.info("Available presets:");
|
|
163
|
+
PRESETS.forEach((p) => logger.info(` • ${p.name} - ${p.description}`));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
if (preset.name === "custom") {
|
|
167
|
+
return await selectProjects(logger);
|
|
175
168
|
}
|
|
169
|
+
return preset.projects;
|
|
176
170
|
}
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
171
|
+
// If default preset in config, use it
|
|
172
|
+
if (config.defaultPreset) {
|
|
173
|
+
const preset = getPreset(config.defaultPreset);
|
|
174
|
+
if (preset && preset.name !== "custom") {
|
|
175
|
+
logger.verbose(`Using default preset: ${preset.name}`);
|
|
176
|
+
return preset.projects;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Interactive project selection - show multi-select checkboxes directly
|
|
180
|
+
return await selectProjects(logger);
|
|
181
|
+
}
|
|
182
|
+
async function selectProjects(logger) {
|
|
183
|
+
const { selectedProjects } = await prompts({
|
|
184
|
+
type: "multiselect",
|
|
185
|
+
name: "selectedProjects",
|
|
186
|
+
message: "Choose projects",
|
|
187
|
+
choices: Object.values(PROJECTS).map((project) => ({
|
|
188
|
+
title: project.name,
|
|
189
|
+
description: project.description,
|
|
190
|
+
value: project.name,
|
|
191
|
+
selected: true, // All selected by default
|
|
188
192
|
})),
|
|
189
|
-
|
|
193
|
+
min: 1,
|
|
194
|
+
hint: "- Space to toggle, Enter to confirm",
|
|
195
|
+
instructions: false,
|
|
190
196
|
});
|
|
191
|
-
if (!
|
|
192
|
-
logger.error(
|
|
197
|
+
if (!selectedProjects || selectedProjects.length === 0) {
|
|
198
|
+
logger.error("At least one project must be selected");
|
|
193
199
|
process.exit(0);
|
|
194
200
|
}
|
|
195
|
-
return
|
|
201
|
+
return selectedProjects;
|
|
202
|
+
}
|
|
203
|
+
function generatePackageJson(projectName, selectedProjects, packageManager) {
|
|
204
|
+
const hasMobile = selectedProjects.includes("mobile");
|
|
205
|
+
// Package manager versions
|
|
206
|
+
const packageManagerVersions = {
|
|
207
|
+
bun: "bun@1.3.6",
|
|
208
|
+
pnpm: "pnpm@10.28.1",
|
|
209
|
+
yarn: "yarn@4.12.0",
|
|
210
|
+
npm: "npm@11.8.0",
|
|
211
|
+
};
|
|
212
|
+
// Clean commands per package manager
|
|
213
|
+
const cleanCommands = {
|
|
214
|
+
bun: "turbo run clean && node scripts/clean.js && bun pm cache rm",
|
|
215
|
+
pnpm: "turbo run clean && node scripts/clean.js && pnpm store prune",
|
|
216
|
+
yarn: "turbo run clean && node scripts/clean.js && yarn cache clean",
|
|
217
|
+
npm: "turbo run clean && node scripts/clean.js && npm cache clean --force",
|
|
218
|
+
};
|
|
219
|
+
const packageJson = {
|
|
220
|
+
name: projectName,
|
|
221
|
+
version: "1.0.0",
|
|
222
|
+
private: true,
|
|
223
|
+
strict: true,
|
|
224
|
+
scripts: {
|
|
225
|
+
dev: "turbo run dev",
|
|
226
|
+
build: "turbo run build",
|
|
227
|
+
lint: "turbo run lint",
|
|
228
|
+
format: "prettier --write .",
|
|
229
|
+
"check-types": "turbo run check-types",
|
|
230
|
+
clean: cleanCommands[packageManager] || cleanCommands.bun,
|
|
231
|
+
},
|
|
232
|
+
devDependencies: {
|
|
233
|
+
"@packages/eslint-config": packageManager === "npm" ? "*" : "workspace:^",
|
|
234
|
+
"@packages/typescript-config": packageManager === "npm" ? "*" : "workspace:^",
|
|
235
|
+
"@types/node": "^25.0.10",
|
|
236
|
+
prettier: "^3.8.1",
|
|
237
|
+
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
238
|
+
rimraf: "^6.1.2",
|
|
239
|
+
turbo: "^2.7.6",
|
|
240
|
+
typescript: "^5.9.3",
|
|
241
|
+
},
|
|
242
|
+
engines: {
|
|
243
|
+
node: ">=22",
|
|
244
|
+
},
|
|
245
|
+
workspaces: ["projects/*", "packages/*"],
|
|
246
|
+
packageManager: packageManagerVersions[packageManager] || packageManagerVersions.bun,
|
|
247
|
+
};
|
|
248
|
+
// Add expo-dev-menu resolution only if mobile project is selected
|
|
249
|
+
if (hasMobile) {
|
|
250
|
+
packageJson.resolutions = {
|
|
251
|
+
"expo-dev-menu": "^7.0.10",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return packageJson;
|
|
255
|
+
}
|
|
256
|
+
async function generatePackageManagerConfig(projectPath, packageManager, logger) {
|
|
257
|
+
switch (packageManager) {
|
|
258
|
+
case "yarn":
|
|
259
|
+
// Yarn: Use hoisted node_modules for compatibility
|
|
260
|
+
const yarnrcContent = `# Yarn configuration - hoisted node_modules for compatibility
|
|
261
|
+
nodeLinker: node-modules
|
|
262
|
+
`;
|
|
263
|
+
await fs.writeFile(path.join(projectPath, ".yarnrc.yml"), yarnrcContent);
|
|
264
|
+
logger.verbose("Created .yarnrc.yml with node-modules linker");
|
|
265
|
+
break;
|
|
266
|
+
case "pnpm":
|
|
267
|
+
// pnpm: Use hoisted node_modules for compatibility
|
|
268
|
+
const npmrcContent = `# pnpm configuration - hoisted node_modules for compatibility
|
|
269
|
+
node-linker=hoisted
|
|
270
|
+
shamefully-hoist=true
|
|
271
|
+
`;
|
|
272
|
+
await fs.writeFile(path.join(projectPath, ".npmrc"), npmrcContent);
|
|
273
|
+
// pnpm requires pnpm-workspace.yaml instead of workspaces in package.json
|
|
274
|
+
const pnpmWorkspaceContent = `# pnpm workspace configuration
|
|
275
|
+
packages:
|
|
276
|
+
- "projects/*"
|
|
277
|
+
- "packages/*"
|
|
278
|
+
`;
|
|
279
|
+
await fs.writeFile(path.join(projectPath, "pnpm-workspace.yaml"), pnpmWorkspaceContent);
|
|
280
|
+
logger.verbose("Created .npmrc and pnpm-workspace.yaml");
|
|
281
|
+
// Verify workspace structure
|
|
282
|
+
const packagesPath = path.join(projectPath, "packages");
|
|
283
|
+
if (await fs.pathExists(packagesPath)) {
|
|
284
|
+
const pkgs = await fs.readdir(packagesPath);
|
|
285
|
+
logger.verbose(`Found packages: ${pkgs.join(", ")}`);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
logger.warn("Warning: packages/ directory not found in project root");
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
case "bun":
|
|
292
|
+
// Bun: Use hoisted node_modules for compatibility
|
|
293
|
+
const bunfigContent = `# Bun configuration - hoisted node_modules for compatibility
|
|
294
|
+
[install]
|
|
295
|
+
linker = "hoisted"
|
|
296
|
+
`;
|
|
297
|
+
await fs.writeFile(path.join(projectPath, "bunfig.toml"), bunfigContent);
|
|
298
|
+
logger.verbose("Created bunfig.toml with symlink backend");
|
|
299
|
+
break;
|
|
300
|
+
case "npm":
|
|
301
|
+
// npm: Uses node_modules by default, no special config needed
|
|
302
|
+
logger.verbose("npm uses node_modules by default, no config needed");
|
|
303
|
+
break;
|
|
304
|
+
default:
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
196
307
|
}
|
|
197
308
|
async function createProject(projectName, options = {}) {
|
|
198
309
|
const startTime = Date.now();
|
|
@@ -200,37 +311,46 @@ async function createProject(projectName, options = {}) {
|
|
|
200
311
|
const config = await loadConfig();
|
|
201
312
|
// Show banner
|
|
202
313
|
logger.banner();
|
|
314
|
+
// Enable Windows Long Paths if needed
|
|
315
|
+
// await enableWindowsLongPaths(logger);
|
|
203
316
|
// Check for updates
|
|
204
317
|
if (!config.skipUpdateCheck && !options.verbose) {
|
|
205
318
|
await checkForUpdates(logger);
|
|
206
319
|
}
|
|
320
|
+
// Handle legacy --template option
|
|
321
|
+
let presetName = options.preset;
|
|
322
|
+
if (options.template && !presetName) {
|
|
323
|
+
const template = getTemplate(options.template);
|
|
324
|
+
if (template) {
|
|
325
|
+
presetName = template.name;
|
|
326
|
+
logger.warn(`--template is deprecated, use --preset instead`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
207
329
|
let targetDir = projectName;
|
|
208
330
|
// Handle project name
|
|
209
331
|
if (!targetDir) {
|
|
210
332
|
const { projectName: inputName } = await prompts({
|
|
211
|
-
type:
|
|
212
|
-
name:
|
|
213
|
-
message:
|
|
214
|
-
initial:
|
|
333
|
+
type: "text",
|
|
334
|
+
name: "projectName",
|
|
335
|
+
message: "What is your project named?",
|
|
336
|
+
initial: "my-sumit-app",
|
|
215
337
|
validate: async (value) => {
|
|
216
338
|
if (!value.trim())
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
if (value === '.')
|
|
339
|
+
return "Project name is required";
|
|
340
|
+
if (value === ".")
|
|
220
341
|
return true;
|
|
221
342
|
const validation = await validateProjectName(value);
|
|
222
343
|
return validation.valid || validation.message;
|
|
223
344
|
},
|
|
224
345
|
});
|
|
225
346
|
if (!inputName) {
|
|
226
|
-
logger.warn(
|
|
347
|
+
logger.warn("Project creation cancelled.");
|
|
227
348
|
process.exit(0);
|
|
228
349
|
}
|
|
229
350
|
targetDir = inputName;
|
|
230
351
|
}
|
|
231
352
|
else {
|
|
232
|
-
|
|
233
|
-
if (targetDir !== '.') {
|
|
353
|
+
if (targetDir !== ".") {
|
|
234
354
|
const validation = await validateProjectName(targetDir);
|
|
235
355
|
if (!validation.valid) {
|
|
236
356
|
logger.error(validation.message);
|
|
@@ -238,253 +358,331 @@ async function createProject(projectName, options = {}) {
|
|
|
238
358
|
}
|
|
239
359
|
}
|
|
240
360
|
}
|
|
241
|
-
// Type guard to ensure targetDir is defined
|
|
242
361
|
if (!targetDir) {
|
|
243
|
-
logger.error(
|
|
362
|
+
logger.error("Project name is required");
|
|
244
363
|
process.exit(1);
|
|
245
364
|
}
|
|
246
365
|
// Handle current directory installation
|
|
247
|
-
let resolvedProjectPath;
|
|
248
|
-
let displayPath;
|
|
249
|
-
if (targetDir ===
|
|
250
|
-
// Install in current directory
|
|
366
|
+
let resolvedProjectPath;
|
|
367
|
+
let displayPath;
|
|
368
|
+
if (targetDir === ".") {
|
|
251
369
|
resolvedProjectPath = process.cwd();
|
|
252
|
-
displayPath =
|
|
253
|
-
// Validate current directory name for package.json compatibility
|
|
370
|
+
displayPath = "current directory";
|
|
254
371
|
const currentDirName = path.basename(resolvedProjectPath);
|
|
255
372
|
const validation = await validateProjectName(currentDirName);
|
|
256
373
|
if (!validation.valid) {
|
|
257
374
|
logger.error(`Current directory name "${currentDirName}" is not a valid package name.`);
|
|
258
375
|
logger.error(validation.message);
|
|
259
|
-
logger.info(
|
|
376
|
+
logger.info("Please rename your directory to use lowercase letters, numbers, hyphens, underscores, and dots only.");
|
|
260
377
|
process.exit(1);
|
|
261
378
|
}
|
|
262
379
|
}
|
|
263
380
|
else {
|
|
264
|
-
// Install in new directory
|
|
265
381
|
resolvedProjectPath = path.resolve(process.cwd(), targetDir);
|
|
266
382
|
const relativeProjectPath = path.relative(process.cwd(), resolvedProjectPath);
|
|
267
|
-
displayPath = relativeProjectPath ||
|
|
383
|
+
displayPath = relativeProjectPath || "current directory";
|
|
268
384
|
}
|
|
269
385
|
logger.newLine();
|
|
270
|
-
logger.step(1,
|
|
386
|
+
logger.step(1, 5, `Creating project in ${chalk.cyan(displayPath)}`);
|
|
271
387
|
// Check if directory exists and is not empty
|
|
272
388
|
if (await fs.pathExists(resolvedProjectPath)) {
|
|
273
389
|
if (!(await isDirectoryEmpty(resolvedProjectPath))) {
|
|
274
|
-
logger.error(`Directory "${targetDir ===
|
|
390
|
+
logger.error(`Directory "${targetDir === "." ? "current directory" : targetDir}" already exists and is not empty.`);
|
|
275
391
|
const { overwrite } = await prompts({
|
|
276
|
-
type:
|
|
277
|
-
name:
|
|
278
|
-
message:
|
|
392
|
+
type: "confirm",
|
|
393
|
+
name: "overwrite",
|
|
394
|
+
message: "Remove existing files and continue?",
|
|
279
395
|
initial: false,
|
|
280
396
|
});
|
|
281
397
|
if (overwrite) {
|
|
282
|
-
// Add spinner while deleting files
|
|
283
398
|
const deleteSpinner = ora({
|
|
284
|
-
text:
|
|
285
|
-
spinner:
|
|
399
|
+
text: "Removing existing files...",
|
|
400
|
+
spinner: "dots",
|
|
286
401
|
}).start();
|
|
287
402
|
try {
|
|
288
403
|
await fs.emptyDir(resolvedProjectPath);
|
|
289
|
-
deleteSpinner.succeed(chalk.green(
|
|
404
|
+
deleteSpinner.succeed(chalk.green("Existing files removed"));
|
|
290
405
|
}
|
|
291
406
|
catch (error) {
|
|
292
|
-
deleteSpinner.fail(chalk.red(
|
|
407
|
+
deleteSpinner.fail(chalk.red("Failed to remove existing files"));
|
|
293
408
|
logger.error(`Directory cleanup failed: ${error}`);
|
|
294
409
|
process.exit(1);
|
|
295
410
|
}
|
|
296
411
|
}
|
|
297
412
|
else {
|
|
298
|
-
logger.warn(
|
|
413
|
+
logger.warn("Project creation cancelled.");
|
|
299
414
|
process.exit(0);
|
|
300
415
|
}
|
|
301
416
|
}
|
|
302
417
|
}
|
|
303
|
-
// Select
|
|
418
|
+
// Select preset or custom projects
|
|
304
419
|
logger.newLine();
|
|
305
|
-
const
|
|
420
|
+
const selectedProjects = await selectPresetOrProjects(config, logger, presetName, options.projects);
|
|
306
421
|
logger.newLine();
|
|
307
|
-
logger.step(2,
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// Clone template with sparse checkout for subdirectories
|
|
313
|
-
const downloadSpinner = ora({
|
|
314
|
-
text: 'Downloading template...',
|
|
315
|
-
spinner: 'dots12',
|
|
422
|
+
logger.step(2, 5, `Selected projects: ${chalk.cyan(selectedProjects.join(", "))}`);
|
|
423
|
+
// Download base template
|
|
424
|
+
const baseSpinner = ora({
|
|
425
|
+
text: "Downloading base template...",
|
|
426
|
+
spinner: "dots12",
|
|
316
427
|
}).start();
|
|
317
428
|
try {
|
|
318
|
-
logger.verbose(`Downloading from: ${template.url}`);
|
|
319
|
-
// Ensure target directory is ready
|
|
320
|
-
if (targetDir !== '.' && (await fs.pathExists(resolvedProjectPath))) {
|
|
321
|
-
await fs.remove(resolvedProjectPath);
|
|
322
|
-
}
|
|
323
|
-
// Ensure directory exists
|
|
324
429
|
await fs.ensureDir(resolvedProjectPath);
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
430
|
+
const repoMatch = BASE_TEMPLATE.url.match(/github\.com[\/:]([\w-]+)\/([\w-]+)/);
|
|
431
|
+
if (!repoMatch) {
|
|
432
|
+
throw new Error("Invalid GitHub repository URL");
|
|
433
|
+
}
|
|
434
|
+
const [, owner, repo] = repoMatch;
|
|
435
|
+
const repoPath = `${owner}/${repo.replace(".git", "")}/${BASE_TEMPLATE.path}`;
|
|
436
|
+
const emitter = degit(repoPath, {
|
|
437
|
+
cache: false,
|
|
438
|
+
force: true,
|
|
439
|
+
verbose: options.verbose,
|
|
440
|
+
});
|
|
441
|
+
await emitter.clone(resolvedProjectPath);
|
|
442
|
+
baseSpinner.succeed(chalk.green("Base template downloaded!"));
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
baseSpinner.fail(chalk.red("Failed to download base template"));
|
|
446
|
+
logger.error(`Download failed: ${error.message || error}`);
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
// Download selected projects
|
|
450
|
+
logger.newLine();
|
|
451
|
+
logger.step(3, 5, "Downloading selected projects...");
|
|
452
|
+
const projectsDir = path.join(resolvedProjectPath, "projects");
|
|
453
|
+
await fs.ensureDir(projectsDir);
|
|
454
|
+
for (const projectName of selectedProjects) {
|
|
455
|
+
const project = PROJECTS[projectName];
|
|
456
|
+
const projectSpinner = ora({
|
|
457
|
+
text: `Downloading ${projectName}...`,
|
|
458
|
+
spinner: "dots",
|
|
459
|
+
}).start();
|
|
460
|
+
try {
|
|
461
|
+
const repoMatch = BASE_TEMPLATE.url.match(/github\.com[\/:]([\w-]+)\/([\w-]+)/);
|
|
331
462
|
if (!repoMatch) {
|
|
332
|
-
throw new Error(
|
|
463
|
+
throw new Error("Invalid GitHub repository URL");
|
|
333
464
|
}
|
|
334
465
|
const [, owner, repo] = repoMatch;
|
|
335
|
-
const repoPath = `${owner}/${repo.replace(
|
|
336
|
-
// degit downloads ONLY this specific folder
|
|
466
|
+
const repoPath = `${owner}/${repo.replace(".git", "")}/${project.path}`;
|
|
337
467
|
const emitter = degit(repoPath, {
|
|
338
468
|
cache: false,
|
|
339
469
|
force: true,
|
|
340
470
|
verbose: options.verbose,
|
|
341
471
|
});
|
|
342
|
-
|
|
343
|
-
await emitter.clone(
|
|
344
|
-
|
|
472
|
+
const projectTargetPath = path.join(projectsDir, project.name);
|
|
473
|
+
await emitter.clone(projectTargetPath);
|
|
474
|
+
projectSpinner.succeed(chalk.green(`Downloaded ${projectName}`));
|
|
345
475
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const [, owner, repo] = repoMatch;
|
|
351
|
-
const emitter = degit(`${owner}/${repo.replace('.git', '')}`, {
|
|
352
|
-
cache: false,
|
|
353
|
-
force: true,
|
|
354
|
-
verbose: options.verbose,
|
|
355
|
-
});
|
|
356
|
-
await emitter.clone(resolvedProjectPath);
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
throw new Error('Invalid repository URL');
|
|
360
|
-
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
projectSpinner.fail(chalk.red(`Failed to download ${projectName}`));
|
|
478
|
+
logger.error(`Download failed: ${error.message || error}`);
|
|
479
|
+
process.exit(1);
|
|
361
480
|
}
|
|
362
|
-
downloadSpinner.succeed(chalk.green('Template downloaded successfully!'));
|
|
363
481
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
482
|
+
// Detect/Select package manager (moved before package.json generation)
|
|
483
|
+
const packageManager = await selectPackageManager(config, logger, options.packageManager);
|
|
484
|
+
const pmInfo = getPackageManagerInfo(packageManager);
|
|
485
|
+
// npm Fix: Recursively replace "workspace:" protocol with "*" in all package.json files
|
|
486
|
+
// because npm doesn't support "workspace:" protocol (it uses "*" or versions directly)
|
|
487
|
+
if (packageManager === "npm") {
|
|
488
|
+
logger.verbose("Adjusting workspace dependencies for npm compatibility...");
|
|
489
|
+
const scanDir = async (dir) => {
|
|
490
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
491
|
+
for (const entry of entries) {
|
|
492
|
+
const fullPath = path.join(dir, entry.name);
|
|
493
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") {
|
|
494
|
+
await scanDir(fullPath);
|
|
495
|
+
}
|
|
496
|
+
else if (entry.name === "package.json") {
|
|
497
|
+
try {
|
|
498
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
499
|
+
if (content.includes("workspace:")) {
|
|
500
|
+
logger.verbose(`Fixing workspace protocol in ${path.relative(resolvedProjectPath, fullPath)}`);
|
|
501
|
+
// Replace "workspace:^" or "workspace:*" with "*" for npm
|
|
502
|
+
const fixedContent = content.replace(/"workspace:\^"/g, '"*"').replace(/"workspace:\*"/g, '"*"');
|
|
503
|
+
await fs.writeFile(fullPath, fixedContent);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
// Ignore read errors
|
|
508
|
+
}
|
|
509
|
+
}
|
|
372
510
|
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Ignore
|
|
376
|
-
}
|
|
377
|
-
process.exit(1);
|
|
511
|
+
};
|
|
512
|
+
await scanDir(resolvedProjectPath);
|
|
378
513
|
}
|
|
514
|
+
// Generate package.json based on selected projects and package manager
|
|
515
|
+
const actualProjectName = targetDir === "." ? path.basename(resolvedProjectPath) : targetDir;
|
|
516
|
+
const packageJsonContent = generatePackageJson(actualProjectName, selectedProjects, packageManager);
|
|
517
|
+
await fs.writeJson(path.join(resolvedProjectPath, "package.json"), packageJsonContent, { spaces: 2 });
|
|
518
|
+
logger.verbose("Generated package.json with correct configuration");
|
|
519
|
+
// Generate package manager config files to ensure node_modules in root
|
|
520
|
+
await generatePackageManagerConfig(resolvedProjectPath, packageManager, logger);
|
|
379
521
|
// Clean up git directory
|
|
380
522
|
await cleanupGitDirectory(resolvedProjectPath, logger);
|
|
381
|
-
logger.verbose('Removed template .git directory');
|
|
382
|
-
// Detect/Select package manager
|
|
383
|
-
const packageManager = await selectPackageManager(config, logger, options.packageManager);
|
|
384
|
-
const pmInfo = getPackageManagerInfo(packageManager);
|
|
385
523
|
logger.newLine();
|
|
386
|
-
logger.step(
|
|
524
|
+
logger.step(4, 5, `Using package manager: ${chalk.cyan(packageManager)}`);
|
|
387
525
|
// Install dependencies
|
|
388
526
|
if (!options.skipInstall) {
|
|
389
527
|
const installSpinner = ora({
|
|
390
528
|
text: `Installing dependencies with ${packageManager}...`,
|
|
391
|
-
spinner:
|
|
529
|
+
spinner: "bouncingBar",
|
|
392
530
|
}).start();
|
|
393
531
|
try {
|
|
394
532
|
await execa(pmInfo.command, pmInfo.installArgs, {
|
|
395
533
|
cwd: resolvedProjectPath,
|
|
396
|
-
stdio: options.verbose ?
|
|
534
|
+
stdio: options.verbose ? "inherit" : "pipe",
|
|
397
535
|
});
|
|
398
|
-
installSpinner.succeed(chalk.green(
|
|
536
|
+
installSpinner.succeed(chalk.green("Dependencies installed successfully!"));
|
|
399
537
|
}
|
|
400
538
|
catch (error) {
|
|
401
|
-
installSpinner.fail(chalk.red(
|
|
539
|
+
installSpinner.fail(chalk.red("Failed to install dependencies"));
|
|
402
540
|
logger.error(`Dependency installation failed: ${error}`);
|
|
403
|
-
logger.warn(
|
|
404
|
-
logger.info(`
|
|
405
|
-
logger.info(
|
|
541
|
+
logger.warn("You can install dependencies manually by running:");
|
|
542
|
+
logger.info(`cd ${targetDir}`);
|
|
543
|
+
logger.info(`${pmInfo.command} ${pmInfo.installArgs.join(" ")}`);
|
|
406
544
|
}
|
|
407
545
|
}
|
|
408
546
|
else {
|
|
409
|
-
logger.info(
|
|
410
|
-
}
|
|
411
|
-
// Initialize git repository
|
|
412
|
-
// logger.step(4, 5, 'Initializing git repository...');
|
|
413
|
-
// logger.newLine();
|
|
414
|
-
// if (options.git !== false && config.git !== false) {
|
|
415
|
-
// const gitSuccess = await initializeGitRepository(projectPath, logger);
|
|
416
|
-
// if (gitSuccess) {
|
|
417
|
-
// logger.success('Git repository initialized');
|
|
418
|
-
// } else {
|
|
419
|
-
// logger.warn(
|
|
420
|
-
// 'Git initialization failed, but project was created successfully'
|
|
421
|
-
// );
|
|
422
|
-
// }
|
|
423
|
-
// } else {
|
|
424
|
-
// logger.info('Skipped git initialization');
|
|
425
|
-
// }
|
|
426
|
-
// Get the actual project name for display
|
|
427
|
-
const actualProjectName = targetDir === '.' ? path.basename(resolvedProjectPath) : targetDir;
|
|
547
|
+
logger.info("Skipped dependency installation");
|
|
548
|
+
}
|
|
428
549
|
// Success message
|
|
429
550
|
const duration = formatDuration(Date.now() - startTime);
|
|
430
551
|
logger.newLine();
|
|
431
|
-
logger.step(
|
|
552
|
+
logger.step(5, 5, "Project setup complete!");
|
|
432
553
|
logger.newLine();
|
|
433
554
|
logger.success(`🎉 Successfully created ${chalk.green(actualProjectName)} in ${chalk.green(duration)}`);
|
|
434
555
|
logger.newLine();
|
|
435
|
-
// Next steps
|
|
556
|
+
// Next steps
|
|
436
557
|
const nextSteps = [];
|
|
437
|
-
if (targetDir ===
|
|
438
|
-
|
|
439
|
-
nextSteps.push(`${pmInfo.command} ${pmInfo.name === 'npm' ? 'run ' : ''}dev`);
|
|
558
|
+
if (targetDir === ".") {
|
|
559
|
+
nextSteps.push(`${pmInfo.command} ${pmInfo.name === "npm" ? "run " : ""}dev`);
|
|
440
560
|
}
|
|
441
561
|
else {
|
|
442
|
-
|
|
443
|
-
nextSteps.push(
|
|
444
|
-
nextSteps.push(`${pmInfo.command} ${pmInfo.name === 'npm' ? 'run ' : ''}dev`);
|
|
562
|
+
nextSteps.push(`cd ${chalk.hex("#FFFFFF")(actualProjectName)}`);
|
|
563
|
+
nextSteps.push(`${pmInfo.command} ${pmInfo.name === "npm" ? "run " : ""}dev`);
|
|
445
564
|
}
|
|
446
565
|
if (options.skipInstall) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
nextSteps.splice(0, 0, installCmd); // Add as first step
|
|
566
|
+
const installCmd = `${pmInfo.command} ${pmInfo.installArgs.join(" ")}`;
|
|
567
|
+
if (targetDir === ".") {
|
|
568
|
+
nextSteps.splice(0, 0, installCmd);
|
|
451
569
|
}
|
|
452
570
|
else {
|
|
453
|
-
nextSteps.splice(1, 0, installCmd);
|
|
571
|
+
nextSteps.splice(1, 0, installCmd);
|
|
454
572
|
}
|
|
455
573
|
}
|
|
456
|
-
logger.box(nextSteps
|
|
457
|
-
.map((step, i) => `${i + 1}. ${chalk.hex('#FFE600FF')(step)}`)
|
|
458
|
-
.join('\n'), '🚀 Get started');
|
|
574
|
+
logger.box(nextSteps.map((step, i) => `${i + 1}. ${chalk.hex("#FFE600FF")(step)}`).join("\n"), "🚀 Get started");
|
|
459
575
|
logger.newLine();
|
|
460
|
-
logger.log(
|
|
576
|
+
logger.log("Happy coding! 🎨✨");
|
|
461
577
|
logger.newLine();
|
|
462
578
|
}
|
|
579
|
+
async function addProject(projectName, options) {
|
|
580
|
+
const logger = new Logger(options.verbose);
|
|
581
|
+
logger.banner();
|
|
582
|
+
// Validate project name
|
|
583
|
+
if (!PROJECTS[projectName]) {
|
|
584
|
+
logger.error(`Invalid project: ${projectName}`);
|
|
585
|
+
logger.info(`Available projects: ${Object.keys(PROJECTS).join(", ")}`);
|
|
586
|
+
process.exit(1);
|
|
587
|
+
}
|
|
588
|
+
// Check if we're in a SumitApp project
|
|
589
|
+
const projectsDir = path.join(process.cwd(), "projects");
|
|
590
|
+
const packagesDir = path.join(process.cwd(), "packages");
|
|
591
|
+
if (!(await fs.pathExists(projectsDir)) || !(await fs.pathExists(packagesDir))) {
|
|
592
|
+
logger.error("This does not appear to be a SumitApp project.");
|
|
593
|
+
logger.info("Run this command from the root of your SumitApp project.");
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
// Check if project already exists
|
|
597
|
+
const targetPath = path.join(projectsDir, projectName);
|
|
598
|
+
if (await fs.pathExists(targetPath)) {
|
|
599
|
+
logger.error(`Project "${projectName}" already exists.`);
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
// Download the project
|
|
603
|
+
const spinner = ora({
|
|
604
|
+
text: `Adding ${projectName} project...`,
|
|
605
|
+
spinner: "dots12",
|
|
606
|
+
}).start();
|
|
607
|
+
try {
|
|
608
|
+
const project = PROJECTS[projectName];
|
|
609
|
+
const repoMatch = BASE_TEMPLATE.url.match(/github\.com[\/:]([\w-]+)\/([\w-]+)/);
|
|
610
|
+
if (!repoMatch) {
|
|
611
|
+
throw new Error("Invalid GitHub repository URL");
|
|
612
|
+
}
|
|
613
|
+
const [, owner, repo] = repoMatch;
|
|
614
|
+
const repoPath = `${owner}/${repo.replace(".git", "")}/${project.path}`;
|
|
615
|
+
const emitter = degit(repoPath, {
|
|
616
|
+
cache: false,
|
|
617
|
+
force: true,
|
|
618
|
+
verbose: options.verbose,
|
|
619
|
+
});
|
|
620
|
+
await emitter.clone(targetPath);
|
|
621
|
+
spinner.succeed(chalk.green(`Added ${projectName} project successfully!`));
|
|
622
|
+
// Update package.json if adding mobile (needs expo-dev-menu resolution)
|
|
623
|
+
if (projectName === "mobile") {
|
|
624
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
625
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
626
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
627
|
+
if (!packageJson.resolutions) {
|
|
628
|
+
packageJson.resolutions = {};
|
|
629
|
+
}
|
|
630
|
+
if (!packageJson.resolutions["expo-dev-menu"]) {
|
|
631
|
+
packageJson.resolutions["expo-dev-menu"] = "^7.0.10";
|
|
632
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
633
|
+
logger.info("Updated package.json with expo-dev-menu resolution");
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
logger.newLine();
|
|
638
|
+
logger.info("Next steps:");
|
|
639
|
+
logger.info(" 1. Run: bun install");
|
|
640
|
+
logger.info(" 2. Run: bun dev");
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
spinner.fail(chalk.red(`Failed to add ${projectName} project`));
|
|
644
|
+
logger.error(error.message);
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
463
648
|
// CLI Setup
|
|
464
649
|
const program = new Command();
|
|
465
650
|
const require = createRequire(import.meta.url);
|
|
466
|
-
const packageJson = require(
|
|
651
|
+
const packageJson = require("../package.json");
|
|
467
652
|
const CLI_VERSION = packageJson.version;
|
|
468
653
|
program
|
|
469
|
-
.name(
|
|
654
|
+
.name("create-sumit-app")
|
|
470
655
|
.description("✨ A beautiful CLI to bootstrap projects from Sumit.app's project templates")
|
|
471
656
|
.version(CLI_VERSION)
|
|
472
|
-
.argument(
|
|
473
|
-
.option(
|
|
474
|
-
.option(
|
|
475
|
-
.option(
|
|
476
|
-
|
|
477
|
-
.option(
|
|
657
|
+
.argument("[project-name]", "The name of the project to create")
|
|
658
|
+
.option("-p, --preset <preset>", "Preset to use (default, mobile-and-backend, website-and-backend, custom)")
|
|
659
|
+
.option("--projects <projects>", "Comma-separated list of projects (website, mobile, backend)")
|
|
660
|
+
.option("-m, --package-manager <manager>", "Package manager to use (bun)")
|
|
661
|
+
.option("-v, --verbose", "Enable verbose logging")
|
|
662
|
+
.option("--skip-install", "Skip dependency installation")
|
|
663
|
+
.option("-t, --template <template>", "[DEPRECATED] Use --preset instead")
|
|
478
664
|
.action(async (projectName, options) => {
|
|
665
|
+
// Parse projects option if provided
|
|
666
|
+
if (options.projects) {
|
|
667
|
+
options.projects = options.projects.split(",").map((p) => p.trim());
|
|
668
|
+
}
|
|
479
669
|
await createProject(projectName, options);
|
|
480
670
|
});
|
|
671
|
+
// Add command for adding projects later
|
|
672
|
+
program
|
|
673
|
+
.command("add <project>")
|
|
674
|
+
.description("Add a project to an existing SumitApp setup (website, mobile, backend)")
|
|
675
|
+
.option("-v, --verbose", "Enable verbose logging")
|
|
676
|
+
.action(async (projectName, options) => {
|
|
677
|
+
await addProject(projectName, options);
|
|
678
|
+
});
|
|
481
679
|
// Config command
|
|
482
680
|
program
|
|
483
|
-
.command(
|
|
484
|
-
.description(
|
|
485
|
-
.option(
|
|
486
|
-
.option(
|
|
487
|
-
.option(
|
|
681
|
+
.command("config")
|
|
682
|
+
.description("Manage CLI configuration")
|
|
683
|
+
.option("-l, --list", "List current configuration")
|
|
684
|
+
.option("-s, --set <key=value>", "Set configuration value")
|
|
685
|
+
.option("-r, --reset", "Reset configuration to defaults")
|
|
488
686
|
.action(async (options) => {
|
|
489
687
|
const logger = new Logger();
|
|
490
688
|
if (options.list) {
|
|
@@ -493,67 +691,73 @@ program
|
|
|
493
691
|
}
|
|
494
692
|
if (options.reset) {
|
|
495
693
|
await resetConfig();
|
|
496
|
-
logger.success(
|
|
694
|
+
logger.success("Configuration reset to defaults");
|
|
497
695
|
return;
|
|
498
696
|
}
|
|
499
697
|
if (options.set) {
|
|
500
|
-
const [key, value] = options.set.split(
|
|
698
|
+
const [key, value] = options.set.split("=");
|
|
501
699
|
if (!key || value === undefined) {
|
|
502
|
-
logger.error(
|
|
700
|
+
logger.error("Invalid format. Use: --set key=value");
|
|
503
701
|
return;
|
|
504
702
|
}
|
|
505
|
-
|
|
506
|
-
const validKeys = [
|
|
507
|
-
'defaultTemplate',
|
|
508
|
-
'packageManager',
|
|
509
|
-
// 'git',
|
|
510
|
-
'verbose',
|
|
511
|
-
'skipUpdateCheck',
|
|
512
|
-
];
|
|
703
|
+
const validKeys = ["defaultPreset", "packageManager", "verbose", "skipUpdateCheck"];
|
|
513
704
|
if (!validKeys.includes(key)) {
|
|
514
705
|
logger.error(`Invalid configuration key: ${key}`);
|
|
515
|
-
logger.info(`Valid keys: ${validKeys.join(
|
|
706
|
+
logger.info(`Valid keys: ${validKeys.join(", ")}`);
|
|
516
707
|
return;
|
|
517
708
|
}
|
|
518
|
-
// Parse boolean values
|
|
519
709
|
let parsedValue = value;
|
|
520
|
-
if (value ===
|
|
521
|
-
parsedValue = value ===
|
|
710
|
+
if (value === "true" || value === "false") {
|
|
711
|
+
parsedValue = value === "true";
|
|
522
712
|
}
|
|
523
713
|
await updateConfig(key, parsedValue);
|
|
524
714
|
logger.success(`Configuration updated: ${chalk.cyan(key)} = ${chalk.green(String(parsedValue))}`);
|
|
525
715
|
logger.info(`Config file: ${chalk.gray(getConfigPath())}`);
|
|
526
716
|
}
|
|
527
717
|
});
|
|
528
|
-
//
|
|
718
|
+
// Presets command
|
|
719
|
+
program
|
|
720
|
+
.command("presets")
|
|
721
|
+
.description("List available presets")
|
|
722
|
+
.action(() => {
|
|
723
|
+
listPresets();
|
|
724
|
+
});
|
|
725
|
+
// Projects command
|
|
726
|
+
program
|
|
727
|
+
.command("projects")
|
|
728
|
+
.description("List available projects")
|
|
729
|
+
.action(() => {
|
|
730
|
+
listProjects();
|
|
731
|
+
});
|
|
732
|
+
// Templates command (legacy, for backward compatibility)
|
|
529
733
|
program
|
|
530
|
-
.command(
|
|
531
|
-
.description('List available templates')
|
|
734
|
+
.command("templates")
|
|
735
|
+
.description('[DEPRECATED] List available templates (use "presets" instead)')
|
|
532
736
|
.action(() => {
|
|
533
737
|
listTemplates();
|
|
534
738
|
});
|
|
535
739
|
// Info command
|
|
536
740
|
program
|
|
537
|
-
.command(
|
|
538
|
-
.description(
|
|
741
|
+
.command("info")
|
|
742
|
+
.description("Display environment info")
|
|
539
743
|
.action(async () => {
|
|
540
744
|
const logger = new Logger();
|
|
541
|
-
const packageJson = await fs.readJson(path.join(__dirname,
|
|
745
|
+
const packageJson = await fs.readJson(path.join(__dirname, "..", "package.json"));
|
|
542
746
|
logger.banner();
|
|
543
747
|
logger.box(`Version: ${packageJson.version}\n` +
|
|
544
748
|
`Node: ${process.version}\n` +
|
|
545
749
|
`Platform: ${process.platform}\n` +
|
|
546
750
|
`Architecture: ${process.arch}\n` +
|
|
547
|
-
`Config: ${getConfigPath()}`,
|
|
751
|
+
`Config: ${getConfigPath()}`, "🔍 Environment Info");
|
|
548
752
|
});
|
|
549
753
|
// Handle uncaught errors
|
|
550
|
-
process.on(
|
|
754
|
+
process.on("uncaughtException", (error) => {
|
|
551
755
|
const logger = new Logger();
|
|
552
756
|
logger.error(`Unexpected error: ${error.message}`);
|
|
553
|
-
logger.debug(error.stack ||
|
|
757
|
+
logger.debug(error.stack || "");
|
|
554
758
|
process.exit(1);
|
|
555
759
|
});
|
|
556
|
-
process.on(
|
|
760
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
557
761
|
const logger = new Logger();
|
|
558
762
|
logger.error(`Unhandled rejection at: ${promise}, reason: ${reason}`);
|
|
559
763
|
process.exit(1);
|