create-blitzpack 0.1.15 → 0.1.16

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 +352 -5
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -610,6 +610,193 @@ async function countFiles(dir) {
610
610
  // src/transform.ts
611
611
  import fs2 from "fs-extra";
612
612
  import path3 from "path";
613
+
614
+ // src/agent-doc-template.ts
615
+ var AGENT_DOC_TEMPLATE = `# CLAUDE.md
616
+
617
+ You are a Senior Full-stack Developer and an Expert in TypeScript, Next.js 16, React 19, Fastify, Prisma v7, PostgreSQL, TailwindCSS v5, and shadcn/ui. You are collaborating with a human developer on a production-ready full-stack application.
618
+
619
+ ---
620
+
621
+ ## Critical Requirements & Constraints
622
+
623
+ ### Forbidden Behaviors
624
+
625
+ - NEVER create markdown files unless the user explicitly asks for it. This is very important
626
+ - NEVER create \`index.ts\` barrel files. This is a strict requirement
627
+ - AVOID writing comments in code unless absolutely necessary for non-obvious edge cases
628
+
629
+ ### Workflow Requirements
630
+
631
+ - Make sure you aggressively prefer planning and waiting for user confirmation before writing code for medium to big tasks. This is a strict requirement
632
+ - If you are unsure about anything, ask the user for clarification. This is a strict requirement
633
+ - Prefer using commands or exec scripts (like \`pnpm dlx\`) for setup related tasks instead of making all the files manually. In case the command requires interactive input, ask the user to do it themselves and provide them with suitable guidance
634
+
635
+ ### Collaboration Principles
636
+
637
+ - Always be unbiased towards the code, the user's preference and opinions. If you do not agree with the user, make it clear with them. Treat them like a peer
638
+ - Always mention alternative approaches to the user if you think it's necessary
639
+ - Do not be afraid of hurting the user's feelings or making them feel bad about their skills. Having well-written and maintainable code is significantly more important than cozying up to the user
640
+ - Always give a brief and compact summary of all the changes done
641
+
642
+ ---
643
+
644
+ ## Common Commands
645
+
646
+ ### Development Commands
647
+
648
+ \`\`\`bash
649
+ pnpm dev
650
+ pnpm build
651
+ pnpm typecheck
652
+ pnpm lint
653
+ \`\`\`
654
+
655
+ <!-- @feature testing -->
656
+ \`\`\`bash
657
+ pnpm test
658
+ pnpm test:unit
659
+ pnpm test:integration
660
+ \`\`\`
661
+ <!-- @endfeature -->
662
+
663
+ ### Individual Package Commands
664
+
665
+ \`\`\`bash
666
+ # Web (Next.js on port 3000)
667
+ cd apps/web
668
+ pnpm dev
669
+ pnpm typecheck
670
+
671
+ # API (Fastify on port 8080)
672
+ cd apps/api
673
+ pnpm dev
674
+ pnpm start
675
+ \`\`\`
676
+
677
+ ### Database Commands (run from \`apps/api\`)
678
+
679
+ \`\`\`bash
680
+ pnpm db:generate # Generate Prisma Client
681
+ pnpm db:migrate # Create and apply migrations
682
+ pnpm db:push # Push schema changes (no migration files)
683
+ pnpm db:studio # Open Prisma Studio UI
684
+ pnpm db:seed # Seed database
685
+ \`\`\`
686
+
687
+ ---
688
+
689
+ ## Coding Standards
690
+
691
+ ### Code Style
692
+
693
+ - Type Definitions: Prefer \`interface\` for public-facing types and object shapes, \`type\` for unions, intersections, and computed types
694
+ - Use descriptive variable names with auxiliary verbs (\`isLoading\`, \`hasError\`, \`canSubmit\`)
695
+ - Favor default exports for pages and named exports for utilities or functions
696
+ - **IMPORTANT**: Always use import aliases for files in apps. For packages, use relative imports.
697
+
698
+ ### Error Handling
699
+
700
+ - Use our custom error classes from \`packages/utils/src/errors.ts\`
701
+ - Provide user-friendly error messages and avoid leaking implementation details
702
+
703
+ ---
704
+
705
+ ## Project Architecture
706
+
707
+ ### Packages
708
+
709
+ - All packages (\`@repo/packages-types\`, \`@repo/packages-utils\`) are consumed directly as TypeScript source files
710
+ - No build or watch required for these packages
711
+
712
+ ### Environment
713
+
714
+ - Environment variables are managed per-app with Zod validation
715
+ - Web: Only \`NEXT_PUBLIC_*\` variables are exposed to the browser
716
+ - Prisma CLI commands are wrapped with \`dotenv-cli\` to read from \`.env.local\`
717
+
718
+ ### Database
719
+
720
+ - The API uses Prisma 7 as the ORM with PostgreSQL
721
+ - Prisma queries are automatically logged based on \`LOG_LEVEL\`
722
+
723
+ ### Type Safety & Validation
724
+
725
+ - The codebase emphasizes runtime and compile-time type safety with **Zod v4 as the single validation library** across the entire stack
726
+ - No class-validator
727
+
728
+ <!-- @feature testing -->
729
+ ### Testing
730
+
731
+ Unit Tests: \`*.spec.ts\`
732
+ Integration Tests: \`*.integration.spec.ts\`
733
+ <!-- @endfeature -->
734
+
735
+ ### Authentication
736
+
737
+ - Uses Better Auth
738
+ - When creating users with password auth, set \`accountId\` to the user's email (not user.id)
739
+
740
+ ---
741
+
742
+ ## Web Architecture Patterns
743
+
744
+ ### State Management
745
+
746
+ - TanStack Query (React Query): For API calls and data synchronization
747
+ - Jotai: For client-side global state (UI state, user preferences)
748
+
749
+ ### API Integration
750
+
751
+ - API config centralized in \`apps/web/src/lib/api.ts\`
752
+ - **API Response Unwrapping**: The \`api.ts\` fetcher unwraps \`{ data: T }\` to \`T\` but preserves \`{ data: T[], pagination: {...} }\` responses intact
753
+
754
+ ### Authentication & Protected Routes
755
+
756
+ The web app uses Better Auth for authentication with modern patterns:
757
+
758
+ - **Auth Client**: Configured in \`apps/web/src/lib/auth.ts\`
759
+ - Provides \`useSession()\` hook for accessing current user/session
760
+ - Handles sign in/out, session persistence via cookies
761
+ - **Protected Routes**: \`<ProtectedRoute>\` wrapper component for page-level protection
762
+
763
+ ### UI Component Patterns
764
+
765
+ - Use Shadcn UI components as base, extend them before creating custom ones
766
+ - Implement thoughtful micro-interactions and hover states wherever needed
767
+ - Use Framer Motion for animations, Lucide React for icons
768
+ - Prefer using \`Skeleton\` components for loading states instead of spinners (especially for data fetching components)
769
+ - Always create a proper loading state for data fetching components.
770
+ - Always use \`cn\` helper for dynamic classnames
771
+ - Never write svg code. Always use existing icons from code or Lucide.
772
+
773
+ ---
774
+
775
+ ## API Architecture Patterns
776
+
777
+ ### Fastify Structure
778
+
779
+ - The Fastify API follows a plugin-based architecture
780
+ - Validation: Zod schemas directly in route definitions via \`fastify-type-provider-zod\`
781
+ - Dependency Injection: Manual DI via Fastify decorators
782
+
783
+ ### Logging
784
+
785
+ - The API uses Pino for structured logging with request tracing
786
+ - Verbosity controlled by \`LOG_LEVEL\` env var: \`minimal\` | \`normal\` | \`detailed\` | \`verbose\`
787
+ - **IMPORTANT**: Never use \`console.log\` in the API. Use the logger service instead
788
+
789
+ **Log Methods:**
790
+
791
+ - \`logger.info(message, context?)\` - Standard information
792
+ - \`logger.error(message, error?, context?)\` - Errors with automatic Error serialization
793
+ - \`logger.warn(message, context?)\` - Warnings
794
+ - \`logger.debug(message, context?)\` - Debug information (detailed+ level)
795
+ - \`logger.trace(message, context?)\` - Trace logs (verbose level only)
796
+ - \`logger.perf(message, metrics)\` - Performance metrics
797
+ `;
798
+
799
+ // src/transform.ts
613
800
  var TESTING_SCRIPTS = [
614
801
  "test",
615
802
  "test:unit",
@@ -628,6 +815,15 @@ var TESTING_ROOT_DEVDEPS = [
628
815
  ];
629
816
  var TESTING_APP_DEVDEPS = ["vitest", "vite-tsconfig-paths"];
630
817
  var UPLOADS_API_DEPS = ["@aws-sdk/client-s3", "sharp"];
818
+ var TESTING_DIR_NAMES = /* @__PURE__ */ new Set(["__tests__", "test", "tests"]);
819
+ var TESTING_FILE_PATTERNS = [
820
+ /\.test\.[^/]+$/i,
821
+ /\.spec\.[^/]+$/i,
822
+ /^vitest(?:\.[^.]+)*\.(?:[cm]?[jt]sx?)$/i,
823
+ /^test-config\.(?:[cm]?[jt]sx?)$/i
824
+ ];
825
+ var TS_CONFIG_FILE_PATTERN = /^tsconfig(?:\.[^.]+)?\.json$/;
826
+ var AGENT_DOC_TARGETS = ["CLAUDE.md", "AGENTS.md"];
631
827
  var MARKER_FILES = [
632
828
  "apps/api/src/app.ts",
633
829
  "apps/api/src/plugins/services.ts",
@@ -637,22 +833,23 @@ function stripFeatureBlocks(content, disabledFeatures) {
637
833
  const lines = content.split("\n");
638
834
  const result = [];
639
835
  let skipUntilEnd = false;
640
- let currentFeature = null;
641
836
  for (const line of lines) {
642
- const featureStart = line.match(/\/\/\s*@feature\s+(\w+)/);
643
- const featureEnd = line.match(/\/\/\s*@endfeature/);
837
+ const featureStart = line.match(
838
+ /^\s*(?:\/\/|<!--)\s*@feature\s+(\w+)\s*(?:-->)?\s*$/
839
+ );
840
+ const featureEnd = line.match(
841
+ /^\s*(?:\/\/|<!--)\s*@endfeature\s*(?:-->)?\s*$/
842
+ );
644
843
  if (featureStart) {
645
844
  const feature = featureStart[1];
646
845
  if (disabledFeatures.includes(feature)) {
647
846
  skipUntilEnd = true;
648
- currentFeature = feature;
649
847
  }
650
848
  continue;
651
849
  }
652
850
  if (featureEnd) {
653
851
  if (skipUntilEnd) {
654
852
  skipUntilEnd = false;
655
- currentFeature = null;
656
853
  }
657
854
  continue;
658
855
  }
@@ -800,6 +997,8 @@ async function applyFeatureTransforms(targetDir, features) {
800
997
  if (!features.testing) disabledFeatures.push("testing");
801
998
  if (!features.admin) disabledFeatures.push("admin");
802
999
  if (!features.uploads) disabledFeatures.push("uploads");
1000
+ if (!features.dockerDeploy) disabledFeatures.push("dockerDeploy");
1001
+ if (!features.ciCd) disabledFeatures.push("ciCd");
803
1002
  for (const relativePath of MARKER_FILES) {
804
1003
  const filePath = path3.join(targetDir, relativePath);
805
1004
  if (await fs2.pathExists(filePath)) {
@@ -812,8 +1011,12 @@ async function applyFeatureTransforms(targetDir, features) {
812
1011
  if (!features.testing) {
813
1012
  await transformForNoTesting(targetDir);
814
1013
  }
1014
+ await transformAgentDocs(targetDir, disabledFeatures);
815
1015
  }
816
1016
  async function transformForNoTesting(targetDir) {
1017
+ await removeTestingArtifacts(targetDir);
1018
+ await stripTestingFromWorkspacePackageJson(targetDir);
1019
+ await stripTestingFromTsConfigs(targetDir);
817
1020
  const turboPath = path3.join(targetDir, "turbo.json");
818
1021
  if (await fs2.pathExists(turboPath)) {
819
1022
  const content = await fs2.readFile(turboPath, "utf-8");
@@ -823,6 +1026,7 @@ async function transformForNoTesting(targetDir) {
823
1026
  delete turbo.tasks?.["test:integration"];
824
1027
  delete turbo.tasks?.["test:watch"];
825
1028
  delete turbo.tasks?.["test:coverage"];
1029
+ delete turbo.tasks?.["test:parallel"];
826
1030
  await fs2.writeFile(turboPath, JSON.stringify(turbo, null, 2) + "\n");
827
1031
  }
828
1032
  const huskyPath = path3.join(targetDir, ".husky/pre-push");
@@ -830,6 +1034,149 @@ async function transformForNoTesting(targetDir) {
830
1034
  await fs2.writeFile(huskyPath, "pnpm typecheck\n");
831
1035
  }
832
1036
  }
1037
+ function isTestingDependency(name) {
1038
+ return name === "vitest" || name.startsWith("@vitest/") || name.startsWith("@testing-library/") || name === "jsdom" || name === "vite-tsconfig-paths";
1039
+ }
1040
+ function isTestingIncludeEntry(value) {
1041
+ return value.includes("__tests__") || value.includes("/test") || value.includes("test/") || value.includes("tests/") || value.includes(".test.") || value.includes(".spec.");
1042
+ }
1043
+ async function removeTestingArtifacts(currentDir) {
1044
+ const entries = await fs2.readdir(currentDir);
1045
+ for (const entry of entries) {
1046
+ if (entry === ".git" || entry === "node_modules") {
1047
+ continue;
1048
+ }
1049
+ const fullPath = path3.join(currentDir, entry);
1050
+ const stat = await fs2.stat(fullPath);
1051
+ if (stat.isDirectory()) {
1052
+ if (TESTING_DIR_NAMES.has(entry)) {
1053
+ await fs2.remove(fullPath);
1054
+ continue;
1055
+ }
1056
+ await removeTestingArtifacts(fullPath);
1057
+ continue;
1058
+ }
1059
+ if (TESTING_FILE_PATTERNS.some((pattern) => pattern.test(entry))) {
1060
+ await fs2.remove(fullPath);
1061
+ }
1062
+ }
1063
+ }
1064
+ async function stripTestingFromWorkspacePackageJson(currentDir) {
1065
+ const entries = await fs2.readdir(currentDir);
1066
+ for (const entry of entries) {
1067
+ if (entry === ".git" || entry === "node_modules") {
1068
+ continue;
1069
+ }
1070
+ const fullPath = path3.join(currentDir, entry);
1071
+ const stat = await fs2.stat(fullPath);
1072
+ if (stat.isDirectory()) {
1073
+ await stripTestingFromWorkspacePackageJson(fullPath);
1074
+ continue;
1075
+ }
1076
+ if (entry !== "package.json") {
1077
+ continue;
1078
+ }
1079
+ const content = await fs2.readFile(fullPath, "utf-8");
1080
+ const pkg = JSON.parse(content);
1081
+ let changed = false;
1082
+ if (pkg.scripts && typeof pkg.scripts === "object") {
1083
+ for (const scriptName of Object.keys(pkg.scripts)) {
1084
+ if (scriptName === "test" || scriptName.startsWith("test:")) {
1085
+ delete pkg.scripts[scriptName];
1086
+ changed = true;
1087
+ }
1088
+ }
1089
+ }
1090
+ const dependencySections = [
1091
+ "dependencies",
1092
+ "devDependencies",
1093
+ "peerDependencies",
1094
+ "optionalDependencies"
1095
+ ];
1096
+ for (const section of dependencySections) {
1097
+ if (!pkg[section] || typeof pkg[section] !== "object") {
1098
+ continue;
1099
+ }
1100
+ for (const depName of Object.keys(pkg[section])) {
1101
+ if (isTestingDependency(depName)) {
1102
+ delete pkg[section][depName];
1103
+ changed = true;
1104
+ }
1105
+ }
1106
+ }
1107
+ if ("vitest" in pkg) {
1108
+ delete pkg.vitest;
1109
+ changed = true;
1110
+ }
1111
+ if (changed) {
1112
+ await fs2.writeFile(fullPath, JSON.stringify(pkg, null, 2) + "\n");
1113
+ }
1114
+ }
1115
+ }
1116
+ async function stripTestingFromTsConfigs(currentDir) {
1117
+ const entries = await fs2.readdir(currentDir);
1118
+ for (const entry of entries) {
1119
+ if (entry === ".git" || entry === "node_modules") {
1120
+ continue;
1121
+ }
1122
+ const fullPath = path3.join(currentDir, entry);
1123
+ const stat = await fs2.stat(fullPath);
1124
+ if (stat.isDirectory()) {
1125
+ await stripTestingFromTsConfigs(fullPath);
1126
+ continue;
1127
+ }
1128
+ if (!TS_CONFIG_FILE_PATTERN.test(entry)) {
1129
+ continue;
1130
+ }
1131
+ const content = await fs2.readFile(fullPath, "utf-8");
1132
+ const tsconfig = JSON.parse(content);
1133
+ let changed = false;
1134
+ const compilerOptions = tsconfig.compilerOptions;
1135
+ if (compilerOptions && typeof compilerOptions === "object") {
1136
+ if (Array.isArray(compilerOptions.types)) {
1137
+ const nextTypes = compilerOptions.types.filter(
1138
+ (value) => typeof value === "string" && !value.includes("vitest") && !value.includes("@testing-library")
1139
+ );
1140
+ if (nextTypes.length !== compilerOptions.types.length) {
1141
+ compilerOptions.types = nextTypes;
1142
+ changed = true;
1143
+ }
1144
+ }
1145
+ if (compilerOptions.paths && typeof compilerOptions.paths === "object") {
1146
+ if ("@test/*" in compilerOptions.paths) {
1147
+ delete compilerOptions.paths["@test/*"];
1148
+ changed = true;
1149
+ }
1150
+ if ("@tests/*" in compilerOptions.paths) {
1151
+ delete compilerOptions.paths["@tests/*"];
1152
+ changed = true;
1153
+ }
1154
+ }
1155
+ }
1156
+ if (Array.isArray(tsconfig.include)) {
1157
+ const nextInclude = tsconfig.include.filter(
1158
+ (value) => typeof value === "string" && !isTestingIncludeEntry(value)
1159
+ );
1160
+ if (nextInclude.length !== tsconfig.include.length) {
1161
+ tsconfig.include = nextInclude;
1162
+ changed = true;
1163
+ }
1164
+ }
1165
+ if (changed) {
1166
+ await fs2.writeFile(fullPath, JSON.stringify(tsconfig, null, 2) + "\n");
1167
+ }
1168
+ }
1169
+ }
1170
+ async function transformAgentDocs(targetDir, disabledFeatures) {
1171
+ let content = stripFeatureBlocks(AGENT_DOC_TEMPLATE, disabledFeatures);
1172
+ content = cleanEmptyLines(content).trimEnd() + "\n";
1173
+ for (const fileName of AGENT_DOC_TARGETS) {
1174
+ const filePath = path3.join(targetDir, fileName);
1175
+ const heading = fileName === "AGENTS.md" ? "# AGENTS.md" : "# CLAUDE.md";
1176
+ const fileContent = content.replace(/^#\s+CLAUDE\.md/m, heading);
1177
+ await fs2.writeFile(filePath, fileContent, "utf-8");
1178
+ }
1179
+ }
833
1180
 
834
1181
  // src/commands/create.ts
835
1182
  var ENV_FILES = [
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "create-blitzpack",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Create a new Blitzpack project - full-stack TypeScript monorepo with Next.js and Fastify",
5
5
  "type": "module",
6
6
  "bin": {
7
- "create-blitzpack": "./dist/index.js"
7
+ "create-blitzpack": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist"
@@ -47,7 +47,7 @@
47
47
  "license": "MIT",
48
48
  "repository": {
49
49
  "type": "git",
50
- "url": "https://github.com/CarboxyDev/blitzpack"
50
+ "url": "git+https://github.com/CarboxyDev/blitzpack.git"
51
51
  },
52
52
  "homepage": "https://github.com/CarboxyDev/blitzpack",
53
53
  "engines": {