create-win-project 1.3.0 → 2.0.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.
Files changed (289) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +58 -160
  3. package/checks/check-compatibility.js +95 -0
  4. package/{scripts/verify-generated.mjs → checks/check-generated-project.js} +28 -4
  5. package/{scripts/validate-content.mjs → checks/check-library.js} +17 -7
  6. package/checks/check-package.js +15 -0
  7. package/checks/classify-changes.js +29 -0
  8. package/checks/run-compatibility-shard.js +21 -0
  9. package/docs/README.md +20 -0
  10. package/docs/capabilities.md +13 -0
  11. package/docs/compatibility.md +13 -0
  12. package/docs/generated-project.md +12 -0
  13. package/docs/getting-started.md +26 -0
  14. package/docs/maintainers/architecture.md +161 -0
  15. package/docs/maintainers/ci-strategy.md +46 -0
  16. package/docs/{CONTENT_MODEL.md → maintainers/content-model.md} +3 -3
  17. package/docs/maintainers/contributing.md +154 -0
  18. package/docs/{DEPENDENCY_MAINTENANCE.md → maintainers/dependencies.md} +1 -1
  19. package/docs/migration-v2.md +13 -0
  20. package/docs/production-contract.md +24 -0
  21. package/index.js +2 -429
  22. package/{playbooks → library}/INDEX.md +5 -5
  23. package/library/compatibility-impact.json +14 -0
  24. package/{playbooks/capabilities/ci/github-actions.manifest.json → library/development-tools/ci/definition.json} +1 -0
  25. package/library/development-tools/devops/makefile/commands.md +11 -0
  26. package/library/development-tools/devops/makefile/definition.json +11 -0
  27. package/library/development-tools/devops/makefile/docker.md +9 -0
  28. package/library/development-tools/devops/makefile/validation.md +5 -0
  29. package/{playbooks/devops/pr-template.manifest.json → library/development-tools/devops/pr-template/definition.json} +1 -0
  30. package/library/features/sqlalchemy-alembic.md +17 -0
  31. package/library/optional-features/concerns/zod/errors.md +5 -0
  32. package/library/optional-features/concerns/zod/testing.md +5 -0
  33. package/library/optional-features/concerns/zod/validation.md +21 -0
  34. package/library/optional-features/styling/css-modules/definition.json +12 -0
  35. package/library/optional-features/styling/css-modules/responsive.md +5 -0
  36. package/library/optional-features/styling/css-modules/theme.md +7 -0
  37. package/{playbooks/platform/mobile.manifest.json → library/platforms/mobile/definition.json} +1 -0
  38. package/{playbooks/platform/web.manifest.json → library/platforms/web/definition.json} +1 -0
  39. package/{playbooks/stack/react-native.manifest.json → library/stacks/expo/definition.json} +2 -2
  40. package/library/stacks/fastapi/architecture.md +40 -0
  41. package/library/stacks/fastapi/definition.json +39 -0
  42. package/library/stacks/fastapi/runtime.md +25 -0
  43. package/library/stacks/fastapi/security.md +26 -0
  44. package/library/stacks/fastapi/structure.md +27 -0
  45. package/library/stacks/fastapi/testing.md +23 -0
  46. package/{playbooks/stack/nextjs.manifest.json → library/stacks/nextjs/definition.json} +2 -2
  47. package/{playbooks/stack/no-frontend.manifest.json → library/stacks/no-frontend/definition.json} +1 -1
  48. package/{playbooks/stack/react-vite.manifest.json → library/stacks/react-vite/definition.json} +2 -2
  49. package/{compatibility/profiles.json → library/tested-versions.json} +46 -2
  50. package/{playbooks/universal/accessibility.manifest.json → library/universal/accessibility/definition.json} +1 -1
  51. package/library/universal/coding-rules/definition.json +13 -0
  52. package/library/universal/coding-rules/hygiene.md +9 -0
  53. package/library/universal/coding-rules/naming.md +17 -0
  54. package/{playbooks/universal/error-handling.manifest.json → library/universal/error-handling/definition.json} +1 -0
  55. package/library/universal/git-conventions/branches.md +5 -0
  56. package/library/universal/git-conventions/commits.md +7 -0
  57. package/library/universal/git-conventions/definition.json +13 -0
  58. package/library/universal/git-conventions/workflow.md +5 -0
  59. package/{playbooks/universal/observability.manifest.json → library/universal/observability/definition.json} +1 -0
  60. package/{playbooks/universal/security.manifest.json → library/universal/security/definition.json} +1 -0
  61. package/library/universal/typescript/boundaries.md +13 -0
  62. package/library/universal/typescript/definition.json +12 -0
  63. package/library/universal/typescript/errors.md +5 -0
  64. package/library/universal/typescript/patterns.md +7 -0
  65. package/package.json +14 -15
  66. package/public/logo.svg +19 -0
  67. package/src/cli/arguments.js +63 -0
  68. package/src/cli/display.js +220 -0
  69. package/src/cli/main.js +203 -0
  70. package/src/cli/navigation.js +156 -0
  71. package/src/cli/questions.js +188 -0
  72. package/src/cli/system-check.js +158 -0
  73. package/src/engine/create-project.js +10 -0
  74. package/src/engine/install-dependencies.js +13 -0
  75. package/{lib/catalog.js → src/engine/load-library.js} +8 -8
  76. package/{lib/files.js → src/engine/project-files.js} +10 -1
  77. package/{lib/playbooks.js → src/engine/project-guidance.js} +26 -3
  78. package/{lib/application-shapes.js → src/engine/project-shapes.js} +4 -4
  79. package/{lib/template.js → src/engine/render-templates.js} +11 -0
  80. package/{lib/compatibility.js → src/engine/tested-versions.js} +21 -2
  81. package/src/engine/upgrade-report.js +20 -0
  82. package/src/engine/write-files.js +34 -0
  83. package/{lib/stacks/registry.js → src/stacks/available-stacks.js} +25 -1
  84. package/src/stacks/backends/README.md +5 -0
  85. package/src/stacks/backends/fastapi/ci.js +3 -0
  86. package/src/stacks/backends/fastapi/create-files.js +874 -0
  87. package/src/stacks/backends/fastapi/docker.js +75 -0
  88. package/src/stacks/backends/fastapi/environment.js +3 -0
  89. package/src/stacks/backends/fastapi/index.js +32 -0
  90. package/{lib/stacks → src/stacks/backends}/laravel/architecture.js +1 -0
  91. package/{lib/stacks → src/stacks/backends}/laravel/auth/oidc.js +1 -0
  92. package/{lib/stacks → src/stacks/backends}/laravel/auth/public.js +1 -0
  93. package/{lib/stacks → src/stacks/backends}/laravel/auth/sanctum.js +1 -0
  94. package/{lib/stacks → src/stacks/backends}/laravel/auth/session.js +1 -0
  95. package/src/stacks/backends/laravel/ci.js +3 -0
  96. package/{lib/stacks → src/stacks/backends}/laravel/composer.js +1 -1
  97. package/{lib/stacks/laravel/generate.js → src/stacks/backends/laravel/create-files.js} +1 -0
  98. package/src/stacks/backends/laravel/docker.js +14 -0
  99. package/src/stacks/backends/laravel/environment.js +5 -0
  100. package/{lib/stacks → src/stacks/backends}/laravel/index.js +11 -3
  101. package/{lib/stacks → src/stacks/backends}/laravel/ui/blade.js +1 -0
  102. package/{lib/stacks → src/stacks/backends}/laravel/ui/index.js +1 -0
  103. package/{lib/stacks → src/stacks/backends}/laravel/ui/inertia-react.js +4 -2
  104. package/{lib/stacks → src/stacks/backends}/laravel/ui/livewire.js +1 -0
  105. package/{lib/stacks → src/stacks/backends}/laravel/ui/shared.js +1 -0
  106. package/src/stacks/backends/none/index.js +23 -0
  107. package/src/stacks/backends/postgres/ci.js +3 -0
  108. package/src/stacks/backends/postgres/create-files.js +21 -0
  109. package/src/stacks/backends/postgres/docker.js +3 -0
  110. package/src/stacks/backends/postgres/environment.js +3 -0
  111. package/src/stacks/backends/postgres/index.js +30 -0
  112. package/src/stacks/backends/springboot/ci.js +3 -0
  113. package/src/stacks/backends/springboot/create-files.js +91 -0
  114. package/src/stacks/backends/springboot/docker.js +3 -0
  115. package/src/stacks/backends/springboot/environment.js +3 -0
  116. package/src/stacks/backends/springboot/index.js +31 -0
  117. package/src/stacks/backends/supabase/ci.js +3 -0
  118. package/src/stacks/backends/supabase/create-files.js +52 -0
  119. package/src/stacks/backends/supabase/docker.js +3 -0
  120. package/src/stacks/backends/supabase/environment.js +3 -0
  121. package/src/stacks/backends/supabase/index.js +31 -0
  122. package/src/stacks/backends/supabase/native.js +14 -0
  123. package/src/stacks/compose-files.js +259 -0
  124. package/{lib/generator.js → src/stacks/create-project.js} +206 -57
  125. package/src/stacks/frontends/README.md +4 -0
  126. package/src/stacks/frontends/nextjs/ci.js +3 -0
  127. package/src/stacks/frontends/nextjs/create-files.js +46 -0
  128. package/src/stacks/frontends/nextjs/dependencies.js +5 -0
  129. package/src/stacks/frontends/nextjs/docker.js +3 -0
  130. package/src/stacks/frontends/nextjs/environment.js +7 -0
  131. package/src/stacks/frontends/nextjs/index.js +35 -0
  132. package/src/stacks/frontends/react-native/ci.js +3 -0
  133. package/src/stacks/frontends/react-native/create-files.js +28 -0
  134. package/src/stacks/frontends/react-native/dependencies.js +5 -0
  135. package/src/stacks/frontends/react-native/docker.js +5 -0
  136. package/src/stacks/frontends/react-native/environment.js +7 -0
  137. package/src/stacks/frontends/react-native/index.js +30 -0
  138. package/src/stacks/frontends/react-vite/ci.js +3 -0
  139. package/src/stacks/frontends/react-vite/create-files.js +27 -0
  140. package/src/stacks/frontends/react-vite/dependencies.js +5 -0
  141. package/src/stacks/frontends/react-vite/docker.js +3 -0
  142. package/src/stacks/frontends/react-vite/environment.js +8 -0
  143. package/src/stacks/frontends/react-vite/index.js +32 -0
  144. package/src/stacks/shared/capability-packs.js +31 -0
  145. package/{lib → src}/stacks/shared/contributions.js +1 -1
  146. package/src/stacks/shared/environment.js +38 -0
  147. package/src/stacks/shared/javascript-package.js +117 -0
  148. package/src/stacks/shared/testing-files.js +16 -0
  149. package/templates/ci/fastapi.yml +62 -0
  150. package/templates/docker/compose/postgres.yml +1 -1
  151. package/templates/docker/compose/springboot.yml +3 -3
  152. package/templates/docker/compose/supabase.yml +1 -1
  153. package/templates/docker/compose-prod/fastapi.yml +52 -0
  154. package/templates/docker/compose-prod/springboot.yml +20 -2
  155. package/templates/docker/dockerfile/fastapi.dev.dockerfile +9 -0
  156. package/templates/docker/dockerfile/fastapi.prod.dockerfile +11 -0
  157. package/templates/docker/dockerfile/nextjs.prod.dockerfile +1 -0
  158. package/templates/docker/dockerfile/springboot.prod.dockerfile +4 -1
  159. package/templates/docker/dockerfile/vite.prod.dockerfile +2 -1
  160. package/templates/makefile/fastapi.mk +97 -0
  161. package/docs/ARCHITECTURE.md +0 -118
  162. package/lib/banner.js +0 -45
  163. package/lib/constants.js +0 -3
  164. package/lib/doctor.js +0 -45
  165. package/lib/interview.js +0 -77
  166. package/lib/laravel-scaffold.js +0 -3
  167. package/lib/scaffold.js +0 -467
  168. package/lib/stacks/index.js +0 -8
  169. package/playbooks/concerns/zod.md +0 -174
  170. package/playbooks/devops/makefile.manifest.json +0 -10
  171. package/playbooks/devops/makefile.md +0 -556
  172. package/playbooks/styling/css-modules-extensions.md +0 -267
  173. package/playbooks/styling/css-modules.manifest.json +0 -11
  174. package/playbooks/universal/coding-rules.manifest.json +0 -12
  175. package/playbooks/universal/coding-rules.md +0 -281
  176. package/playbooks/universal/git-conventions.manifest.json +0 -10
  177. package/playbooks/universal/git-conventions.md +0 -186
  178. package/playbooks/universal/typescript.manifest.json +0 -11
  179. package/playbooks/universal/typescript.md +0 -272
  180. package/scripts/compatibility-matrix.mjs +0 -48
  181. /package/{playbooks/capabilities → library/development-tools}/ci/github-actions.md +0 -0
  182. /package/{playbooks/devops → library/development-tools/devops/pr-template}/pr-template.md +0 -0
  183. /package/{playbooks/capabilities/docker/docker.manifest.json → library/development-tools/docker/definition.json} +0 -0
  184. /package/{playbooks/capabilities → library/development-tools}/docker/overview.md +0 -0
  185. /package/{playbooks/capabilities → library/features}/auth/oidc-resource-server.md +0 -0
  186. /package/{playbooks/capabilities → library/features}/auth/spring-session.md +0 -0
  187. /package/{playbooks/capabilities/flyway/flyway.manifest.json → library/features/flyway/definition.json} +0 -0
  188. /package/{playbooks/capabilities → library/features}/flyway/environments.md +0 -0
  189. /package/{playbooks/capabilities → library/features}/flyway/migrations.md +0 -0
  190. /package/{playbooks/capabilities → library/features}/flyway/testing.md +0 -0
  191. /package/{playbooks/capabilities → library/features}/laravel/database.md +0 -0
  192. /package/{playbooks/capabilities → library/features}/laravel/migrations.md +0 -0
  193. /package/{playbooks/capabilities → library/features}/laravel/observability.md +0 -0
  194. /package/{playbooks/capabilities → library/features}/laravel/oidc-resource-server.md +0 -0
  195. /package/{playbooks/capabilities → library/features}/laravel/queues.md +0 -0
  196. /package/{playbooks/capabilities → library/features}/laravel/sanctum-spa.md +0 -0
  197. /package/{playbooks/capabilities → library/features}/laravel/scheduler.md +0 -0
  198. /package/{playbooks/capabilities → library/features}/laravel/session-auth.md +0 -0
  199. /package/{playbooks/capabilities → library/features}/laravel/storage-uploads.md +0 -0
  200. /package/{playbooks/capabilities → library/features}/postgresql/architecture.md +0 -0
  201. /package/{playbooks/capabilities/postgresql/postgresql.manifest.json → library/features/postgresql/definition.json} +0 -0
  202. /package/{playbooks/capabilities → library/features}/postgresql/migrations.md +0 -0
  203. /package/{playbooks/capabilities → library/features}/postgresql/schema-design.md +0 -0
  204. /package/{playbooks/capabilities → library/features}/postgresql/security.md +0 -0
  205. /package/{playbooks/capabilities → library/features}/postgresql/testing.md +0 -0
  206. /package/{playbooks/capabilities → library/features}/prisma/architecture.md +0 -0
  207. /package/{playbooks/capabilities/prisma/prisma.manifest.json → library/features/prisma/definition.json} +0 -0
  208. /package/{playbooks/capabilities → library/features}/prisma/migrations.md +0 -0
  209. /package/{playbooks/capabilities → library/features}/prisma/runtime.md +0 -0
  210. /package/{playbooks/capabilities → library/features}/prisma/schema.md +0 -0
  211. /package/{playbooks/capabilities → library/features}/prisma/testing.md +0 -0
  212. /package/{playbooks/capabilities → library/features}/supabase/architecture.md +0 -0
  213. /package/{playbooks/capabilities → library/features}/supabase/authentication.md +0 -0
  214. /package/{playbooks/capabilities/supabase/supabase.manifest.json → library/features/supabase/definition.json} +0 -0
  215. /package/{playbooks/capabilities → library/features}/supabase/expo.md +0 -0
  216. /package/{playbooks/capabilities → library/features}/supabase/migrations.md +0 -0
  217. /package/{playbooks/capabilities → library/features}/supabase/nextjs.md +0 -0
  218. /package/{playbooks/capabilities → library/features}/supabase/rls.md +0 -0
  219. /package/{playbooks/capabilities → library/features}/supabase/testing.md +0 -0
  220. /package/{playbooks/capabilities → library/features}/supabase/vite.md +0 -0
  221. /package/{playbooks → library/optional-features}/concerns/axios.md +0 -0
  222. /package/{playbooks → library/optional-features}/concerns/next-safe-action.md +0 -0
  223. /package/{playbooks → library/optional-features}/concerns/next-themes.md +0 -0
  224. /package/{playbooks → library/optional-features}/concerns/nuqs.md +0 -0
  225. /package/{playbooks → library/optional-features}/concerns/t3-env.md +0 -0
  226. /package/{playbooks → library/optional-features}/concerns/tanstack-query.md +0 -0
  227. /package/{playbooks → library/optional-features}/concerns/zustand.md +0 -0
  228. /package/{playbooks/styling/native-styles.manifest.json → library/optional-features/styling/native-styles/definition.json} +0 -0
  229. /package/{playbooks/styling → library/optional-features/styling/native-styles}/native-styles.md +0 -0
  230. /package/{playbooks/styling/tailwind.manifest.json → library/optional-features/styling/tailwind/definition.json} +0 -0
  231. /package/{playbooks/styling → library/optional-features/styling/tailwind}/tailwind-extensions.md +0 -0
  232. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/architecture.md +0 -0
  233. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/runtime.md +0 -0
  234. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/security.md +0 -0
  235. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/structure.md +0 -0
  236. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/testing.md +0 -0
  237. /package/{playbooks/stack/laravel-ui.manifest.json → library/platforms/laravel-ui/definition.json} +0 -0
  238. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/architecture.md +0 -0
  239. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/runtime.md +0 -0
  240. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/security.md +0 -0
  241. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/structure.md +0 -0
  242. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/testing.md +0 -0
  243. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/architecture.md +0 -0
  244. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/runtime.md +0 -0
  245. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/security.md +0 -0
  246. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/structure.md +0 -0
  247. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/testing.md +0 -0
  248. /package/{playbooks/platform → library/platforms/mobile}/mobile.md +0 -0
  249. /package/{playbooks/platform → library/platforms/web}/web.md +0 -0
  250. /package/{playbooks/stack → library/stacks}/expo/architecture.md +0 -0
  251. /package/{playbooks/stack → library/stacks}/expo/runtime.md +0 -0
  252. /package/{playbooks/stack → library/stacks}/expo/security.md +0 -0
  253. /package/{playbooks/stack → library/stacks}/expo/structure.md +0 -0
  254. /package/{playbooks/stack → library/stacks}/expo/testing.md +0 -0
  255. /package/{playbooks/stack → library/stacks}/laravel/architecture.md +0 -0
  256. /package/{playbooks/stack/laravel.manifest.json → library/stacks/laravel/definition.json} +0 -0
  257. /package/{playbooks/stack → library/stacks}/laravel/runtime.md +0 -0
  258. /package/{playbooks/stack → library/stacks}/laravel/security.md +0 -0
  259. /package/{playbooks/stack → library/stacks}/laravel/structure.md +0 -0
  260. /package/{playbooks/stack → library/stacks}/laravel/testing.md +0 -0
  261. /package/{playbooks/stack → library/stacks}/nextjs/architecture.md +0 -0
  262. /package/{playbooks/stack → library/stacks}/nextjs/runtime.md +0 -0
  263. /package/{playbooks/stack → library/stacks}/nextjs/security.md +0 -0
  264. /package/{playbooks/stack → library/stacks}/nextjs/structure.md +0 -0
  265. /package/{playbooks/stack → library/stacks}/nextjs/testing.md +0 -0
  266. /package/{playbooks/stack/none.manifest.json → library/stacks/none/definition.json} +0 -0
  267. /package/{playbooks/stack → library/stacks}/react-vite/architecture.md +0 -0
  268. /package/{playbooks/stack → library/stacks}/react-vite/runtime.md +0 -0
  269. /package/{playbooks/stack → library/stacks}/react-vite/security.md +0 -0
  270. /package/{playbooks/stack → library/stacks}/react-vite/structure.md +0 -0
  271. /package/{playbooks/stack → library/stacks}/react-vite/testing.md +0 -0
  272. /package/{playbooks/stack → library/stacks}/springboot/architecture.md +0 -0
  273. /package/{playbooks/stack/springboot.manifest.json → library/stacks/springboot/definition.json} +0 -0
  274. /package/{playbooks/stack → library/stacks}/springboot/runtime.md +0 -0
  275. /package/{playbooks/stack → library/stacks}/springboot/security.md +0 -0
  276. /package/{playbooks/stack → library/stacks}/springboot/structure.md +0 -0
  277. /package/{playbooks/stack → library/stacks}/springboot/testing.md +0 -0
  278. /package/{playbooks/universal → library/universal/accessibility}/accessibility.md +0 -0
  279. /package/{playbooks/universal → library/universal/error-handling}/error-handling.md +0 -0
  280. /package/{playbooks/universal → library/universal/observability}/observability.md +0 -0
  281. /package/{playbooks/universal → library/universal/security}/security.md +0 -0
  282. /package/{lib → src/engine}/project-location.js +0 -0
  283. /package/{lib → src}/stacks/context.js +0 -0
  284. /package/{lib/stacks/contract.js → src/stacks/rules.js} +0 -0
  285. /package/{ci → templates/ci}/expo.yml +0 -0
  286. /package/{ci → templates/ci}/laravel.yml +0 -0
  287. /package/{ci → templates/ci}/nextjs.yml +0 -0
  288. /package/{ci → templates/ci}/springboot.yml +0 -0
  289. /package/{ci → templates/ci}/vite.yml +0 -0
@@ -0,0 +1,31 @@
1
+ import { defineStackAdapter } from '../../rules.js'
2
+ import { buildSupabaseProjectFiles } from './create-files.js'
3
+ import { ciContributions } from './ci.js'
4
+ import { dockerContributions } from './docker.js'
5
+ import { environmentContributions } from './environment.js'
6
+
7
+ export const supabaseAdapter = defineStackAdapter({
8
+ id: 'supabase',
9
+ kind: 'backend',
10
+ label: 'Supabase',
11
+ compatibleWith: { frontend: ['nextjs', 'react', 'react-native'] },
12
+ capabilities: {
13
+ applicationShapes: ['fullstack', 'separate', 'mobile'],
14
+ architectureProfiles: ['small', 'medium', 'large'],
15
+ authenticationModels: ['public', 'undecided', 'supabase'],
16
+ runtime: 'docker',
17
+ },
18
+ contributes: {
19
+ files: ({ stack }) => Object.entries(buildSupabaseProjectFiles(stack)),
20
+ environment: environmentContributions,
21
+ install: () => [{ cwd: '.', command: 'npm', args: ['run', 'supabase:start'] }],
22
+ docker: dockerContributions,
23
+ ci: ciContributions,
24
+ verification: () => [
25
+ { frontend: 'nextjs', architecture: 'small', authentication: 'public' },
26
+ { frontend: 'nextjs', architecture: 'large', authentication: 'supabase' },
27
+ { frontend: 'react', architecture: 'medium', authentication: 'supabase' },
28
+ { frontend: 'react-native', architecture: 'medium', authentication: 'supabase' },
29
+ ],
30
+ },
31
+ })
@@ -0,0 +1,14 @@
1
+ export function augmentSupabaseNativeFiles(files, answers, stack) {
2
+ if (stack.backendKey === 'supabase') {
3
+ files['lib/supabase.ts'] = stack.authentication === 'supabase'
4
+ ? `import { createClient } from '@supabase/supabase-js'\nimport * as SecureStore from 'expo-secure-store'\n\nconst storage = { getItem: SecureStore.getItemAsync, setItem: SecureStore.setItemAsync, removeItem: SecureStore.deleteItemAsync }\nexport const supabase = createClient(process.env.EXPO_PUBLIC_SUPABASE_URL!, process.env.EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, { auth: { storage, persistSession: true, autoRefreshToken: true, detectSessionInUrl: false } })\n`
5
+ : `import { createClient } from '@supabase/supabase-js'\n\nexport const supabase = createClient(process.env.EXPO_PUBLIC_SUPABASE_URL!, process.env.EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, { auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false } })\n`
6
+ if (stack.authentication === 'supabase') {
7
+ files['lib/supabase-lifecycle.ts'] = `import { AppState, type AppStateStatus } from 'react-native'\nimport { supabase } from './supabase'\n\nexport function bindSupabaseAuthLifecycle() {\n const update = (state: AppStateStatus) => {\n if (state === 'active') supabase.auth.startAutoRefresh()\n else supabase.auth.stopAutoRefresh()\n }\n update(AppState.currentState)\n const subscription = AppState.addEventListener('change', update)\n return () => { subscription.remove(); supabase.auth.stopAutoRefresh() }\n}\n`
8
+ files['app/login.tsx'] = `import { useState } from 'react'\nimport { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'\nimport { supabase } from '@/lib/supabase'\n\nexport default function LoginScreen() {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState('')\n const [pending, setPending] = useState(false)\n async function signIn() {\n setPending(true); setError('')\n const result = await supabase.auth.signInWithPassword({ email: email.trim(), password })\n if (result.error) setError('Unable to sign in with those credentials.')\n setPending(false)\n }\n return <View style={styles.container}><Text accessibilityRole="header">Sign in</Text>{error ? <Text accessibilityRole="alert">{error}</Text> : null}<TextInput accessibilityLabel="Email" autoCapitalize="none" autoComplete="email" keyboardType="email-address" onChangeText={setEmail} value={email} /><TextInput accessibilityLabel="Password" autoComplete="current-password" onChangeText={setPassword} secureTextEntry value={password} /><Pressable accessibilityRole="button" disabled={pending} onPress={signIn}><Text>{pending ? 'Signing in…' : 'Sign in'}</Text></Pressable></View>\n}\nconst styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', padding: 24, gap: 12 } })\n`
9
+ if ((answers.testing || 'basic') !== 'none') files['lib/supabase-lifecycle.test.ts'] = `import { AppState } from 'react-native'\nimport { bindSupabaseAuthLifecycle } from './supabase-lifecycle'\nimport { supabase } from './supabase'\n\njest.mock('./supabase', () => ({ supabase: { auth: { startAutoRefresh: jest.fn(), stopAutoRefresh: jest.fn() } } }))\n\ntest('starts refresh while active and stops it on cleanup', () => {\n const remove = jest.fn()\n jest.spyOn(AppState, 'addEventListener').mockReturnValue({ remove } as never)\n Object.defineProperty(AppState, 'currentState', { configurable: true, value: 'active' })\n const cleanup = bindSupabaseAuthLifecycle()\n expect(supabase.auth.startAutoRefresh).toHaveBeenCalled()\n cleanup()\n expect(remove).toHaveBeenCalled()\n expect(supabase.auth.stopAutoRefresh).toHaveBeenCalled()\n})\n`
10
+ }
11
+ }
12
+ return files
13
+ }
14
+
@@ -0,0 +1,259 @@
1
+ import path from 'node:path'
2
+ import { packageVersion } from './shared/javascript-package.js'
3
+ import { stackRegistry } from './available-stacks.js'
4
+ import { capabilityPackFiles } from './shared/capability-packs.js'
5
+ import { collectContributions } from './shared/contributions.js'
6
+ import { buildSharedTestFiles, buildSupabaseWebFiles } from './backends/supabase/create-files.js'
7
+ import { augmentSupabaseNativeFiles } from './backends/supabase/native.js'
8
+ import { addPostgresScripts, buildPostgresFiles } from './backends/postgres/create-files.js'
9
+ import { buildEnvironmentFiles as nextjsEnvironment } from './frontends/nextjs/environment.js'
10
+ import { buildEnvironmentFiles as reactEnvironment } from './frontends/react-vite/environment.js'
11
+ import { buildEnvironmentFiles as nativeEnvironment } from './frontends/react-native/environment.js'
12
+ import { renderEnvironment } from './shared/environment.js'
13
+
14
+ function json(value) {
15
+ return `${JSON.stringify(value, null, 2)}\n`
16
+ }
17
+
18
+ function html(value) {
19
+ return String(value).replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
20
+ }
21
+
22
+ function envFiles(answers, stack) {
23
+ if (stack.frontendKey === 'no-frontend') {
24
+ return { '.env.example': renderEnvironment(stack.env, answers) }
25
+ }
26
+ if (stack.frontendKey === 'react') return reactEnvironment(answers, stack)
27
+ if (stack.isMobile) return nativeEnvironment(answers, stack)
28
+ if (stack.frontendKey === 'nextjs') return nextjsEnvironment(answers, stack)
29
+ return { '.env.example': renderEnvironment(stack.env, answers) }
30
+ }
31
+
32
+ function projectReadme(answers, stack) {
33
+ if (stack.frontendKey === 'no-frontend') {
34
+ if (stack.backendKey === 'fastapi') {
35
+ return `# ${answers.projectName}\n\n> ${answers.projectDescription}\n\nGenerated backend-only ${stack.backendLabel} application.\n\n## Start\n\n\`\`\`bash\ncp .env.example .env\nuv sync\nalembic upgrade head\nuv run uvicorn app.main:app --reload\n\`\`\`\n\n## Validate\n\n\`\`\`bash\nuv run ruff check . && uv run ruff format --check . && uv run mypy . && uv run pytest\n\`\`\`\n`
36
+ }
37
+ return `# ${answers.projectName}\n\n> ${answers.projectDescription}\n\nGenerated backend-only ${stack.backendLabel} application.\n\n## Start\n\n\`\`\`bash\ncp .env.example .env\ncd backend\n./mvnw spring-boot:run # use mvnw.cmd on Windows\n\`\`\`\n\n## Validate\n\n\`\`\`bash\ncd backend\n./mvnw --batch-mode test\n./mvnw --batch-mode package -DskipTests\n\`\`\`\n`
38
+ }
39
+ const root = stack.frontendKey === 'react' ? 'frontend/' : ''
40
+ const quality = stack.isMobile
41
+ ? `npm run typecheck\n${answers.testing === 'none' ? '' : 'npm test -- --runInBand\n'}npm run build -- --platform web`
42
+ : `npm run lint\nnpm run typecheck\nnpm run test --if-present\nnpm run build`
43
+ const backend = stack.backendKey === 'springboot'
44
+ ? `\nThe backend lives in \`backend/\`:\n\n\`\`\`bash\ncd backend\nmvn spring-boot:run\n# verify: curl http://localhost:8080/api/health\n\`\`\`\n`
45
+ : ''
46
+ return `# ${answers.projectName}\n\n> ${answers.projectDescription}\n\nGenerated with create-win-project. This repository includes a runnable application, tests, CI guidance, and task-routed agent playbooks.\n\n## Start\n\n\`\`\`bash\n${root ? `cd ${root}\n` : ''}cp .env.example .env.local 2>/dev/null || cp .env.example .env\nnpm install\nnpm run dev\n\`\`\`\n${backend}\n## Validate\n\n\`\`\`bash\n${root ? `cd ${root}\n` : ''}${quality}\n\`\`\`\n\nCommit the generated lockfile before enabling CI; CI intentionally uses \`npm ci\`.\n\n## Agent-assisted work\n\n1. Put product goals and boundaries in \`CONTEXT.md\`.\n2. Read \`AGENTS.md\` for commands and authority boundaries.\n3. Use \`RULES.md\` to open only the relevant playbook section.\n4. Treat tests and application behavior as the source of truth when prose drifts.\n\n## Important files\n\n- \`AGENTS.md\`: small always-on operating contract.\n- \`RULES.md\`: concern-to-playbook router.\n- \`CONTEXT.md\`: project-specific intent and decisions.\n- \`docs/\`: architecture, API, setup, and deployment documentation.\n`
47
+ }
48
+
49
+ function testingPackage(stack, level) {
50
+ const scripts = { ...stack.scripts, start: stack.frontendKey === 'nextjs' ? 'next start' : undefined }
51
+ const devDeps = { ...stack.devDeps }
52
+
53
+ if (level === 'none') delete scripts.test
54
+ if (level !== 'none') {
55
+ if (stack.isMobile) {
56
+ Object.assign(devDeps, {
57
+ jest: packageVersion(stack.profile, 'jest', 'react-native'),
58
+ '@types/jest': packageVersion(stack.profile, '@types/jest', 'react-native'),
59
+ 'jest-expo': packageVersion(stack.profile, 'jest-expo', 'react-native'),
60
+ '@testing-library/react-native': packageVersion(stack.profile, '@testing-library/react-native', 'react-native'),
61
+ })
62
+ } else {
63
+ Object.assign(devDeps, {
64
+ vitest: packageVersion(stack.profile, 'vitest', stack.frontendKey),
65
+ jsdom: packageVersion(stack.profile, 'jsdom', stack.frontendKey),
66
+ '@testing-library/react': packageVersion(stack.profile, '@testing-library/react', stack.frontendKey),
67
+ '@testing-library/jest-dom': packageVersion(stack.profile, '@testing-library/jest-dom', stack.frontendKey),
68
+ })
69
+ }
70
+ }
71
+ if (level === 'full' && !stack.isMobile) {
72
+ scripts['test:e2e'] = 'playwright test'
73
+ devDeps['@playwright/test'] = packageVersion(stack.profile, '@playwright/test', stack.frontendKey)
74
+ }
75
+
76
+ scripts.typecheck = 'tsc --noEmit'
77
+ scripts.format = 'prettier --write .'
78
+ scripts['format:check'] = 'prettier --check .'
79
+ if (!stack.isMobile) scripts.lint = 'eslint .'
80
+ if (stack.architecture === 'large') scripts['check:boundaries'] = 'node scripts/check-boundaries.mjs'
81
+ if (stack.backendKey === 'supabase') {
82
+ const workdir = stack.frontendKey === 'react' ? ' --workdir ..' : ''
83
+ scripts['supabase:start'] = `supabase${workdir} start`
84
+ scripts['supabase:stop'] = `supabase${workdir} stop`
85
+ scripts['supabase:status'] = `supabase${workdir} status`
86
+ scripts['supabase:reset'] = `supabase${workdir} db reset`
87
+ scripts['supabase:test'] = `supabase${workdir} test db`
88
+ const typePath = stack.frontendKey === 'react' ? 'src/types/database.types.ts' : stack.isMobile ? 'types/database.types.ts' : 'src/types/database.types.ts'
89
+ scripts['supabase:types'] = `supabase${workdir} gen types typescript --local > ${typePath}`
90
+ }
91
+ const boundaryCheck = stack.architecture === 'large' ? ' && npm run check:boundaries' : ''
92
+ scripts.check = stack.isMobile
93
+ ? `npm run format:check && npm run typecheck${level === 'none' ? '' : ' && npm test -- --runInBand'}${boundaryCheck}`
94
+ : `npm run format:check && npm run lint && npm run typecheck${level === 'none' ? '' : ' && npm test'}${boundaryCheck}`
95
+ Object.keys(scripts).forEach((key) => scripts[key] === undefined && delete scripts[key])
96
+ return { scripts, devDeps }
97
+ }
98
+
99
+ function packageFile(answers, stack) {
100
+ const { scripts, devDeps } = testingPackage(stack, answers.testing || 'basic')
101
+ if (stack.backendKey === 'postgres') {
102
+ addPostgresScripts(scripts)
103
+ }
104
+ if (stack.styleId === 'tailwind') {
105
+ devDeps.tailwindcss = packageVersion(stack.profile, 'tailwindcss', stack.frontendKey)
106
+ if (stack.frontendKey === 'nextjs') devDeps['@tailwindcss/postcss'] = packageVersion(stack.profile, '@tailwindcss/postcss', 'nextjs')
107
+ if (stack.frontendKey === 'react') devDeps['@tailwindcss/vite'] = packageVersion(stack.profile, '@tailwindcss/vite', 'react')
108
+ }
109
+ const dependencies = { ...stack.deps }
110
+ if (stack.frontendKey !== 'nextjs') delete dependencies['@supabase/ssr']
111
+ if (stack.isMobile && stack.authentication === 'supabase') {
112
+ dependencies['expo-secure-store'] = packageVersion(stack.profile, 'expo-secure-store', 'react-native')
113
+ }
114
+ const packageJson = {
115
+ name: answers.projectName,
116
+ version: '0.1.0',
117
+ private: true,
118
+ type: 'module',
119
+ packageManager: `npm@${stack.profile.runtimes.npmMinimum}`,
120
+ engines: {
121
+ node: `>=${stack.profile.runtimes.nodeMinimum}`,
122
+ npm: `>=${stack.profile.runtimes.npmMinimum}`,
123
+ },
124
+ main: stack.isMobile ? 'expo-router/entry' : undefined,
125
+ scripts,
126
+ dependencies,
127
+ devDependencies: devDeps,
128
+ }
129
+ if (!packageJson.main) delete packageJson.main
130
+ return json(packageJson)
131
+ }
132
+
133
+ function boundaryScript(sourceRoot) {
134
+ return `/* eslint-disable no-undef -- node script runs outside linted frontend bundle */
135
+ import fs from 'node:fs'\nimport path from 'node:path'\n\nconst root = path.resolve(${JSON.stringify(sourceRoot)}, 'features')\nconst violations = []\nif (fs.existsSync(root)) {\n for (const feature of fs.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)) {\n const featureRoot = path.join(root, feature)\n const visit = (directory) => {\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\n const file = path.join(directory, entry.name)\n if (entry.isDirectory()) visit(file)\n else if (/\\.[cm]?[jt]sx?$/.test(entry.name)) {\n const content = fs.readFileSync(file, 'utf8')\n for (const match of content.matchAll(/from\\s+['\"]@\\/features\\/([^/'\"]+)(\\/[^'\"]+)?['\"]/g)) {\n if (match[1] !== feature && match[2]) violations.push(\`${'${file}'} deep-imports feature ${'${match[1]}'}; import its public API instead\`)\n }\n }\n }\n }\n visit(featureRoot)\n }\n}\nif (violations.length) { console.error(violations.join('\\n')); process.exit(1) }\nconsole.log('Feature boundaries are valid.')\n`
136
+ }
137
+
138
+ function statusFeatureFiles(root, stack) {
139
+ if (stack.architecture === 'small') return {}
140
+ const prefix = root ? `${root}/` : ''
141
+ const files = {
142
+ [`${prefix}src/features/status/types.ts`]: "export interface StarterStatus { heading: string; profile: 'medium' | 'large' }\n",
143
+ [`${prefix}src/features/status/services/getStarterStatus.ts`]: `import type { StarterStatus } from '../types'\n\nexport function getStarterStatus(): StarterStatus {\n return { heading: 'Your starter is running', profile: '${stack.architecture}' }\n}\n`,
144
+ [`${prefix}src/features/status/components/StarterStatus.tsx`]: "import type { StarterStatus as Status } from '../types'\n\nexport function StarterStatus({ status }: { status: Status }) {\n return <><h1>{status.heading}</h1><p>Architecture: {status.profile}</p></>\n}\n",
145
+ }
146
+ if (stack.backendKey === 'springboot') {
147
+ const baseUrl = stack.frontendKey === 'nextjs' ? 'process.env.NEXT_PUBLIC_API_URL' : 'import.meta.env.VITE_API_URL'
148
+ files[`${prefix}src/features/status/api/getBackendStatus.ts`] = `export interface BackendStatus { status: string }\n\nexport async function getBackendStatus(signal?: AbortSignal): Promise<BackendStatus> {\n const response = await fetch(\`${'${' + baseUrl + '}'}/api/health\`, { signal })\n if (!response.ok) throw new Error(\`Backend health request failed (${'${response.status}'})\`)\n return response.json() as Promise<BackendStatus>\n}\n`
149
+ }
150
+ if (stack.architecture === 'large') {
151
+ files[`${prefix}src/features/status/index.ts`] = "export { StarterStatus } from './components/StarterStatus'\nexport { getStarterStatus } from './services/getStarterStatus'\nexport type { StarterStatus as StarterStatusModel } from './types'\n"
152
+ files[`${prefix}scripts/check-boundaries.mjs`] = boundaryScript(prefix ? 'src' : 'src')
153
+ }
154
+ return files
155
+ }
156
+
157
+ function nativeStatusFeatureFiles(stack) {
158
+ if (stack.architecture === 'small') return {}
159
+ const files = {
160
+ 'features/status/types.ts': "export interface StarterStatus { heading: string; profile: 'medium' | 'large' }\n",
161
+ 'features/status/services/getStarterStatus.ts': `import type { StarterStatus } from '../types'\n\nexport function getStarterStatus(): StarterStatus {\n return { heading: 'Your starter is running', profile: '${stack.architecture}' }\n}\n`,
162
+ 'features/status/components/StarterStatus.tsx': "import { Text } from 'react-native'\nimport type { StarterStatus as Status } from '../types'\n\nexport function StarterStatus({ status }: { status: Status }) {\n return <><Text accessibilityRole=\"header\">{status.heading}</Text><Text>Architecture: {status.profile}</Text></>\n}\n",
163
+ }
164
+ if (stack.backendKey === 'springboot') {
165
+ files['features/status/api/getBackendStatus.ts'] = "export interface BackendStatus { status: string }\n\nexport async function getBackendStatus(signal?: AbortSignal): Promise<BackendStatus> {\n const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/api/health`, { signal })\n if (!response.ok) throw new Error(`Backend health request failed (${response.status})`)\n return response.json() as Promise<BackendStatus>\n}\n"
166
+ }
167
+ if (stack.architecture === 'large') {
168
+ files['features/status/index.ts'] = "export { StarterStatus } from './components/StarterStatus'\nexport { getStarterStatus } from './services/getStarterStatus'\nexport type { StarterStatus as StarterStatusModel } from './types'\n"
169
+ files['scripts/check-boundaries.mjs'] = boundaryScript('.')
170
+ }
171
+ return files
172
+ }
173
+
174
+ function supabaseWebFiles(isNext, withAuth = false) {
175
+ return buildSupabaseWebFiles(isNext, withAuth)
176
+ }
177
+
178
+ const testFiles = buildSharedTestFiles
179
+
180
+ function prismaFiles() {
181
+ return buildPostgresFiles()
182
+ }
183
+
184
+ function augmentViteFiles(files, stack) {
185
+ const root = 'frontend'
186
+ if (stack.backendKey === 'supabase') {
187
+ for (const [name, value] of Object.entries(supabaseWebFiles(false, stack.authentication === 'supabase'))) files[`${root}/${name}`] = value
188
+ if (stack.authentication === 'supabase') {
189
+ files[`${root}/src/features/auth/services/auth.ts`] = `import { supabase } from '@/lib/supabase'\n\nexport async function signIn(email: string, password: string) {\n const { error } = await supabase.auth.signInWithPassword({ email, password })\n if (error) throw new Error('Unable to sign in with those credentials.')\n}\n\nexport async function signOut() {\n const { error } = await supabase.auth.signOut()\n if (error) throw new Error('Unable to sign out.')\n}\n`
190
+ files[`${root}/src/features/auth/components/AuthPanel.tsx`] = `import { useState, type FormEvent } from 'react'\nimport { signIn } from '../services/auth'\n\nexport function AuthPanel() {\n const [error, setError] = useState('')\n const [pending, setPending] = useState(false)\n async function submit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault()\n setPending(true)\n setError('')\n const data = new FormData(event.currentTarget)\n try { await signIn(String(data.get('email') || ''), String(data.get('password') || '')) }\n catch (reason) { setError(reason instanceof Error ? reason.message : 'Unable to sign in.') }\n finally { setPending(false) }\n }\n return <section aria-labelledby="login-heading"><h2 id="login-heading">Sign in</h2>{error ? <p role="alert">{error}</p> : null}<form onSubmit={submit}><label>Email <input name="email" type="email" autoComplete="email" required /></label><label>Password <input name="password" type="password" autoComplete="current-password" required /></label><button disabled={pending} type="submit">{pending ? 'Signing in…' : 'Sign in'}</button></form></section>\n}\n`
191
+ files[`${root}/src/App.tsx`] = `import { AuthPanel } from '@/features/auth/components/AuthPanel'\n${files[`${root}/src/App.tsx`].replace('<p>Read <code>AGENTS.md</code> before your first agent-assisted change.</p>', '<p>Read <code>AGENTS.md</code> before your first agent-assisted change.</p><AuthPanel />')}`
192
+ }
193
+ }
194
+ return files
195
+ }
196
+
197
+ function nativeFiles(answers, stack) {
198
+ const files = frontendFiles(answers, stack)
199
+ augmentSupabaseNativeFiles(files, answers, stack)
200
+ return files
201
+ }
202
+
203
+ function frontendFiles(answers, stack) {
204
+ if (stack.frontendKey === 'no-frontend' || stack.frontendKey === 'laravel-ui') return {}
205
+ const adapter = stackRegistry.require(stack.frontendKey)
206
+ return Object.fromEntries(collectContributions([adapter], 'files', {
207
+ answers,
208
+ stack,
209
+ shared: { json, packageFile, testFiles, statusFeatureFiles, nativeStatusFeatureFiles, supabaseWebFiles, prismaFiles },
210
+ }))
211
+ }
212
+
213
+ function springFiles(answers, vars, stack) {
214
+ const adapter = stackRegistry.require(stack.backendKey)
215
+ return Object.fromEntries(collectContributions([adapter], 'files', { answers, stack, vars }))
216
+ }
217
+
218
+ export function buildRunnableFiles(answers, stack, vars) {
219
+ let files = stack.isMobile ? nativeFiles(answers, stack) : frontendFiles(answers, stack)
220
+ if (stack.frontendKey === 'react') files = augmentViteFiles(files, stack)
221
+ Object.assign(files, envFiles(answers, stack))
222
+ Object.assign(files, capabilityPackFiles(answers, stack))
223
+ files['create-win-project.profile.json'] = json({
224
+ schemaVersion: 2,
225
+ applicationShape: stack.applicationShape,
226
+ compatibilityProfile: {
227
+ id: stack.profile.id,
228
+ status: stack.profile.status,
229
+ supportedUntil: stack.profile.supportedUntil,
230
+ },
231
+ architectureProfile: stack.architecture,
232
+ authentication: {
233
+ intent: stack.authenticationIntent,
234
+ model: stack.authentication,
235
+ audience: stack.authAudience,
236
+ },
237
+ stack: stack.key,
238
+ productionBaseline: {
239
+ tests: true,
240
+ continuousIntegration: true,
241
+ productionBuild: !stack.isMobile,
242
+ securityRules: true,
243
+ operationsDocumentation: true,
244
+ deployment: stack.isMobile ? 'eas' : 'cloud-neutral-docker',
245
+ },
246
+ capabilities: {
247
+ uploads: answers.uploads || 'none',
248
+ backgroundJobs: answers.backgroundJobs || 'none',
249
+ offline: answers.offline || 'none',
250
+ },
251
+ runtimes: stack.profile.runtimes,
252
+ })
253
+ files['README.md'] = projectReadme(answers, stack)
254
+ if (stack.backendKey === 'springboot') {
255
+ files['README.md'] = files['README.md'].replace('mvn spring-boot:run', './mvnw spring-boot:run # use mvnw.cmd on Windows')
256
+ }
257
+ if (stackRegistry.get(stack.backendKey)) Object.assign(files, springFiles(answers, vars, stack))
258
+ return files
259
+ }