create-blitzpack 0.1.17 → 0.1.19
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/README.md +5 -3
- package/dist/index.js +570 -244
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -31,9 +31,11 @@ pnpm create blitzpack [project-name] [options]
|
|
|
31
31
|
|
|
32
32
|
The scaffold wizard supports three profiles:
|
|
33
33
|
|
|
34
|
-
- **Recommended**:
|
|
35
|
-
- **Platform-
|
|
36
|
-
- **
|
|
34
|
+
- **Recommended**: Core app with everything included.
|
|
35
|
+
- **Platform-Agnostic**: Core app without dockerfiles.
|
|
36
|
+
- **Modular**: Core app with features of your choice.
|
|
37
|
+
|
|
38
|
+
Each profile is a setup path. The **Modular** path opens full feature customization before files are created.
|
|
37
39
|
|
|
38
40
|
## Requirements
|
|
39
41
|
|
package/dist/index.js
CHANGED
|
@@ -10,13 +10,82 @@ import { fileURLToPath } from "url";
|
|
|
10
10
|
import chalk4 from "chalk";
|
|
11
11
|
import { spawn } from "child_process";
|
|
12
12
|
import fs3 from "fs-extra";
|
|
13
|
-
import
|
|
13
|
+
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
14
|
+
import ora2 from "ora";
|
|
14
15
|
import path4 from "path";
|
|
15
|
-
import prompts2 from "prompts";
|
|
16
16
|
|
|
17
17
|
// src/checks.ts
|
|
18
18
|
import chalk from "chalk";
|
|
19
|
+
import { execSync as execSync3 } from "child_process";
|
|
20
|
+
import ora from "ora";
|
|
21
|
+
|
|
22
|
+
// src/docker.ts
|
|
19
23
|
import { execSync } from "child_process";
|
|
24
|
+
function isDockerInstalled() {
|
|
25
|
+
try {
|
|
26
|
+
execSync("docker --version", { stdio: "ignore" });
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function isDockerRunning() {
|
|
33
|
+
try {
|
|
34
|
+
execSync("docker info", { stdio: "ignore", timeout: 3e3 });
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function runDockerCompose(targetDir) {
|
|
41
|
+
try {
|
|
42
|
+
execSync("docker compose up -d", {
|
|
43
|
+
cwd: targetDir,
|
|
44
|
+
stdio: "inherit"
|
|
45
|
+
});
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function runDatabaseMigrations(targetDir) {
|
|
52
|
+
try {
|
|
53
|
+
const isWindows = process.platform === "win32";
|
|
54
|
+
execSync(isWindows ? "pnpm.cmd db:migrate" : "pnpm db:migrate", {
|
|
55
|
+
cwd: targetDir,
|
|
56
|
+
stdio: "inherit"
|
|
57
|
+
});
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/git.ts
|
|
65
|
+
import { execSync as execSync2 } from "child_process";
|
|
66
|
+
function isGitInstalled() {
|
|
67
|
+
try {
|
|
68
|
+
execSync2("git --version", { stdio: "ignore" });
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function initGit(targetDir) {
|
|
75
|
+
try {
|
|
76
|
+
execSync2("git init", { cwd: targetDir, stdio: "ignore" });
|
|
77
|
+
execSync2("git add -A", { cwd: targetDir, stdio: "ignore" });
|
|
78
|
+
execSync2('git commit -m "Initial commit from create-blitzpack"', {
|
|
79
|
+
cwd: targetDir,
|
|
80
|
+
stdio: "ignore"
|
|
81
|
+
});
|
|
82
|
+
return true;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/checks.ts
|
|
20
89
|
function checkNodeVersion() {
|
|
21
90
|
try {
|
|
22
91
|
const nodeVersion = process.version;
|
|
@@ -24,127 +93,126 @@ function checkNodeVersion() {
|
|
|
24
93
|
if (majorVersion >= 20) {
|
|
25
94
|
return {
|
|
26
95
|
passed: true,
|
|
27
|
-
name: "Node.js"
|
|
96
|
+
name: "Node.js",
|
|
97
|
+
required: true,
|
|
98
|
+
message: nodeVersion
|
|
28
99
|
};
|
|
29
100
|
}
|
|
30
101
|
return {
|
|
31
102
|
passed: false,
|
|
32
103
|
name: "Node.js",
|
|
104
|
+
required: true,
|
|
33
105
|
message: `Node.js >= 20.0.0 required (found ${nodeVersion})`
|
|
34
106
|
};
|
|
35
107
|
} catch {
|
|
36
108
|
return {
|
|
37
109
|
passed: false,
|
|
38
110
|
name: "Node.js",
|
|
111
|
+
required: true,
|
|
39
112
|
message: "Failed to check Node.js version"
|
|
40
113
|
};
|
|
41
114
|
}
|
|
42
115
|
}
|
|
43
116
|
function checkPnpmInstalled() {
|
|
44
117
|
try {
|
|
45
|
-
|
|
118
|
+
const version = execSync3("pnpm --version", {
|
|
119
|
+
encoding: "utf-8",
|
|
120
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
121
|
+
}).trim();
|
|
46
122
|
return {
|
|
47
123
|
passed: true,
|
|
48
|
-
name: "pnpm"
|
|
124
|
+
name: "pnpm",
|
|
125
|
+
required: true,
|
|
126
|
+
message: `v${version}`
|
|
49
127
|
};
|
|
50
128
|
} catch {
|
|
51
129
|
return {
|
|
52
130
|
passed: false,
|
|
53
131
|
name: "pnpm",
|
|
132
|
+
required: true,
|
|
54
133
|
message: "pnpm not found. Install: npm install -g pnpm"
|
|
55
134
|
};
|
|
56
135
|
}
|
|
57
136
|
}
|
|
137
|
+
function checkGit() {
|
|
138
|
+
const installed = isGitInstalled();
|
|
139
|
+
return {
|
|
140
|
+
passed: installed,
|
|
141
|
+
name: "git",
|
|
142
|
+
required: false,
|
|
143
|
+
message: installed ? "available (repository initialization supported)" : "not found (git init step will be skipped)"
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function checkDocker() {
|
|
147
|
+
const installed = isDockerInstalled();
|
|
148
|
+
return {
|
|
149
|
+
passed: installed,
|
|
150
|
+
name: "Docker",
|
|
151
|
+
required: false,
|
|
152
|
+
message: installed ? "available (automatic local DB setup supported)" : "not found (start PostgreSQL separately)"
|
|
153
|
+
};
|
|
154
|
+
}
|
|
58
155
|
async function runPreflightChecks() {
|
|
59
156
|
console.log();
|
|
60
|
-
console.log(chalk.bold("
|
|
157
|
+
console.log(chalk.bold(" System readiness"));
|
|
158
|
+
console.log(chalk.dim(" Validating required and optional local tooling..."));
|
|
61
159
|
console.log();
|
|
62
|
-
const checks = [
|
|
63
|
-
|
|
160
|
+
const checks = [
|
|
161
|
+
checkNodeVersion(),
|
|
162
|
+
checkPnpmInstalled(),
|
|
163
|
+
checkGit(),
|
|
164
|
+
checkDocker()
|
|
165
|
+
];
|
|
166
|
+
const requiredFailures = [];
|
|
167
|
+
const optionalWarnings = [];
|
|
64
168
|
for (const check of checks) {
|
|
169
|
+
const spinner = ora(`Checking ${check.name}...`).start();
|
|
65
170
|
if (check.passed) {
|
|
66
|
-
|
|
171
|
+
spinner.succeed(chalk.bold(check.name));
|
|
67
172
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
173
|
+
if (check.required) {
|
|
174
|
+
requiredFailures.push(check);
|
|
175
|
+
spinner.fail(chalk.bold(check.name));
|
|
176
|
+
} else {
|
|
177
|
+
optionalWarnings.push(check);
|
|
178
|
+
spinner.warn(chalk.bold(check.name));
|
|
72
179
|
}
|
|
180
|
+
console.log(chalk.dim(` ${check.message}`));
|
|
73
181
|
}
|
|
74
182
|
}
|
|
75
183
|
console.log();
|
|
76
|
-
if (
|
|
184
|
+
if (optionalWarnings.length > 0) {
|
|
185
|
+
console.log(chalk.yellow(" Optional tools missing:"));
|
|
186
|
+
for (const warning of optionalWarnings) {
|
|
187
|
+
console.log(chalk.dim(` \u2022 ${warning.name}: ${warning.message}`));
|
|
188
|
+
}
|
|
189
|
+
console.log();
|
|
190
|
+
}
|
|
191
|
+
if (requiredFailures.length > 0) {
|
|
77
192
|
console.log(
|
|
78
193
|
chalk.red(" \u2716"),
|
|
79
|
-
"
|
|
194
|
+
"Required dependencies are missing. Fix the items below and try again:"
|
|
80
195
|
);
|
|
196
|
+
for (const failure of requiredFailures) {
|
|
197
|
+
console.log(chalk.dim(` \u2022 ${failure.name}: ${failure.message}`));
|
|
198
|
+
}
|
|
81
199
|
console.log();
|
|
82
200
|
return false;
|
|
83
201
|
}
|
|
84
202
|
return true;
|
|
85
203
|
}
|
|
86
204
|
|
|
87
|
-
// src/docker.ts
|
|
88
|
-
import { execSync as execSync2 } from "child_process";
|
|
89
|
-
function isDockerRunning() {
|
|
90
|
-
try {
|
|
91
|
-
execSync2("docker info", { stdio: "ignore", timeout: 3e3 });
|
|
92
|
-
return true;
|
|
93
|
-
} catch {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function runDockerCompose(targetDir) {
|
|
98
|
-
try {
|
|
99
|
-
execSync2("docker compose up -d", {
|
|
100
|
-
cwd: targetDir,
|
|
101
|
-
stdio: "inherit"
|
|
102
|
-
});
|
|
103
|
-
return true;
|
|
104
|
-
} catch {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function runDatabaseMigrations(targetDir) {
|
|
109
|
-
try {
|
|
110
|
-
const isWindows = process.platform === "win32";
|
|
111
|
-
execSync2(isWindows ? "pnpm.cmd db:migrate" : "pnpm db:migrate", {
|
|
112
|
-
cwd: targetDir,
|
|
113
|
-
stdio: "inherit"
|
|
114
|
-
});
|
|
115
|
-
return true;
|
|
116
|
-
} catch {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/git.ts
|
|
122
|
-
import { execSync as execSync3 } from "child_process";
|
|
123
|
-
function isGitInstalled() {
|
|
124
|
-
try {
|
|
125
|
-
execSync3("git --version", { stdio: "ignore" });
|
|
126
|
-
return true;
|
|
127
|
-
} catch {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function initGit(targetDir) {
|
|
132
|
-
try {
|
|
133
|
-
execSync3("git init", { cwd: targetDir, stdio: "ignore" });
|
|
134
|
-
execSync3("git add -A", { cwd: targetDir, stdio: "ignore" });
|
|
135
|
-
execSync3('git commit -m "Initial commit from create-blitzpack"', {
|
|
136
|
-
cwd: targetDir,
|
|
137
|
-
stdio: "ignore"
|
|
138
|
-
});
|
|
139
|
-
return true;
|
|
140
|
-
} catch {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
205
|
// src/prompts.ts
|
|
146
206
|
import chalk3 from "chalk";
|
|
147
|
-
import
|
|
207
|
+
import {
|
|
208
|
+
cancel,
|
|
209
|
+
confirm,
|
|
210
|
+
isCancel,
|
|
211
|
+
multiselect,
|
|
212
|
+
note,
|
|
213
|
+
select,
|
|
214
|
+
text
|
|
215
|
+
} from "@clack/prompts";
|
|
148
216
|
|
|
149
217
|
// src/constants.ts
|
|
150
218
|
var REPLACEABLE_FILES = [
|
|
@@ -229,10 +297,49 @@ var FEATURE_EXCLUSIONS = {
|
|
|
229
297
|
"apps/api/.dockerignore",
|
|
230
298
|
"apps/api/Dockerfile",
|
|
231
299
|
"apps/web/Dockerfile",
|
|
232
|
-
"deploy/docker"
|
|
300
|
+
"deploy/docker",
|
|
301
|
+
".github/workflows/docker-build.yml"
|
|
233
302
|
],
|
|
234
303
|
ciCd: [".github/workflows/cd.yml"]
|
|
235
304
|
};
|
|
305
|
+
var PROJECT_PROFILES = [
|
|
306
|
+
{
|
|
307
|
+
key: "recommended",
|
|
308
|
+
name: "Recommended",
|
|
309
|
+
description: "Core app with everything included",
|
|
310
|
+
defaultFeatures: {
|
|
311
|
+
testing: true,
|
|
312
|
+
admin: true,
|
|
313
|
+
uploads: true,
|
|
314
|
+
dockerDeploy: true,
|
|
315
|
+
ciCd: true
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
key: "platformAgnostic",
|
|
320
|
+
name: "Platform-Agnostic",
|
|
321
|
+
description: "Core app without dockerfiles",
|
|
322
|
+
defaultFeatures: {
|
|
323
|
+
testing: true,
|
|
324
|
+
admin: true,
|
|
325
|
+
uploads: true,
|
|
326
|
+
dockerDeploy: false,
|
|
327
|
+
ciCd: false
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
key: "modular",
|
|
332
|
+
name: "Modular",
|
|
333
|
+
description: "Core app with features of your choice",
|
|
334
|
+
defaultFeatures: {
|
|
335
|
+
testing: false,
|
|
336
|
+
admin: false,
|
|
337
|
+
uploads: false,
|
|
338
|
+
dockerDeploy: false,
|
|
339
|
+
ciCd: false
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
];
|
|
236
343
|
|
|
237
344
|
// src/utils.ts
|
|
238
345
|
import chalk2 from "chalk";
|
|
@@ -350,46 +457,84 @@ function printError(message) {
|
|
|
350
457
|
}
|
|
351
458
|
|
|
352
459
|
// src/prompts.ts
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
questions.push({
|
|
371
|
-
type: "text",
|
|
372
|
-
name: "projectDescription",
|
|
373
|
-
message: "Project description:",
|
|
374
|
-
initial: DEFAULT_DESCRIPTION
|
|
375
|
-
});
|
|
376
|
-
let cancelled = false;
|
|
377
|
-
const response = await prompts(questions, {
|
|
378
|
-
onCancel: () => {
|
|
379
|
-
cancelled = true;
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
if (cancelled) {
|
|
460
|
+
var FEATURE_LABELS = {
|
|
461
|
+
testing: "Testing",
|
|
462
|
+
admin: "Admin Dashboard",
|
|
463
|
+
uploads: "File Uploads",
|
|
464
|
+
dockerDeploy: "Docker deploy assets",
|
|
465
|
+
ciCd: "CD workflow"
|
|
466
|
+
};
|
|
467
|
+
var FEATURE_HINTS = {
|
|
468
|
+
testing: "Vitest, integration tests, and test helpers",
|
|
469
|
+
admin: "Admin routes, dashboard views, and management hooks",
|
|
470
|
+
uploads: "Upload APIs, storage service, and UI upload components",
|
|
471
|
+
dockerDeploy: "API/Web Dockerfiles and production Docker Compose",
|
|
472
|
+
ciCd: "GitHub Actions workflow for image build and publish"
|
|
473
|
+
};
|
|
474
|
+
function handleCancelledPrompt(value) {
|
|
475
|
+
if (isCancel(value)) {
|
|
476
|
+
cancel("Setup cancelled.");
|
|
383
477
|
return null;
|
|
384
478
|
}
|
|
385
|
-
|
|
479
|
+
return value;
|
|
480
|
+
}
|
|
481
|
+
function getEnabledFeatureKeys(features) {
|
|
482
|
+
return Object.keys(features).filter((key) => features[key]);
|
|
483
|
+
}
|
|
484
|
+
function deriveProfileFeatureSelection(profileKey) {
|
|
485
|
+
const profile = PROJECT_PROFILES.find((item) => item.key === profileKey) ?? PROJECT_PROFILES[0];
|
|
486
|
+
return getEnabledFeatureKeys(profile.defaultFeatures);
|
|
487
|
+
}
|
|
488
|
+
function resolveFeatureSelection(selected) {
|
|
489
|
+
const unique = Array.from(new Set(selected));
|
|
490
|
+
const hasCiCd = unique.includes("ciCd");
|
|
491
|
+
const hasDockerDeploy = unique.includes("dockerDeploy");
|
|
492
|
+
if (hasCiCd && !hasDockerDeploy) {
|
|
493
|
+
return [...unique.filter((key) => key !== "dockerDeploy"), "dockerDeploy"];
|
|
494
|
+
}
|
|
495
|
+
return unique;
|
|
496
|
+
}
|
|
497
|
+
function buildFeatureOptions(selectedFeatures) {
|
|
498
|
+
const normalized = resolveFeatureSelection(selectedFeatures);
|
|
499
|
+
return {
|
|
500
|
+
testing: normalized.includes("testing"),
|
|
501
|
+
admin: normalized.includes("admin"),
|
|
502
|
+
uploads: normalized.includes("uploads"),
|
|
503
|
+
dockerDeploy: normalized.includes("dockerDeploy"),
|
|
504
|
+
ciCd: normalized.includes("ciCd")
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function printWizardStep(step, total, title) {
|
|
508
|
+
console.log();
|
|
509
|
+
console.log(chalk3.bold(` Step ${step}/${total}`), chalk3.dim(title));
|
|
510
|
+
console.log();
|
|
511
|
+
}
|
|
512
|
+
function getStepTotal(profileKey) {
|
|
513
|
+
return profileKey === "modular" ? 4 : 2;
|
|
514
|
+
}
|
|
515
|
+
function printMultiselectControls() {
|
|
516
|
+
console.log(
|
|
517
|
+
chalk3.dim(" Controls: \u2191/\u2193 navigate \u2022 Space toggle \u2022 Enter confirm")
|
|
518
|
+
);
|
|
519
|
+
console.log();
|
|
520
|
+
}
|
|
521
|
+
function printConfigurationSummary(profileName, features) {
|
|
522
|
+
const lines = [
|
|
523
|
+
`${chalk3.dim("Profile")}: ${profileName}`,
|
|
524
|
+
`${chalk3.dim("Testing")}: ${features.testing ? "yes" : "no"}`,
|
|
525
|
+
`${chalk3.dim("Admin dashboard")}: ${features.admin ? "yes" : "no"}`,
|
|
526
|
+
`${chalk3.dim("File uploads")}: ${features.uploads ? "yes" : "no"}`,
|
|
527
|
+
`${chalk3.dim("Docker deploy assets")}: ${features.dockerDeploy ? "yes" : "no"}`,
|
|
528
|
+
`${chalk3.dim("CD workflow")}: ${features.ciCd ? "yes" : "no"}`
|
|
529
|
+
];
|
|
530
|
+
note(lines.join("\n"), chalk3.cyan("Configuration summary"));
|
|
531
|
+
}
|
|
532
|
+
function finalizeOptions(state, providedName, flags) {
|
|
533
|
+
const projectName = providedName || state.projectNameInput;
|
|
386
534
|
const validation = validateProjectName(projectName);
|
|
387
535
|
if (!validation.valid) {
|
|
388
|
-
console.log(
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
const features = await promptFeatureSelection();
|
|
392
|
-
if (!features) {
|
|
536
|
+
console.log();
|
|
537
|
+
console.log(chalk3.red(" \u2716"), validation.problems?.[0] ?? "Invalid project name");
|
|
393
538
|
return null;
|
|
394
539
|
}
|
|
395
540
|
const useCurrentDir = projectName === ".";
|
|
@@ -397,134 +542,201 @@ async function getProjectOptions(providedName, flags = {}) {
|
|
|
397
542
|
return {
|
|
398
543
|
projectName: actualProjectName,
|
|
399
544
|
projectSlug: toSlug(actualProjectName),
|
|
400
|
-
projectDescription:
|
|
545
|
+
projectDescription: state.projectDescription || DEFAULT_DESCRIPTION,
|
|
401
546
|
skipGit: flags.skipGit || false,
|
|
402
547
|
skipInstall: flags.skipInstall || false,
|
|
403
548
|
useCurrentDir,
|
|
404
|
-
features
|
|
549
|
+
features: buildFeatureOptions(state.selectedFeatures)
|
|
405
550
|
};
|
|
406
551
|
}
|
|
407
|
-
async function
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
552
|
+
async function getProjectOptions(providedName, flags = {}) {
|
|
553
|
+
const initialProjectName = providedName || "my-app";
|
|
554
|
+
const initialProfileKey = "recommended";
|
|
555
|
+
const state = {
|
|
556
|
+
projectNameInput: initialProjectName,
|
|
557
|
+
projectDescription: DEFAULT_DESCRIPTION,
|
|
558
|
+
profileKey: initialProfileKey,
|
|
559
|
+
selectedFeatures: deriveProfileFeatureSelection(initialProfileKey)
|
|
560
|
+
};
|
|
561
|
+
let stage = "details";
|
|
562
|
+
while (true) {
|
|
563
|
+
if (stage === "details") {
|
|
564
|
+
printWizardStep(1, getStepTotal(state.profileKey), "Project details");
|
|
565
|
+
if (!providedName) {
|
|
566
|
+
const projectNameInput = handleCancelledPrompt(
|
|
567
|
+
await text({
|
|
568
|
+
message: chalk3.cyan("Project name"),
|
|
569
|
+
initialValue: state.projectNameInput,
|
|
570
|
+
validate: (value) => {
|
|
571
|
+
if (typeof value !== "string") {
|
|
572
|
+
return "Project name is required";
|
|
573
|
+
}
|
|
574
|
+
const result = validateProjectName(value);
|
|
575
|
+
return result.valid ? void 0 : result.problems?.[0] ?? "Invalid project name";
|
|
576
|
+
}
|
|
577
|
+
})
|
|
578
|
+
);
|
|
579
|
+
if (projectNameInput === null) {
|
|
580
|
+
return null;
|
|
429
581
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
onCancel: () => {
|
|
436
|
-
cancelled = true;
|
|
582
|
+
state.projectNameInput = projectNameInput;
|
|
583
|
+
} else {
|
|
584
|
+
console.log(chalk3.dim(" Project name:"), chalk3.white(providedName));
|
|
437
585
|
}
|
|
586
|
+
const descriptionInput = handleCancelledPrompt(
|
|
587
|
+
await text({
|
|
588
|
+
message: chalk3.cyan("Project description"),
|
|
589
|
+
initialValue: state.projectDescription
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
if (descriptionInput === null) {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
state.projectDescription = descriptionInput;
|
|
596
|
+
stage = "profile";
|
|
597
|
+
continue;
|
|
438
598
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
selected: true
|
|
466
|
-
}));
|
|
467
|
-
const { selectedAppFeatures } = await prompts(
|
|
468
|
-
{
|
|
469
|
-
type: "multiselect",
|
|
470
|
-
name: "selectedAppFeatures",
|
|
471
|
-
message: "Select app features:",
|
|
472
|
-
choices: appFeatureChoices,
|
|
473
|
-
hint: "- Space to toggle, Enter to confirm",
|
|
474
|
-
instructions: false
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
onCancel: () => {
|
|
478
|
-
cancelled = true;
|
|
599
|
+
if (stage === "profile") {
|
|
600
|
+
printWizardStep(2, getStepTotal(state.profileKey), "Choose a setup preset");
|
|
601
|
+
const profileAction = handleCancelledPrompt(
|
|
602
|
+
await select({
|
|
603
|
+
message: chalk3.cyan("Setup preset"),
|
|
604
|
+
options: [
|
|
605
|
+
...PROJECT_PROFILES.map((profile2) => ({
|
|
606
|
+
value: profile2.key,
|
|
607
|
+
label: profile2.name,
|
|
608
|
+
hint: profile2.description
|
|
609
|
+
})),
|
|
610
|
+
{
|
|
611
|
+
value: "__back__",
|
|
612
|
+
label: "Back",
|
|
613
|
+
hint: "Return to project details"
|
|
614
|
+
}
|
|
615
|
+
],
|
|
616
|
+
initialValue: state.profileKey
|
|
617
|
+
})
|
|
618
|
+
);
|
|
619
|
+
if (profileAction === null) {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
if (profileAction === "__back__") {
|
|
623
|
+
stage = "details";
|
|
624
|
+
continue;
|
|
479
625
|
}
|
|
626
|
+
state.profileKey = profileAction;
|
|
627
|
+
state.selectedFeatures = deriveProfileFeatureSelection(state.profileKey);
|
|
628
|
+
if (state.profileKey !== "modular") {
|
|
629
|
+
const result = finalizeOptions(state, providedName, flags);
|
|
630
|
+
if (!result) {
|
|
631
|
+
stage = "details";
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
return result;
|
|
635
|
+
}
|
|
636
|
+
stage = "features";
|
|
637
|
+
continue;
|
|
480
638
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
639
|
+
if (stage === "features") {
|
|
640
|
+
const isModular = state.profileKey === "modular";
|
|
641
|
+
printWizardStep(3, 4, isModular ? "Select features" : "Deployment options");
|
|
642
|
+
if (isModular) {
|
|
643
|
+
printMultiselectControls();
|
|
644
|
+
const selectedFeatures = handleCancelledPrompt(
|
|
645
|
+
await multiselect({
|
|
646
|
+
message: chalk3.cyan("Select features"),
|
|
647
|
+
options: [...APP_FEATURES, ...DEPLOYMENT_FEATURES].map((feature) => ({
|
|
648
|
+
value: feature.key,
|
|
649
|
+
label: FEATURE_LABELS[feature.key],
|
|
650
|
+
hint: FEATURE_HINTS[feature.key]
|
|
651
|
+
})),
|
|
652
|
+
initialValues: state.selectedFeatures,
|
|
653
|
+
required: false
|
|
654
|
+
})
|
|
655
|
+
);
|
|
656
|
+
if (selectedFeatures === null) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
state.selectedFeatures = resolveFeatureSelection(
|
|
660
|
+
selectedFeatures
|
|
661
|
+
);
|
|
662
|
+
if (state.selectedFeatures.includes("ciCd") && !selectedFeatures.includes("dockerDeploy")) {
|
|
663
|
+
console.log();
|
|
664
|
+
console.log(
|
|
665
|
+
chalk3.dim(
|
|
666
|
+
" \u2139 CD workflow requires Docker deployment assets, enabling both."
|
|
667
|
+
)
|
|
668
|
+
);
|
|
669
|
+
}
|
|
503
670
|
}
|
|
671
|
+
const nextAction = handleCancelledPrompt(
|
|
672
|
+
await select({
|
|
673
|
+
message: chalk3.cyan("Continue"),
|
|
674
|
+
options: [
|
|
675
|
+
{
|
|
676
|
+
value: "review",
|
|
677
|
+
label: "Review configuration"
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
value: "profile",
|
|
681
|
+
label: "Back",
|
|
682
|
+
hint: "Return to preset selection"
|
|
683
|
+
}
|
|
684
|
+
],
|
|
685
|
+
initialValue: "review"
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
if (nextAction === null) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
stage = nextAction;
|
|
692
|
+
continue;
|
|
504
693
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
"
|
|
518
|
-
|
|
694
|
+
const profile = PROJECT_PROFILES.find((item) => item.key === state.profileKey) ?? PROJECT_PROFILES[0];
|
|
695
|
+
const options = finalizeOptions(state, providedName, flags);
|
|
696
|
+
if (!options) {
|
|
697
|
+
stage = "details";
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const features = options.features;
|
|
701
|
+
const reviewStep = state.profileKey === "modular" ? 4 : 3;
|
|
702
|
+
printWizardStep(reviewStep, getStepTotal(state.profileKey), "Review and confirm");
|
|
703
|
+
printConfigurationSummary(profile.name, features);
|
|
704
|
+
const reviewOptions = [
|
|
705
|
+
{
|
|
706
|
+
value: "create",
|
|
707
|
+
label: "Create project"
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
value: "profile",
|
|
711
|
+
label: "Edit preset"
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
value: "details",
|
|
715
|
+
label: "Edit project details"
|
|
716
|
+
}
|
|
717
|
+
];
|
|
718
|
+
if (state.profileKey === "modular") {
|
|
719
|
+
reviewOptions.splice(1, 0, {
|
|
720
|
+
value: "features",
|
|
721
|
+
label: "Edit features"
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
const reviewAction = handleCancelledPrompt(
|
|
725
|
+
await select({
|
|
726
|
+
message: chalk3.cyan("Ready to scaffold?"),
|
|
727
|
+
options: reviewOptions,
|
|
728
|
+
initialValue: "create"
|
|
729
|
+
})
|
|
519
730
|
);
|
|
731
|
+
if (reviewAction === null) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
if (reviewAction !== "create") {
|
|
735
|
+
stage = reviewAction;
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
return options;
|
|
520
739
|
}
|
|
521
|
-
return {
|
|
522
|
-
testing: selectedApp.includes("testing"),
|
|
523
|
-
admin: selectedApp.includes("admin"),
|
|
524
|
-
uploads: selectedApp.includes("uploads"),
|
|
525
|
-
dockerDeploy: includesDockerDeploy,
|
|
526
|
-
ciCd: includesCiCd
|
|
527
|
-
};
|
|
528
740
|
}
|
|
529
741
|
async function promptAutomaticSetup() {
|
|
530
742
|
const dockerRunning = isDockerRunning();
|
|
@@ -541,13 +753,16 @@ async function promptAutomaticSetup() {
|
|
|
541
753
|
return false;
|
|
542
754
|
}
|
|
543
755
|
console.log();
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
756
|
+
const runSetup = handleCancelledPrompt(
|
|
757
|
+
await confirm({
|
|
758
|
+
message: "Run local setup now? (Docker PostgreSQL + database migrations)",
|
|
759
|
+
initialValue: true
|
|
760
|
+
})
|
|
761
|
+
);
|
|
762
|
+
if (runSetup === null) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
return runSetup;
|
|
551
766
|
}
|
|
552
767
|
|
|
553
768
|
// src/template.ts
|
|
@@ -958,6 +1173,59 @@ jobs:
|
|
|
958
1173
|
BETTER_AUTH_SECRET: 'test-secret-minimum-32-characters-long-for-ci'
|
|
959
1174
|
BETTER_AUTH_URL: 'http://localhost:8080'
|
|
960
1175
|
NEXT_PUBLIC_API_URL: 'http://localhost:8080/api'
|
|
1176
|
+
|
|
1177
|
+
`;
|
|
1178
|
+
|
|
1179
|
+
// src/docker-ci-workflow-template.ts
|
|
1180
|
+
var DOCKER_CI_WORKFLOW_TEMPLATE = `name: Docker Build
|
|
1181
|
+
|
|
1182
|
+
on:
|
|
1183
|
+
pull_request:
|
|
1184
|
+
branches: [main, development]
|
|
1185
|
+
push:
|
|
1186
|
+
branches: [main]
|
|
1187
|
+
|
|
1188
|
+
# Cancel in-progress runs when a new commit is pushed
|
|
1189
|
+
concurrency:
|
|
1190
|
+
group: \${{ github.workflow }}-\${{ github.ref }}
|
|
1191
|
+
cancel-in-progress: true
|
|
1192
|
+
|
|
1193
|
+
jobs:
|
|
1194
|
+
build-api:
|
|
1195
|
+
runs-on: ubuntu-latest
|
|
1196
|
+
steps:
|
|
1197
|
+
- uses: actions/checkout@v4
|
|
1198
|
+
|
|
1199
|
+
- name: Set up Docker Buildx
|
|
1200
|
+
uses: docker/setup-buildx-action@v3
|
|
1201
|
+
|
|
1202
|
+
- name: Build API image
|
|
1203
|
+
uses: docker/build-push-action@v6
|
|
1204
|
+
with:
|
|
1205
|
+
context: .
|
|
1206
|
+
file: ./apps/api/Dockerfile
|
|
1207
|
+
push: false
|
|
1208
|
+
cache-from: type=gha
|
|
1209
|
+
cache-to: type=gha,mode=max
|
|
1210
|
+
|
|
1211
|
+
build-web:
|
|
1212
|
+
runs-on: ubuntu-latest
|
|
1213
|
+
steps:
|
|
1214
|
+
- uses: actions/checkout@v4
|
|
1215
|
+
|
|
1216
|
+
- name: Set up Docker Buildx
|
|
1217
|
+
uses: docker/setup-buildx-action@v3
|
|
1218
|
+
|
|
1219
|
+
- name: Build Web image
|
|
1220
|
+
uses: docker/build-push-action@v6
|
|
1221
|
+
with:
|
|
1222
|
+
context: .
|
|
1223
|
+
file: ./apps/web/Dockerfile
|
|
1224
|
+
push: false
|
|
1225
|
+
build-args: |
|
|
1226
|
+
NEXT_PUBLIC_API_URL=http://localhost:8080/api
|
|
1227
|
+
cache-from: type=gha
|
|
1228
|
+
cache-to: type=gha,mode=max
|
|
961
1229
|
`;
|
|
962
1230
|
|
|
963
1231
|
// src/transform.ts
|
|
@@ -989,6 +1257,7 @@ var TESTING_FILE_PATTERNS = [
|
|
|
989
1257
|
var TS_CONFIG_FILE_PATTERN = /^tsconfig(?:\.[^.]+)?\.json$/;
|
|
990
1258
|
var AGENT_DOC_TARGETS = ["CLAUDE.md", "AGENTS.md"];
|
|
991
1259
|
var CI_WORKFLOW_RELATIVE_PATH = ".github/workflows/ci.yml";
|
|
1260
|
+
var DOCKER_CI_WORKFLOW_RELATIVE_PATH = ".github/workflows/docker-build.yml";
|
|
992
1261
|
var MARKER_FILES = [
|
|
993
1262
|
"apps/api/src/app.ts",
|
|
994
1263
|
"apps/api/src/plugins/services.ts",
|
|
@@ -1177,6 +1446,7 @@ async function applyFeatureTransforms(targetDir, features) {
|
|
|
1177
1446
|
await transformForNoTesting(targetDir);
|
|
1178
1447
|
}
|
|
1179
1448
|
await transformCiWorkflow(targetDir, disabledFeatures);
|
|
1449
|
+
await transformDockerCiWorkflow(targetDir, features);
|
|
1180
1450
|
await transformAgentDocs(targetDir, disabledFeatures);
|
|
1181
1451
|
}
|
|
1182
1452
|
async function transformForNoTesting(targetDir) {
|
|
@@ -1350,6 +1620,18 @@ async function transformCiWorkflow(targetDir, disabledFeatures) {
|
|
|
1350
1620
|
content = cleanEmptyLines(content).trimEnd() + "\n";
|
|
1351
1621
|
await fs2.writeFile(workflowPath, content, "utf-8");
|
|
1352
1622
|
}
|
|
1623
|
+
async function transformDockerCiWorkflow(targetDir, features) {
|
|
1624
|
+
const workflowPath = path3.join(targetDir, DOCKER_CI_WORKFLOW_RELATIVE_PATH);
|
|
1625
|
+
if (!features.dockerDeploy) {
|
|
1626
|
+
if (await fs2.pathExists(workflowPath)) {
|
|
1627
|
+
await fs2.remove(workflowPath);
|
|
1628
|
+
}
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
await fs2.ensureDir(path3.dirname(workflowPath));
|
|
1632
|
+
const content = DOCKER_CI_WORKFLOW_TEMPLATE.trimEnd() + "\n";
|
|
1633
|
+
await fs2.writeFile(workflowPath, content, "utf-8");
|
|
1634
|
+
}
|
|
1353
1635
|
|
|
1354
1636
|
// src/commands/create.ts
|
|
1355
1637
|
var ENV_FILES = [
|
|
@@ -1376,6 +1658,37 @@ function runInstall(cwd) {
|
|
|
1376
1658
|
child.on("error", () => resolve(false));
|
|
1377
1659
|
});
|
|
1378
1660
|
}
|
|
1661
|
+
function renderProgressBar(current, total) {
|
|
1662
|
+
const width = 28;
|
|
1663
|
+
const clampedTotal = Math.max(total, 1);
|
|
1664
|
+
const ratio = Math.min(Math.max(current / clampedTotal, 0), 1);
|
|
1665
|
+
const filled = Math.round(width * ratio);
|
|
1666
|
+
const empty = width - filled;
|
|
1667
|
+
const filledBar = chalk4.cyan("\u2588".repeat(filled));
|
|
1668
|
+
const emptyBar = chalk4.dim("\u2591".repeat(empty));
|
|
1669
|
+
const percentage = `${Math.round(ratio * 100)}`.padStart(3, " ");
|
|
1670
|
+
return `[${filledBar}${emptyBar}] ${percentage}%`;
|
|
1671
|
+
}
|
|
1672
|
+
function renderStepTrack(step, total) {
|
|
1673
|
+
const segments = [];
|
|
1674
|
+
for (let index = 1; index <= total; index++) {
|
|
1675
|
+
if (index < step) {
|
|
1676
|
+
segments.push(chalk4.green("\u25CF"));
|
|
1677
|
+
} else if (index === step) {
|
|
1678
|
+
segments.push(chalk4.cyan("\u25C6"));
|
|
1679
|
+
} else {
|
|
1680
|
+
segments.push(chalk4.dim("\u25C7"));
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
return segments.join(chalk4.dim("\u2500\u2500"));
|
|
1684
|
+
}
|
|
1685
|
+
function printStepHeader(step, total, title) {
|
|
1686
|
+
const completed = step - 1;
|
|
1687
|
+
console.log();
|
|
1688
|
+
console.log(chalk4.cyan(` Step ${step}/${total}`), chalk4.bold(title));
|
|
1689
|
+
console.log(` ${renderProgressBar(completed, total)}`);
|
|
1690
|
+
console.log(` ${renderStepTrack(step, total)}`);
|
|
1691
|
+
}
|
|
1379
1692
|
function printDryRun(options) {
|
|
1380
1693
|
console.log(chalk4.yellow(" Dry run mode - no changes will be made"));
|
|
1381
1694
|
console.log();
|
|
@@ -1447,13 +1760,11 @@ async function create(projectName, flags) {
|
|
|
1447
1760
|
const files = await fs3.readdir(targetDir);
|
|
1448
1761
|
if (files.length > 0) {
|
|
1449
1762
|
if (options.useCurrentDir) {
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
message: `Current directory is not empty. Continue?`,
|
|
1454
|
-
initial: false
|
|
1763
|
+
const shouldContinue = await confirm2({
|
|
1764
|
+
message: "Current directory is not empty. Continue?",
|
|
1765
|
+
initialValue: false
|
|
1455
1766
|
});
|
|
1456
|
-
if (!
|
|
1767
|
+
if (isCancel2(shouldContinue) || !shouldContinue) {
|
|
1457
1768
|
return;
|
|
1458
1769
|
}
|
|
1459
1770
|
} else {
|
|
@@ -1462,11 +1773,18 @@ async function create(projectName, flags) {
|
|
|
1462
1773
|
}
|
|
1463
1774
|
}
|
|
1464
1775
|
}
|
|
1465
|
-
const
|
|
1776
|
+
const shouldRunSetup = await promptAutomaticSetup();
|
|
1777
|
+
const totalSteps = 2 + (options.skipGit ? 0 : 1) + (options.skipInstall ? 0 : 1) + (shouldRunSetup ? 1 : 0);
|
|
1778
|
+
let currentStep = 0;
|
|
1779
|
+
let spinner;
|
|
1466
1780
|
try {
|
|
1467
|
-
|
|
1781
|
+
currentStep += 1;
|
|
1782
|
+
printStepHeader(currentStep, totalSteps, "Scaffold template");
|
|
1783
|
+
spinner = ora2("Downloading template from GitHub...").start();
|
|
1468
1784
|
await downloadAndPrepareTemplate(targetDir, spinner, options.features);
|
|
1469
|
-
|
|
1785
|
+
currentStep += 1;
|
|
1786
|
+
printStepHeader(currentStep, totalSteps, "Configure project files");
|
|
1787
|
+
spinner.start("Applying template transforms...");
|
|
1470
1788
|
await transformFiles(
|
|
1471
1789
|
targetDir,
|
|
1472
1790
|
{
|
|
@@ -1479,6 +1797,8 @@ async function create(projectName, flags) {
|
|
|
1479
1797
|
await copyEnvFiles(targetDir);
|
|
1480
1798
|
spinner.succeed("Configured project");
|
|
1481
1799
|
if (!options.skipGit && isGitInstalled()) {
|
|
1800
|
+
currentStep += 1;
|
|
1801
|
+
printStepHeader(currentStep, totalSteps, "Initialize git repository");
|
|
1482
1802
|
spinner.start("Initializing git repository...");
|
|
1483
1803
|
const gitSuccess = initGit(targetDir);
|
|
1484
1804
|
if (gitSuccess) {
|
|
@@ -1486,8 +1806,14 @@ async function create(projectName, flags) {
|
|
|
1486
1806
|
} else {
|
|
1487
1807
|
spinner.warn("Failed to initialize git repository");
|
|
1488
1808
|
}
|
|
1809
|
+
} else if (!options.skipGit) {
|
|
1810
|
+
currentStep += 1;
|
|
1811
|
+
printStepHeader(currentStep, totalSteps, "Initialize git repository");
|
|
1812
|
+
spinner.warn("Skipped git initialization (git not installed)");
|
|
1489
1813
|
}
|
|
1490
1814
|
if (!options.skipInstall) {
|
|
1815
|
+
currentStep += 1;
|
|
1816
|
+
printStepHeader(currentStep, totalSteps, "Install dependencies");
|
|
1491
1817
|
spinner.start("Installing dependencies...");
|
|
1492
1818
|
const success = await runInstall(targetDir);
|
|
1493
1819
|
if (success) {
|
|
@@ -1499,9 +1825,9 @@ async function create(projectName, flags) {
|
|
|
1499
1825
|
}
|
|
1500
1826
|
}
|
|
1501
1827
|
let ranAutomaticSetup = false;
|
|
1502
|
-
const shouldRunSetup = await promptAutomaticSetup();
|
|
1503
1828
|
if (shouldRunSetup) {
|
|
1504
|
-
|
|
1829
|
+
currentStep += 1;
|
|
1830
|
+
printStepHeader(currentStep, totalSteps, "Run local database setup");
|
|
1505
1831
|
spinner.start("Starting PostgreSQL database...");
|
|
1506
1832
|
const dockerSuccess = runDockerCompose(targetDir);
|
|
1507
1833
|
if (dockerSuccess) {
|
|
@@ -1528,7 +1854,7 @@ async function create(projectName, flags) {
|
|
|
1528
1854
|
ranAutomaticSetup
|
|
1529
1855
|
);
|
|
1530
1856
|
} catch (error) {
|
|
1531
|
-
spinner
|
|
1857
|
+
spinner?.fail();
|
|
1532
1858
|
printError(
|
|
1533
1859
|
error instanceof Error ? error.message : "Unknown error occurred"
|
|
1534
1860
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-blitzpack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Create a new Blitzpack project - full-stack TypeScript monorepo with Next.js and Fastify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,18 +16,17 @@
|
|
|
16
16
|
"clean": "rm -rf dist"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@clack/prompts": "^1.0.0",
|
|
19
20
|
"chalk": "^5.6.2",
|
|
20
21
|
"commander": "^13.1.0",
|
|
21
22
|
"fs-extra": "^11.3.3",
|
|
22
23
|
"giget": "^2.0.0",
|
|
23
24
|
"ora": "^8.2.0",
|
|
24
|
-
"prompts": "^2.4.2",
|
|
25
25
|
"validate-npm-package-name": "^6.0.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/fs-extra": "^11.0.4",
|
|
29
29
|
"@types/node": "^22.19.3",
|
|
30
|
-
"@types/prompts": "^2.4.9",
|
|
31
30
|
"@types/validate-npm-package-name": "^4.0.2",
|
|
32
31
|
"tsup": "^8.5.1",
|
|
33
32
|
"typescript": "^5.9.3"
|