create-blitzpack 0.1.10 → 0.1.12
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 +262 -16
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -155,6 +155,65 @@ var REPLACEABLE_FILES = [
|
|
|
155
155
|
"README.md"
|
|
156
156
|
];
|
|
157
157
|
var DEFAULT_DESCRIPTION = "A full-stack TypeScript monorepo built with Blitzpack";
|
|
158
|
+
var OPTIONAL_FEATURES = [
|
|
159
|
+
{
|
|
160
|
+
key: "testing",
|
|
161
|
+
name: "Testing",
|
|
162
|
+
description: "vitest, integration tests, test helpers"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
key: "admin",
|
|
166
|
+
name: "Admin Dashboard",
|
|
167
|
+
description: "user management, stats, sessions admin"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
key: "uploads",
|
|
171
|
+
name: "File Uploads",
|
|
172
|
+
description: "S3 storage, upload routes, file components"
|
|
173
|
+
}
|
|
174
|
+
];
|
|
175
|
+
var FEATURE_EXCLUSIONS = {
|
|
176
|
+
testing: [
|
|
177
|
+
"vitest.workspace.ts",
|
|
178
|
+
"vitest.shared.ts",
|
|
179
|
+
"apps/api/test",
|
|
180
|
+
"apps/api/vitest.config.ts",
|
|
181
|
+
"apps/web/src/test",
|
|
182
|
+
"apps/web/vitest.config.ts",
|
|
183
|
+
"packages/types/src/__tests__",
|
|
184
|
+
"packages/types/vitest.config.ts",
|
|
185
|
+
"packages/utils/src/__tests__",
|
|
186
|
+
"packages/utils/vitest.config.ts",
|
|
187
|
+
"packages/ui/src/__tests__",
|
|
188
|
+
"packages/ui/vitest.config.ts",
|
|
189
|
+
"packages/ui/vitest.setup.ts",
|
|
190
|
+
"packages/ui/test-config.js",
|
|
191
|
+
"apps/web/src/hooks/api/__tests__"
|
|
192
|
+
],
|
|
193
|
+
admin: [
|
|
194
|
+
"apps/web/src/app/(admin)",
|
|
195
|
+
"apps/web/src/components/admin",
|
|
196
|
+
"apps/web/src/hooks/api/use-admin-sessions.ts",
|
|
197
|
+
"apps/web/src/hooks/api/use-admin-stats.ts",
|
|
198
|
+
"apps/web/src/hooks/use-realtime-metrics.ts",
|
|
199
|
+
"apps/api/src/routes/admin-sessions.ts",
|
|
200
|
+
"apps/api/src/routes/stats.ts",
|
|
201
|
+
"apps/api/src/routes/metrics.ts",
|
|
202
|
+
"apps/api/src/services/stats.service.ts",
|
|
203
|
+
"apps/api/src/services/metrics.service.ts",
|
|
204
|
+
"packages/types/src/stats.ts"
|
|
205
|
+
],
|
|
206
|
+
uploads: [
|
|
207
|
+
"apps/api/src/routes/uploads.ts",
|
|
208
|
+
"apps/api/src/routes/uploads-serve.ts",
|
|
209
|
+
"apps/api/src/services/uploads.service.ts",
|
|
210
|
+
"apps/api/src/services/file-storage.service.ts",
|
|
211
|
+
"apps/api/public/uploads",
|
|
212
|
+
"apps/web/src/hooks/api/use-uploads.ts",
|
|
213
|
+
"packages/ui/src/file-upload-input.tsx",
|
|
214
|
+
"packages/types/src/upload.ts"
|
|
215
|
+
]
|
|
216
|
+
};
|
|
158
217
|
|
|
159
218
|
// src/utils.ts
|
|
160
219
|
import chalk2 from "chalk";
|
|
@@ -310,6 +369,10 @@ async function getProjectOptions(providedName, flags = {}) {
|
|
|
310
369
|
console.log(`Invalid project name: ${validation.problems?.[0]}`);
|
|
311
370
|
return null;
|
|
312
371
|
}
|
|
372
|
+
const features = await promptFeatureSelection();
|
|
373
|
+
if (!features) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
313
376
|
const useCurrentDir = projectName === ".";
|
|
314
377
|
const actualProjectName = useCurrentDir ? getCurrentDirName() : projectName;
|
|
315
378
|
return {
|
|
@@ -318,7 +381,40 @@ async function getProjectOptions(providedName, flags = {}) {
|
|
|
318
381
|
projectDescription: response.projectDescription || DEFAULT_DESCRIPTION,
|
|
319
382
|
skipGit: flags.skipGit || false,
|
|
320
383
|
skipInstall: flags.skipInstall || false,
|
|
321
|
-
useCurrentDir
|
|
384
|
+
useCurrentDir,
|
|
385
|
+
features
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async function promptFeatureSelection() {
|
|
389
|
+
const featureChoices = OPTIONAL_FEATURES.map((feature) => ({
|
|
390
|
+
title: `${feature.name} ${chalk3.dim(`(${feature.description})`)}`,
|
|
391
|
+
value: feature.key,
|
|
392
|
+
selected: true
|
|
393
|
+
}));
|
|
394
|
+
let cancelled = false;
|
|
395
|
+
const { selectedFeatures } = await prompts(
|
|
396
|
+
{
|
|
397
|
+
type: "multiselect",
|
|
398
|
+
name: "selectedFeatures",
|
|
399
|
+
message: "Include optional features:",
|
|
400
|
+
choices: featureChoices,
|
|
401
|
+
hint: "- Space to toggle, Enter to confirm",
|
|
402
|
+
instructions: false
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
onCancel: () => {
|
|
406
|
+
cancelled = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
if (cancelled) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const selected = selectedFeatures || [];
|
|
414
|
+
return {
|
|
415
|
+
testing: selected.includes("testing"),
|
|
416
|
+
admin: selected.includes("admin"),
|
|
417
|
+
uploads: selected.includes("uploads")
|
|
322
418
|
};
|
|
323
419
|
}
|
|
324
420
|
async function promptAutomaticSetup() {
|
|
@@ -357,15 +453,25 @@ var POST_DOWNLOAD_EXCLUDES = [
|
|
|
357
453
|
"Dockerfile",
|
|
358
454
|
"docker-compose.prod.yml"
|
|
359
455
|
];
|
|
360
|
-
|
|
361
|
-
|
|
456
|
+
function getFeatureExclusions(features) {
|
|
457
|
+
const exclusions = [];
|
|
458
|
+
for (const [key, enabled] of Object.entries(features)) {
|
|
459
|
+
if (!enabled) {
|
|
460
|
+
exclusions.push(...FEATURE_EXCLUSIONS[key]);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return exclusions;
|
|
464
|
+
}
|
|
465
|
+
async function cleanupExcludes(targetDir, additionalExcludes = []) {
|
|
466
|
+
const allExcludes = [...POST_DOWNLOAD_EXCLUDES, ...additionalExcludes];
|
|
467
|
+
for (const exclude of allExcludes) {
|
|
362
468
|
const fullPath = path2.join(targetDir, exclude);
|
|
363
469
|
if (await fs.pathExists(fullPath)) {
|
|
364
470
|
await fs.remove(fullPath);
|
|
365
471
|
}
|
|
366
472
|
}
|
|
367
473
|
}
|
|
368
|
-
async function downloadAndPrepareTemplate(targetDir, spinner) {
|
|
474
|
+
async function downloadAndPrepareTemplate(targetDir, spinner, features) {
|
|
369
475
|
spinner.text = "Fetching template from GitHub...";
|
|
370
476
|
await downloadTemplate(GITHUB_REPO, {
|
|
371
477
|
dir: targetDir,
|
|
@@ -374,7 +480,8 @@ async function downloadAndPrepareTemplate(targetDir, spinner) {
|
|
|
374
480
|
spinner.text = "Extracting files...";
|
|
375
481
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
376
482
|
spinner.text = "Cleaning up template files...";
|
|
377
|
-
|
|
483
|
+
const featureExclusions = getFeatureExclusions(features);
|
|
484
|
+
await cleanupExcludes(targetDir, featureExclusions);
|
|
378
485
|
const files = await countFiles(targetDir);
|
|
379
486
|
spinner.succeed(`Downloaded template (${files} files)`);
|
|
380
487
|
}
|
|
@@ -400,7 +507,62 @@ async function countFiles(dir) {
|
|
|
400
507
|
// src/transform.ts
|
|
401
508
|
import fs2 from "fs-extra";
|
|
402
509
|
import path3 from "path";
|
|
403
|
-
|
|
510
|
+
var TESTING_SCRIPTS = [
|
|
511
|
+
"test",
|
|
512
|
+
"test:unit",
|
|
513
|
+
"test:integration",
|
|
514
|
+
"test:watch",
|
|
515
|
+
"test:coverage",
|
|
516
|
+
"test:parallel"
|
|
517
|
+
];
|
|
518
|
+
var TESTING_ROOT_DEVDEPS = [
|
|
519
|
+
"@testing-library/jest-dom",
|
|
520
|
+
"@testing-library/react",
|
|
521
|
+
"@testing-library/user-event",
|
|
522
|
+
"@vitest/coverage-v8",
|
|
523
|
+
"jsdom",
|
|
524
|
+
"vitest"
|
|
525
|
+
];
|
|
526
|
+
var TESTING_APP_DEVDEPS = ["vitest", "vite-tsconfig-paths"];
|
|
527
|
+
var UPLOADS_API_DEPS = ["@aws-sdk/client-s3", "sharp"];
|
|
528
|
+
var MARKER_FILES = [
|
|
529
|
+
"apps/api/src/app.ts",
|
|
530
|
+
"apps/api/src/plugins/services.ts",
|
|
531
|
+
"apps/api/prisma/schema.prisma"
|
|
532
|
+
];
|
|
533
|
+
function stripFeatureBlocks(content, disabledFeatures) {
|
|
534
|
+
const lines = content.split("\n");
|
|
535
|
+
const result = [];
|
|
536
|
+
let skipUntilEnd = false;
|
|
537
|
+
let currentFeature = null;
|
|
538
|
+
for (const line of lines) {
|
|
539
|
+
const featureStart = line.match(/\/\/\s*@feature\s+(\w+)/);
|
|
540
|
+
const featureEnd = line.match(/\/\/\s*@endfeature/);
|
|
541
|
+
if (featureStart) {
|
|
542
|
+
const feature = featureStart[1];
|
|
543
|
+
if (disabledFeatures.includes(feature)) {
|
|
544
|
+
skipUntilEnd = true;
|
|
545
|
+
currentFeature = feature;
|
|
546
|
+
}
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (featureEnd) {
|
|
550
|
+
if (skipUntilEnd) {
|
|
551
|
+
skipUntilEnd = false;
|
|
552
|
+
currentFeature = null;
|
|
553
|
+
}
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (!skipUntilEnd) {
|
|
557
|
+
result.push(line);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return result.join("\n");
|
|
561
|
+
}
|
|
562
|
+
function cleanEmptyLines(content) {
|
|
563
|
+
return content.replace(/\n{3,}/g, "\n\n");
|
|
564
|
+
}
|
|
565
|
+
function transformPackageJson(content, vars, filePath, features) {
|
|
404
566
|
const pkg = JSON.parse(content);
|
|
405
567
|
if (filePath === "package.json") {
|
|
406
568
|
pkg.name = vars.projectSlug;
|
|
@@ -409,6 +571,31 @@ function transformPackageJson(content, vars, filePath) {
|
|
|
409
571
|
delete pkg.homepage;
|
|
410
572
|
delete pkg.scripts?.["init:project"];
|
|
411
573
|
pkg.version = "0.1.0";
|
|
574
|
+
if (!features.testing) {
|
|
575
|
+
for (const script of TESTING_SCRIPTS) {
|
|
576
|
+
delete pkg.scripts?.[script];
|
|
577
|
+
}
|
|
578
|
+
for (const dep of TESTING_ROOT_DEVDEPS) {
|
|
579
|
+
delete pkg.devDependencies?.[dep];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (filePath === "apps/api/package.json" || filePath === "apps/web/package.json") {
|
|
584
|
+
if (!features.testing) {
|
|
585
|
+
for (const script of TESTING_SCRIPTS) {
|
|
586
|
+
delete pkg.scripts?.[script];
|
|
587
|
+
}
|
|
588
|
+
for (const dep of TESTING_APP_DEVDEPS) {
|
|
589
|
+
delete pkg.devDependencies?.[dep];
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (filePath === "apps/api/package.json") {
|
|
594
|
+
if (!features.uploads) {
|
|
595
|
+
for (const dep of UPLOADS_API_DEPS) {
|
|
596
|
+
delete pkg.dependencies?.[dep];
|
|
597
|
+
}
|
|
598
|
+
}
|
|
412
599
|
}
|
|
413
600
|
return JSON.stringify(pkg, null, 2) + "\n";
|
|
414
601
|
}
|
|
@@ -475,8 +662,13 @@ pnpm db:seed # Seed database
|
|
|
475
662
|
Built with [Blitzpack](https://github.com/CarboxyDev/blitzpack)
|
|
476
663
|
`;
|
|
477
664
|
}
|
|
478
|
-
async function transformFiles(targetDir, vars) {
|
|
479
|
-
|
|
665
|
+
async function transformFiles(targetDir, vars, features) {
|
|
666
|
+
const filesToTransform = [
|
|
667
|
+
...REPLACEABLE_FILES,
|
|
668
|
+
"apps/api/package.json",
|
|
669
|
+
"apps/web/package.json"
|
|
670
|
+
];
|
|
671
|
+
for (const relativePath of filesToTransform) {
|
|
480
672
|
const filePath = path3.join(targetDir, relativePath);
|
|
481
673
|
if (!await fs2.pathExists(filePath)) {
|
|
482
674
|
continue;
|
|
@@ -486,7 +678,7 @@ async function transformFiles(targetDir, vars) {
|
|
|
486
678
|
if (relativePath === "README.md") {
|
|
487
679
|
transformed = generateReadme(vars);
|
|
488
680
|
} else if (relativePath.endsWith("package.json")) {
|
|
489
|
-
transformed = transformPackageJson(content, vars, relativePath);
|
|
681
|
+
transformed = transformPackageJson(content, vars, relativePath, features);
|
|
490
682
|
} else if (relativePath.includes("site.ts")) {
|
|
491
683
|
transformed = transformSiteConfig(content, vars);
|
|
492
684
|
} else if (relativePath.includes("layout.tsx")) {
|
|
@@ -498,6 +690,42 @@ async function transformFiles(targetDir, vars) {
|
|
|
498
690
|
}
|
|
499
691
|
await fs2.writeFile(filePath, transformed, "utf-8");
|
|
500
692
|
}
|
|
693
|
+
await applyFeatureTransforms(targetDir, features);
|
|
694
|
+
}
|
|
695
|
+
async function applyFeatureTransforms(targetDir, features) {
|
|
696
|
+
const disabledFeatures = [];
|
|
697
|
+
if (!features.testing) disabledFeatures.push("testing");
|
|
698
|
+
if (!features.admin) disabledFeatures.push("admin");
|
|
699
|
+
if (!features.uploads) disabledFeatures.push("uploads");
|
|
700
|
+
for (const relativePath of MARKER_FILES) {
|
|
701
|
+
const filePath = path3.join(targetDir, relativePath);
|
|
702
|
+
if (await fs2.pathExists(filePath)) {
|
|
703
|
+
let content = await fs2.readFile(filePath, "utf-8");
|
|
704
|
+
content = stripFeatureBlocks(content, disabledFeatures);
|
|
705
|
+
content = cleanEmptyLines(content);
|
|
706
|
+
await fs2.writeFile(filePath, content);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (!features.testing) {
|
|
710
|
+
await transformForNoTesting(targetDir);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
async function transformForNoTesting(targetDir) {
|
|
714
|
+
const turboPath = path3.join(targetDir, "turbo.json");
|
|
715
|
+
if (await fs2.pathExists(turboPath)) {
|
|
716
|
+
const content = await fs2.readFile(turboPath, "utf-8");
|
|
717
|
+
const turbo = JSON.parse(content);
|
|
718
|
+
delete turbo.tasks?.test;
|
|
719
|
+
delete turbo.tasks?.["test:unit"];
|
|
720
|
+
delete turbo.tasks?.["test:integration"];
|
|
721
|
+
delete turbo.tasks?.["test:watch"];
|
|
722
|
+
delete turbo.tasks?.["test:coverage"];
|
|
723
|
+
await fs2.writeFile(turboPath, JSON.stringify(turbo, null, 2) + "\n");
|
|
724
|
+
}
|
|
725
|
+
const huskyPath = path3.join(targetDir, ".husky/pre-push");
|
|
726
|
+
if (await fs2.pathExists(huskyPath)) {
|
|
727
|
+
await fs2.writeFile(huskyPath, "pnpm typecheck\n");
|
|
728
|
+
}
|
|
501
729
|
}
|
|
502
730
|
|
|
503
731
|
// src/commands/create.ts
|
|
@@ -537,6 +765,19 @@ function printDryRun(options) {
|
|
|
537
765
|
` ${chalk4.cyan("Description:")} ${options.projectDescription}`
|
|
538
766
|
);
|
|
539
767
|
console.log();
|
|
768
|
+
console.log(chalk4.bold(" Features:"));
|
|
769
|
+
console.log();
|
|
770
|
+
const featureStatus = (enabled) => enabled ? chalk4.green("\u2713") : chalk4.red("\u2717");
|
|
771
|
+
console.log(
|
|
772
|
+
` ${featureStatus(options.features.testing)} Testing ${chalk4.dim("(vitest, integration tests)")}`
|
|
773
|
+
);
|
|
774
|
+
console.log(
|
|
775
|
+
` ${featureStatus(options.features.admin)} Admin Dashboard ${chalk4.dim("(user management, stats)")}`
|
|
776
|
+
);
|
|
777
|
+
console.log(
|
|
778
|
+
` ${featureStatus(options.features.uploads)} File Uploads ${chalk4.dim("(S3 storage, upload routes)")}`
|
|
779
|
+
);
|
|
780
|
+
console.log();
|
|
540
781
|
console.log(chalk4.bold(" Would run:"));
|
|
541
782
|
console.log();
|
|
542
783
|
console.log(` ${chalk4.dim("\u2022")} Download template from GitHub`);
|
|
@@ -568,7 +809,8 @@ async function create(projectName, flags) {
|
|
|
568
809
|
projectDescription: options.projectDescription,
|
|
569
810
|
targetDir,
|
|
570
811
|
skipGit: options.skipGit,
|
|
571
|
-
skipInstall: options.skipInstall
|
|
812
|
+
skipInstall: options.skipInstall,
|
|
813
|
+
features: options.features
|
|
572
814
|
});
|
|
573
815
|
return;
|
|
574
816
|
}
|
|
@@ -594,13 +836,17 @@ async function create(projectName, flags) {
|
|
|
594
836
|
const spinner = ora();
|
|
595
837
|
try {
|
|
596
838
|
spinner.start("Downloading template from GitHub...");
|
|
597
|
-
await downloadAndPrepareTemplate(targetDir, spinner);
|
|
839
|
+
await downloadAndPrepareTemplate(targetDir, spinner, options.features);
|
|
598
840
|
spinner.start("Configuring project...");
|
|
599
|
-
await transformFiles(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
841
|
+
await transformFiles(
|
|
842
|
+
targetDir,
|
|
843
|
+
{
|
|
844
|
+
projectName: options.projectName,
|
|
845
|
+
projectSlug: options.projectSlug,
|
|
846
|
+
projectDescription: options.projectDescription
|
|
847
|
+
},
|
|
848
|
+
options.features
|
|
849
|
+
);
|
|
604
850
|
await copyEnvFiles(targetDir);
|
|
605
851
|
spinner.succeed("Configured project");
|
|
606
852
|
if (!options.skipGit && isGitInstalled()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-blitzpack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
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,21 +16,21 @@
|
|
|
16
16
|
"clean": "rm -rf dist"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"chalk": "^5.
|
|
19
|
+
"chalk": "^5.6.2",
|
|
20
20
|
"commander": "^13.1.0",
|
|
21
|
-
"fs-extra": "^11.3.
|
|
21
|
+
"fs-extra": "^11.3.3",
|
|
22
22
|
"giget": "^2.0.0",
|
|
23
23
|
"ora": "^8.2.0",
|
|
24
24
|
"prompts": "^2.4.2",
|
|
25
|
-
"validate-npm-package-name": "^6.0.
|
|
25
|
+
"validate-npm-package-name": "^6.0.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/fs-extra": "^11.0.4",
|
|
29
|
-
"@types/node": "^22.
|
|
29
|
+
"@types/node": "^22.19.3",
|
|
30
30
|
"@types/prompts": "^2.4.9",
|
|
31
31
|
"@types/validate-npm-package-name": "^4.0.2",
|
|
32
|
-
"tsup": "^8.5.
|
|
33
|
-
"typescript": "^5.
|
|
32
|
+
"tsup": "^8.5.1",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"create",
|