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.
Files changed (2) hide show
  1. package/dist/index.js +262 -16
  2. 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
- async function cleanupExcludes(targetDir) {
361
- for (const exclude of POST_DOWNLOAD_EXCLUDES) {
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
- await cleanupExcludes(targetDir);
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
- function transformPackageJson(content, vars, filePath) {
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
- for (const relativePath of REPLACEABLE_FILES) {
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(targetDir, {
600
- projectName: options.projectName,
601
- projectSlug: options.projectSlug,
602
- projectDescription: options.projectDescription
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.10",
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.4.1",
19
+ "chalk": "^5.6.2",
20
20
  "commander": "^13.1.0",
21
- "fs-extra": "^11.3.0",
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.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.10.5",
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.0",
33
- "typescript": "^5.7.2"
32
+ "tsup": "^8.5.1",
33
+ "typescript": "^5.9.3"
34
34
  },
35
35
  "keywords": [
36
36
  "create",