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.
- package/LICENSE +1 -1
- package/README.md +58 -160
- package/checks/check-compatibility.js +95 -0
- package/{scripts/verify-generated.mjs → checks/check-generated-project.js} +28 -4
- package/{scripts/validate-content.mjs → checks/check-library.js} +17 -7
- package/checks/check-package.js +15 -0
- package/checks/classify-changes.js +29 -0
- package/checks/run-compatibility-shard.js +21 -0
- package/docs/README.md +20 -0
- package/docs/capabilities.md +13 -0
- package/docs/compatibility.md +13 -0
- package/docs/generated-project.md +12 -0
- package/docs/getting-started.md +26 -0
- package/docs/maintainers/architecture.md +161 -0
- package/docs/maintainers/ci-strategy.md +46 -0
- package/docs/{CONTENT_MODEL.md → maintainers/content-model.md} +3 -3
- package/docs/maintainers/contributing.md +154 -0
- package/docs/{DEPENDENCY_MAINTENANCE.md → maintainers/dependencies.md} +1 -1
- package/docs/migration-v2.md +13 -0
- package/docs/production-contract.md +24 -0
- package/index.js +2 -429
- package/{playbooks → library}/INDEX.md +5 -5
- package/library/compatibility-impact.json +14 -0
- package/{playbooks/capabilities/ci/github-actions.manifest.json → library/development-tools/ci/definition.json} +1 -0
- package/library/development-tools/devops/makefile/commands.md +11 -0
- package/library/development-tools/devops/makefile/definition.json +11 -0
- package/library/development-tools/devops/makefile/docker.md +9 -0
- package/library/development-tools/devops/makefile/validation.md +5 -0
- package/{playbooks/devops/pr-template.manifest.json → library/development-tools/devops/pr-template/definition.json} +1 -0
- package/library/features/sqlalchemy-alembic.md +17 -0
- package/library/optional-features/concerns/zod/errors.md +5 -0
- package/library/optional-features/concerns/zod/testing.md +5 -0
- package/library/optional-features/concerns/zod/validation.md +21 -0
- package/library/optional-features/styling/css-modules/definition.json +12 -0
- package/library/optional-features/styling/css-modules/responsive.md +5 -0
- package/library/optional-features/styling/css-modules/theme.md +7 -0
- package/{playbooks/platform/mobile.manifest.json → library/platforms/mobile/definition.json} +1 -0
- package/{playbooks/platform/web.manifest.json → library/platforms/web/definition.json} +1 -0
- package/{playbooks/stack/react-native.manifest.json → library/stacks/expo/definition.json} +2 -2
- package/library/stacks/fastapi/architecture.md +40 -0
- package/library/stacks/fastapi/definition.json +39 -0
- package/library/stacks/fastapi/runtime.md +25 -0
- package/library/stacks/fastapi/security.md +26 -0
- package/library/stacks/fastapi/structure.md +27 -0
- package/library/stacks/fastapi/testing.md +23 -0
- package/{playbooks/stack/nextjs.manifest.json → library/stacks/nextjs/definition.json} +2 -2
- package/{playbooks/stack/no-frontend.manifest.json → library/stacks/no-frontend/definition.json} +1 -1
- package/{playbooks/stack/react-vite.manifest.json → library/stacks/react-vite/definition.json} +2 -2
- package/{compatibility/profiles.json → library/tested-versions.json} +46 -2
- package/{playbooks/universal/accessibility.manifest.json → library/universal/accessibility/definition.json} +1 -1
- package/library/universal/coding-rules/definition.json +13 -0
- package/library/universal/coding-rules/hygiene.md +9 -0
- package/library/universal/coding-rules/naming.md +17 -0
- package/{playbooks/universal/error-handling.manifest.json → library/universal/error-handling/definition.json} +1 -0
- package/library/universal/git-conventions/branches.md +5 -0
- package/library/universal/git-conventions/commits.md +7 -0
- package/library/universal/git-conventions/definition.json +13 -0
- package/library/universal/git-conventions/workflow.md +5 -0
- package/{playbooks/universal/observability.manifest.json → library/universal/observability/definition.json} +1 -0
- package/{playbooks/universal/security.manifest.json → library/universal/security/definition.json} +1 -0
- package/library/universal/typescript/boundaries.md +13 -0
- package/library/universal/typescript/definition.json +12 -0
- package/library/universal/typescript/errors.md +5 -0
- package/library/universal/typescript/patterns.md +7 -0
- package/package.json +14 -15
- package/public/logo.svg +19 -0
- package/src/cli/arguments.js +63 -0
- package/src/cli/display.js +220 -0
- package/src/cli/main.js +203 -0
- package/src/cli/navigation.js +156 -0
- package/src/cli/questions.js +188 -0
- package/src/cli/system-check.js +158 -0
- package/src/engine/create-project.js +10 -0
- package/src/engine/install-dependencies.js +13 -0
- package/{lib/catalog.js → src/engine/load-library.js} +8 -8
- package/{lib/files.js → src/engine/project-files.js} +10 -1
- package/{lib/playbooks.js → src/engine/project-guidance.js} +26 -3
- package/{lib/application-shapes.js → src/engine/project-shapes.js} +4 -4
- package/{lib/template.js → src/engine/render-templates.js} +11 -0
- package/{lib/compatibility.js → src/engine/tested-versions.js} +21 -2
- package/src/engine/upgrade-report.js +20 -0
- package/src/engine/write-files.js +34 -0
- package/{lib/stacks/registry.js → src/stacks/available-stacks.js} +25 -1
- package/src/stacks/backends/README.md +5 -0
- package/src/stacks/backends/fastapi/ci.js +3 -0
- package/src/stacks/backends/fastapi/create-files.js +874 -0
- package/src/stacks/backends/fastapi/docker.js +75 -0
- package/src/stacks/backends/fastapi/environment.js +3 -0
- package/src/stacks/backends/fastapi/index.js +32 -0
- package/{lib/stacks → src/stacks/backends}/laravel/architecture.js +1 -0
- package/{lib/stacks → src/stacks/backends}/laravel/auth/oidc.js +1 -0
- package/{lib/stacks → src/stacks/backends}/laravel/auth/public.js +1 -0
- package/{lib/stacks → src/stacks/backends}/laravel/auth/sanctum.js +1 -0
- package/{lib/stacks → src/stacks/backends}/laravel/auth/session.js +1 -0
- package/src/stacks/backends/laravel/ci.js +3 -0
- package/{lib/stacks → src/stacks/backends}/laravel/composer.js +1 -1
- package/{lib/stacks/laravel/generate.js → src/stacks/backends/laravel/create-files.js} +1 -0
- package/src/stacks/backends/laravel/docker.js +14 -0
- package/src/stacks/backends/laravel/environment.js +5 -0
- package/{lib/stacks → src/stacks/backends}/laravel/index.js +11 -3
- package/{lib/stacks → src/stacks/backends}/laravel/ui/blade.js +1 -0
- package/{lib/stacks → src/stacks/backends}/laravel/ui/index.js +1 -0
- package/{lib/stacks → src/stacks/backends}/laravel/ui/inertia-react.js +4 -2
- package/{lib/stacks → src/stacks/backends}/laravel/ui/livewire.js +1 -0
- package/{lib/stacks → src/stacks/backends}/laravel/ui/shared.js +1 -0
- package/src/stacks/backends/none/index.js +23 -0
- package/src/stacks/backends/postgres/ci.js +3 -0
- package/src/stacks/backends/postgres/create-files.js +21 -0
- package/src/stacks/backends/postgres/docker.js +3 -0
- package/src/stacks/backends/postgres/environment.js +3 -0
- package/src/stacks/backends/postgres/index.js +30 -0
- package/src/stacks/backends/springboot/ci.js +3 -0
- package/src/stacks/backends/springboot/create-files.js +91 -0
- package/src/stacks/backends/springboot/docker.js +3 -0
- package/src/stacks/backends/springboot/environment.js +3 -0
- package/src/stacks/backends/springboot/index.js +31 -0
- package/src/stacks/backends/supabase/ci.js +3 -0
- package/src/stacks/backends/supabase/create-files.js +52 -0
- package/src/stacks/backends/supabase/docker.js +3 -0
- package/src/stacks/backends/supabase/environment.js +3 -0
- package/src/stacks/backends/supabase/index.js +31 -0
- package/src/stacks/backends/supabase/native.js +14 -0
- package/src/stacks/compose-files.js +259 -0
- package/{lib/generator.js → src/stacks/create-project.js} +206 -57
- package/src/stacks/frontends/README.md +4 -0
- package/src/stacks/frontends/nextjs/ci.js +3 -0
- package/src/stacks/frontends/nextjs/create-files.js +46 -0
- package/src/stacks/frontends/nextjs/dependencies.js +5 -0
- package/src/stacks/frontends/nextjs/docker.js +3 -0
- package/src/stacks/frontends/nextjs/environment.js +7 -0
- package/src/stacks/frontends/nextjs/index.js +35 -0
- package/src/stacks/frontends/react-native/ci.js +3 -0
- package/src/stacks/frontends/react-native/create-files.js +28 -0
- package/src/stacks/frontends/react-native/dependencies.js +5 -0
- package/src/stacks/frontends/react-native/docker.js +5 -0
- package/src/stacks/frontends/react-native/environment.js +7 -0
- package/src/stacks/frontends/react-native/index.js +30 -0
- package/src/stacks/frontends/react-vite/ci.js +3 -0
- package/src/stacks/frontends/react-vite/create-files.js +27 -0
- package/src/stacks/frontends/react-vite/dependencies.js +5 -0
- package/src/stacks/frontends/react-vite/docker.js +3 -0
- package/src/stacks/frontends/react-vite/environment.js +8 -0
- package/src/stacks/frontends/react-vite/index.js +32 -0
- package/src/stacks/shared/capability-packs.js +31 -0
- package/{lib → src}/stacks/shared/contributions.js +1 -1
- package/src/stacks/shared/environment.js +38 -0
- package/src/stacks/shared/javascript-package.js +117 -0
- package/src/stacks/shared/testing-files.js +16 -0
- package/templates/ci/fastapi.yml +62 -0
- package/templates/docker/compose/postgres.yml +1 -1
- package/templates/docker/compose/springboot.yml +3 -3
- package/templates/docker/compose/supabase.yml +1 -1
- package/templates/docker/compose-prod/fastapi.yml +52 -0
- package/templates/docker/compose-prod/springboot.yml +20 -2
- package/templates/docker/dockerfile/fastapi.dev.dockerfile +9 -0
- package/templates/docker/dockerfile/fastapi.prod.dockerfile +11 -0
- package/templates/docker/dockerfile/nextjs.prod.dockerfile +1 -0
- package/templates/docker/dockerfile/springboot.prod.dockerfile +4 -1
- package/templates/docker/dockerfile/vite.prod.dockerfile +2 -1
- package/templates/makefile/fastapi.mk +97 -0
- package/docs/ARCHITECTURE.md +0 -118
- package/lib/banner.js +0 -45
- package/lib/constants.js +0 -3
- package/lib/doctor.js +0 -45
- package/lib/interview.js +0 -77
- package/lib/laravel-scaffold.js +0 -3
- package/lib/scaffold.js +0 -467
- package/lib/stacks/index.js +0 -8
- package/playbooks/concerns/zod.md +0 -174
- package/playbooks/devops/makefile.manifest.json +0 -10
- package/playbooks/devops/makefile.md +0 -556
- package/playbooks/styling/css-modules-extensions.md +0 -267
- package/playbooks/styling/css-modules.manifest.json +0 -11
- package/playbooks/universal/coding-rules.manifest.json +0 -12
- package/playbooks/universal/coding-rules.md +0 -281
- package/playbooks/universal/git-conventions.manifest.json +0 -10
- package/playbooks/universal/git-conventions.md +0 -186
- package/playbooks/universal/typescript.manifest.json +0 -11
- package/playbooks/universal/typescript.md +0 -272
- package/scripts/compatibility-matrix.mjs +0 -48
- /package/{playbooks/capabilities → library/development-tools}/ci/github-actions.md +0 -0
- /package/{playbooks/devops → library/development-tools/devops/pr-template}/pr-template.md +0 -0
- /package/{playbooks/capabilities/docker/docker.manifest.json → library/development-tools/docker/definition.json} +0 -0
- /package/{playbooks/capabilities → library/development-tools}/docker/overview.md +0 -0
- /package/{playbooks/capabilities → library/features}/auth/oidc-resource-server.md +0 -0
- /package/{playbooks/capabilities → library/features}/auth/spring-session.md +0 -0
- /package/{playbooks/capabilities/flyway/flyway.manifest.json → library/features/flyway/definition.json} +0 -0
- /package/{playbooks/capabilities → library/features}/flyway/environments.md +0 -0
- /package/{playbooks/capabilities → library/features}/flyway/migrations.md +0 -0
- /package/{playbooks/capabilities → library/features}/flyway/testing.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/database.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/migrations.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/observability.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/oidc-resource-server.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/queues.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/sanctum-spa.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/scheduler.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/session-auth.md +0 -0
- /package/{playbooks/capabilities → library/features}/laravel/storage-uploads.md +0 -0
- /package/{playbooks/capabilities → library/features}/postgresql/architecture.md +0 -0
- /package/{playbooks/capabilities/postgresql/postgresql.manifest.json → library/features/postgresql/definition.json} +0 -0
- /package/{playbooks/capabilities → library/features}/postgresql/migrations.md +0 -0
- /package/{playbooks/capabilities → library/features}/postgresql/schema-design.md +0 -0
- /package/{playbooks/capabilities → library/features}/postgresql/security.md +0 -0
- /package/{playbooks/capabilities → library/features}/postgresql/testing.md +0 -0
- /package/{playbooks/capabilities → library/features}/prisma/architecture.md +0 -0
- /package/{playbooks/capabilities/prisma/prisma.manifest.json → library/features/prisma/definition.json} +0 -0
- /package/{playbooks/capabilities → library/features}/prisma/migrations.md +0 -0
- /package/{playbooks/capabilities → library/features}/prisma/runtime.md +0 -0
- /package/{playbooks/capabilities → library/features}/prisma/schema.md +0 -0
- /package/{playbooks/capabilities → library/features}/prisma/testing.md +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/architecture.md +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/authentication.md +0 -0
- /package/{playbooks/capabilities/supabase/supabase.manifest.json → library/features/supabase/definition.json} +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/expo.md +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/migrations.md +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/nextjs.md +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/rls.md +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/testing.md +0 -0
- /package/{playbooks/capabilities → library/features}/supabase/vite.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/axios.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/next-safe-action.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/next-themes.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/nuqs.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/t3-env.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/tanstack-query.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/zustand.md +0 -0
- /package/{playbooks/styling/native-styles.manifest.json → library/optional-features/styling/native-styles/definition.json} +0 -0
- /package/{playbooks/styling → library/optional-features/styling/native-styles}/native-styles.md +0 -0
- /package/{playbooks/styling/tailwind.manifest.json → library/optional-features/styling/tailwind/definition.json} +0 -0
- /package/{playbooks/styling → library/optional-features/styling/tailwind}/tailwind-extensions.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/blade/architecture.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/blade/runtime.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/blade/security.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/blade/structure.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/blade/testing.md +0 -0
- /package/{playbooks/stack/laravel-ui.manifest.json → library/platforms/laravel-ui/definition.json} +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/architecture.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/runtime.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/security.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/structure.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/testing.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/architecture.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/runtime.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/security.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/structure.md +0 -0
- /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/testing.md +0 -0
- /package/{playbooks/platform → library/platforms/mobile}/mobile.md +0 -0
- /package/{playbooks/platform → library/platforms/web}/web.md +0 -0
- /package/{playbooks/stack → library/stacks}/expo/architecture.md +0 -0
- /package/{playbooks/stack → library/stacks}/expo/runtime.md +0 -0
- /package/{playbooks/stack → library/stacks}/expo/security.md +0 -0
- /package/{playbooks/stack → library/stacks}/expo/structure.md +0 -0
- /package/{playbooks/stack → library/stacks}/expo/testing.md +0 -0
- /package/{playbooks/stack → library/stacks}/laravel/architecture.md +0 -0
- /package/{playbooks/stack/laravel.manifest.json → library/stacks/laravel/definition.json} +0 -0
- /package/{playbooks/stack → library/stacks}/laravel/runtime.md +0 -0
- /package/{playbooks/stack → library/stacks}/laravel/security.md +0 -0
- /package/{playbooks/stack → library/stacks}/laravel/structure.md +0 -0
- /package/{playbooks/stack → library/stacks}/laravel/testing.md +0 -0
- /package/{playbooks/stack → library/stacks}/nextjs/architecture.md +0 -0
- /package/{playbooks/stack → library/stacks}/nextjs/runtime.md +0 -0
- /package/{playbooks/stack → library/stacks}/nextjs/security.md +0 -0
- /package/{playbooks/stack → library/stacks}/nextjs/structure.md +0 -0
- /package/{playbooks/stack → library/stacks}/nextjs/testing.md +0 -0
- /package/{playbooks/stack/none.manifest.json → library/stacks/none/definition.json} +0 -0
- /package/{playbooks/stack → library/stacks}/react-vite/architecture.md +0 -0
- /package/{playbooks/stack → library/stacks}/react-vite/runtime.md +0 -0
- /package/{playbooks/stack → library/stacks}/react-vite/security.md +0 -0
- /package/{playbooks/stack → library/stacks}/react-vite/structure.md +0 -0
- /package/{playbooks/stack → library/stacks}/react-vite/testing.md +0 -0
- /package/{playbooks/stack → library/stacks}/springboot/architecture.md +0 -0
- /package/{playbooks/stack/springboot.manifest.json → library/stacks/springboot/definition.json} +0 -0
- /package/{playbooks/stack → library/stacks}/springboot/runtime.md +0 -0
- /package/{playbooks/stack → library/stacks}/springboot/security.md +0 -0
- /package/{playbooks/stack → library/stacks}/springboot/structure.md +0 -0
- /package/{playbooks/stack → library/stacks}/springboot/testing.md +0 -0
- /package/{playbooks/universal → library/universal/accessibility}/accessibility.md +0 -0
- /package/{playbooks/universal → library/universal/error-handling}/error-handling.md +0 -0
- /package/{playbooks/universal → library/universal/observability}/observability.md +0 -0
- /package/{playbooks/universal → library/universal/security}/security.md +0 -0
- /package/{lib → src/engine}/project-location.js +0 -0
- /package/{lib → src}/stacks/context.js +0 -0
- /package/{lib/stacks/contract.js → src/stacks/rules.js} +0 -0
- /package/{ci → templates/ci}/expo.yml +0 -0
- /package/{ci → templates/ci}/laravel.yml +0 -0
- /package/{ci → templates/ci}/nextjs.yml +0 -0
- /package/{ci → templates/ci}/springboot.yml +0 -0
- /package/{ci → templates/ci}/vite.yml +0 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,187 +1,85 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
╚███╔███╔╝ ██║ ██║ ╚███║
|
|
7
|
-
╚══╝╚══╝ ╚═╝ ╚═╝ ╚══╝
|
|
8
|
-
|
|
9
|
-
█▀█ █▀█ █▀█ █ █▀▀ █▀▀ ▀█▀
|
|
10
|
-
█▀▀ █▀█ █ █ █ █▀ █ █
|
|
11
|
-
█ █ █ █▄█ █▄█ █▄▄ █▄▄ █
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
# Are you confused creating architecture rules, AGENTS.md, and folder structures from scratch every time you pick a new stack?
|
|
15
|
-
|
|
16
|
-
> **Just answer what tech stack you want.** `create-win-project` turns your answers into a runnable, tested, agent-ready foundation — not an empty folder with a giant doc dump.
|
|
17
|
-
|
|
18
|
-
Stop bikeshedding folders for 3 days. Stop pasting a 500-line `AGENTS.md` that burns tokens and still lets the agent hallucinate your stack. Your stack, your rules, already wired.
|
|
19
|
-
|
|
20
|
-
[](https://github.com/itsw1n/create-win-project/actions/workflows/ci.yml)
|
|
21
|
-
[](https://nodejs.org)
|
|
22
|
-
[](./playbooks)
|
|
23
|
-
[](./playbooks)
|
|
24
|
-
[](./index.js)
|
|
25
|
-
|
|
26
|
-
Next.js · React + Vite · Expo (React Native) · Spring Boot · Supabase · PostgreSQL · Tailwind · CSS Modules
|
|
27
|
-
|
|
28
|
-
---
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="./public/logo.svg" alt="W1N Project logo" width="160">
|
|
3
|
+
<h1>W1N PROJECT</h1>
|
|
4
|
+
<p>Generate a tested, production-oriented web or mobile project with stack-specific code, CI, deployment artifacts, documentation, and agent guidance.</p>
|
|
5
|
+
</div>
|
|
29
6
|
|
|
30
|
-
##
|
|
7
|
+
## Start
|
|
31
8
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
1. **A small runnable app** — real page/screen, strict TypeScript, ESLint, health endpoint, tests, and the exact config for your stack.
|
|
37
|
-
2. **A task-routed docs layer** — a tiny always-on `AGENTS.md` + a lazy `RULES.md` (`concern → playbook §`). The agent reads only what it touches, when it touches it.
|
|
38
|
-
|
|
39
|
-
Manifests declare capabilities and package names. A tested compatibility profile owns every exact version, so generated stacks are declarative without letting version strings drift.
|
|
40
|
-
|
|
41
|
-
## Prerequisites — what you actually need to install
|
|
42
|
-
|
|
43
|
-
### Lane 1 — To run *this generator*
|
|
44
|
-
**Node 24 LTS *OR* Docker — that's it.**
|
|
9
|
+
```bash
|
|
10
|
+
npx create-win-project@latest
|
|
11
|
+
```
|
|
45
12
|
|
|
46
|
-
|
|
47
|
-
|
|
13
|
+
Answer product questions, choose whether dependencies should be installed, then follow the generated project README. Existing non-empty destinations are never overwritten.
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
my-project/
|
|
17
|
+
├── application source and tests
|
|
18
|
+
├── .github/workflows/ CI and security checks
|
|
19
|
+
├── Dockerfile / eas.json production artifacts when applicable
|
|
20
|
+
├── AGENTS.md small agent operating contract
|
|
21
|
+
├── RULES.md task-to-playbook router
|
|
22
|
+
├── CONTEXT.md product decisions and approved deviations
|
|
23
|
+
├── playbooks/ selected stack guidance
|
|
24
|
+
└── create-win-project.profile.json
|
|
25
|
+
```
|
|
48
26
|
|
|
49
|
-
|
|
50
|
-
| You picked | You need | What the generator includes |
|
|
51
|
-
|---|---|---|
|
|
52
|
-
| **Next.js** or **React + Vite** | Node 24 LTS → `npm install` → `npm run dev` | Page/entry, Vite/Next config, strict TS, ESLint, tests, `frontend/.env.example` |
|
|
53
|
-
| **Expo** | Node 24 LTS + Expo Go app → `npm install` → `npx expo start` | Expo Router layout/screen, `app.json`, Jest, TS |
|
|
54
|
-
| **Supabase** | Docker for the generated local Supabase stack | Pinned local CLI, migrations/RLS tests, platform-native clients; login/callback/secure lifecycle only when login is selected |
|
|
55
|
-
| **Spring Boot / PostgreSQL** | JDK 21 + Docker for DB **only if you selected them** | Maven app, public health + fail-closed security, Flyway/PostgreSQL; server session or OIDC Resource Server when login is selected |
|
|
27
|
+
## Supported stacks
|
|
56
28
|
|
|
57
|
-
|
|
29
|
+
| Application | Backends and data |
|
|
30
|
+
|---|---|
|
|
31
|
+
| Next.js | None, Supabase, PostgreSQL, Spring Boot, Laravel, FastAPI |
|
|
32
|
+
| React + Vite | None, Supabase, Spring Boot, Laravel, FastAPI |
|
|
33
|
+
| Expo / React Native | None, Supabase, Spring Boot, Laravel, FastAPI |
|
|
34
|
+
| Laravel UI | Blade, Livewire, or Inertia React with Laravel |
|
|
35
|
+
| API only | Spring Boot, Laravel, or FastAPI |
|
|
58
36
|
|
|
59
|
-
|
|
37
|
+
Authentication follows the chosen stack and audience: Supabase Auth, server sessions, Sanctum SPA, or OIDC validation where supported.
|
|
60
38
|
|
|
61
|
-
|
|
39
|
+
## Production-oriented by default
|
|
62
40
|
|
|
63
|
-
|
|
64
|
-
npx create-win-project@latest
|
|
65
|
-
```
|
|
41
|
+
Version 2 generates stack-appropriate tests, CI and security checks, production builds, environment guidance, operations documentation, and cloud-neutral deployment artifacts. Web tests include Playwright; Expo uses Jest and React Native Testing Library.
|
|
66
42
|
|
|
67
|
-
|
|
43
|
+
Optional complexity remains requirement-driven:
|
|
68
44
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```
|
|
45
|
+
- Private object-storage uploads appear only when uploads are required.
|
|
46
|
+
- Durable queue conventions appear only when background jobs are required.
|
|
47
|
+
- Offline cache or synchronization appears only for mobile when selected.
|
|
48
|
+
- Development Docker and Make remain optional.
|
|
74
49
|
|
|
75
|
-
|
|
50
|
+
The baseline is not a substitute for product authorization, infrastructure sizing, compliance, secrets, monitoring, deployment approval, backups, or restore drills. See the [production contract](./docs/production-contract.md).
|
|
76
51
|
|
|
77
|
-
|
|
78
|
-
git clone https://github.com/itsw1n/create-win-project && cd create-win-project
|
|
79
|
-
docker compose build
|
|
80
|
-
docker compose run --rm app
|
|
81
|
-
```
|
|
52
|
+
## Agent-flexible defaults
|
|
82
53
|
|
|
83
|
-
|
|
54
|
+
Agents may propose different architecture, providers, authentication, data boundaries, or major dependencies, but must obtain approval and record the decision in `CONTEXT.md` before changing them.
|
|
84
55
|
|
|
85
|
-
|
|
86
|
-
<summary>Prefer host Node?</summary>
|
|
56
|
+
Exact tested direct versions come from dated profiles. Package managers resolve transitive dependencies and create project-owned lockfiles. Existing projects can run a read-only comparison:
|
|
87
57
|
|
|
88
58
|
```bash
|
|
89
|
-
|
|
90
|
-
npx create-win-project --no-install # generate files only
|
|
91
|
-
npx create-win-project doctor # diagnose available tools
|
|
92
|
-
# then follow the same interview
|
|
59
|
+
create-win-project upgrade-report .
|
|
93
60
|
```
|
|
94
61
|
|
|
95
|
-
|
|
62
|
+
## Documentation
|
|
96
63
|
|
|
97
|
-
|
|
64
|
+
- [Documentation map](./docs/README.md)
|
|
65
|
+
- [Getting started](./docs/getting-started.md)
|
|
66
|
+
- [Production contract](./docs/production-contract.md)
|
|
67
|
+
- [Stacks and capabilities](./docs/capabilities.md)
|
|
68
|
+
- [Understanding a generated project](./docs/generated-project.md)
|
|
69
|
+
- [Compatibility and upgrades](./docs/compatibility.md)
|
|
70
|
+
- [Migrating from version 1](./docs/migration-v2.md)
|
|
98
71
|
|
|
99
|
-
|
|
72
|
+
## Development
|
|
100
73
|
|
|
101
74
|
```bash
|
|
102
|
-
|
|
103
|
-
cd
|
|
104
|
-
npm
|
|
105
|
-
npm
|
|
106
|
-
|
|
107
|
-
# React + Vite (frontend workspace)
|
|
108
|
-
cd your-project/frontend
|
|
109
|
-
npm install
|
|
110
|
-
npm run dev
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
The generator asks whether to install dependencies. One local `npm install` provides Prettier, ESLint, TypeScript, and the selected test tools; global installs are neither required nor silently performed.
|
|
114
|
-
|
|
115
|
-
> The generator **never overwrites a non-empty folder** — it stages to a temp dir and moves into place only on success.
|
|
116
|
-
|
|
117
|
-
## Features
|
|
118
|
-
|
|
119
|
-
- **Three frontend families** — Next.js App Router, React + Vite, and Expo Router.
|
|
120
|
-
- **Optional backends** — every frontend can choose no backend, Supabase, PostgreSQL where supported, or Spring Boot.
|
|
121
|
-
- **Styling** — Tailwind CSS or CSS Modules (native-styles for Expo).
|
|
122
|
-
- **Lean agent docs** — `AGENTS.md` (tiny, always on) + `RULES.md` (lazy index) generated per project.
|
|
123
|
-
- **Stack-native profiles** — Small, Medium (recommended/default), and Large map to familiar architecture for each selected stack; Large defaults to a modular monolith, not microservices.
|
|
124
|
-
- **Intent-based authentication** — choose Yes, Not yet, or No; the generator maps that intent to Supabase Auth, Spring server sessions, or external-provider OIDC validation as appropriate.
|
|
125
|
-
- **Manifest-driven** — `*.manifest.json` drives compatibility, exact dependency requests, env prefixes (`NEXT_PUBLIC_`/`VITE_`/`EXPO_PUBLIC_`), conditional playbooks, and concern wiring.
|
|
126
|
-
- **Tested compatibility profiles** — exact direct dependencies and runtime/container versions are resolved from one catalog; current and previous profiles are verified in CI.
|
|
127
|
-
- **Optional concerns, never mandated** — validation/Zod, data-fetching, state, t3-env, URL state are advisory (`CONTEXT.md` only) not forced.
|
|
128
|
-
- **Runnable foundations** — profile-specific feature slices, health endpoints, security headers, selected auth plumbing, Spring `ProblemDetail`, PostgreSQL Testcontainers, and risk-based tests.
|
|
129
|
-
- **Safety + contracts** — destination-exists guard, manifest ↔ heading checks, and a generated-output matrix covering every pairing, architecture profile, and applicable auth model.
|
|
130
|
-
|
|
131
|
-
## What you get
|
|
132
|
-
|
|
133
|
-
| File | Purpose |
|
|
134
|
-
|------|---------|
|
|
135
|
-
| `AGENTS.md` | Lean, always-loaded guidance for your coding agent. |
|
|
136
|
-
| `RULES.md` | Lazy index — `concern → playbook §`. Read only what you touch. |
|
|
137
|
-
| `CONTEXT.md` | Project context + any advisory "expected concerns". |
|
|
138
|
-
| `playbooks/` | Curated rule playbooks (shipped lean). |
|
|
139
|
-
| `package.json` | Generated from the selected stack's manifest. |
|
|
140
|
-
| `create-win-project.profile.json` | Separately records compatibility, architecture, and authentication selections. |
|
|
141
|
-
| `.env.example` | Generated from the stack's declared env vars (prefixes already applied). |
|
|
142
|
-
| Framework source/config | A working page or screen, health endpoint where applicable, strict TypeScript, lint, tests, and build scripts. |
|
|
143
|
-
| `Makefile` / `docker-compose.yml` / `.github/workflows` | Optional, interview-toggled. |
|
|
144
|
-
|
|
145
|
-
## How it works
|
|
146
|
-
|
|
147
|
-
The generator never hardcodes the folder or playbook list. It loads `playbooks/**/*.manifest.json` and resolves a *stack*:
|
|
148
|
-
|
|
149
|
-
```jsonc
|
|
150
|
-
// playbooks/stack/nextjs.manifest.json (excerpt)
|
|
151
|
-
{
|
|
152
|
-
"id": "nextjs",
|
|
153
|
-
"kind": "frontend",
|
|
154
|
-
"label": "Next.js",
|
|
155
|
-
"appliesTo": { "backend": ["none", "supabase", "springboot", "postgres"] },
|
|
156
|
-
"architectureProfiles": ["small", "medium", "large"],
|
|
157
|
-
"playbooks": [
|
|
158
|
-
"stack/nextjs/architecture.md",
|
|
159
|
-
"stack/nextjs/structure.md",
|
|
160
|
-
"stack/nextjs/runtime.md",
|
|
161
|
-
"stack/nextjs/security.md",
|
|
162
|
-
"stack/nextjs/testing.md"
|
|
163
|
-
],
|
|
164
|
-
"deps": ["next", "react", "react-dom"],
|
|
165
|
-
"concerns": [
|
|
166
|
-
{ "id": "validation", "required": false, "when": "runtime validation needed", "sections": ["Zod for Runtime Validation"] }
|
|
167
|
-
]
|
|
168
|
-
}
|
|
75
|
+
git clone https://github.com/itsw1n/create-win-project
|
|
76
|
+
cd create-win-project
|
|
77
|
+
npm ci
|
|
78
|
+
npm test
|
|
169
79
|
```
|
|
170
80
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
## Topics
|
|
174
|
-
|
|
175
|
-
Next.js · React · Spring Boot · Supabase · PostgreSQL · TypeScript · Tailwind CSS · CSS Modules · project scaffolding · project template · `AGENTS.md` · coding agents · manifest-driven · lean documentation · lazy rules index
|
|
176
|
-
|
|
177
|
-
## Contributing
|
|
178
|
-
|
|
179
|
-
1. Fork and create a feature branch.
|
|
180
|
-
2. `npm install` and `npm test` must stay green.
|
|
181
|
-
3. Add or update a `*.manifest.json` for new stacks/concerns; put package versions only in `compatibility/profiles.json`.
|
|
182
|
-
4. Run `npm run verify:generated -- --profile=2026.09 --case=nextjs-supabase --architecture=medium --authentication=yes` for a focused executable check. CI runs every supported combination, architecture/auth model, and retained profile.
|
|
183
|
-
5. Document migration work for major upgrades, then open a pull request.
|
|
81
|
+
See the [maintainer documentation](./docs/maintainers/contributing.md) before changing manifests, generated behavior, compatibility profiles, or CI.
|
|
184
82
|
|
|
185
83
|
## License
|
|
186
84
|
|
|
187
|
-
|
|
85
|
+
Licensed under the [MIT License](./LICENSE). Copyright © 2026 itsw1n.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from 'fs-extra'
|
|
2
|
+
|
|
3
|
+
const catalog = await fs.readJson(new URL('../library/tested-versions.json', import.meta.url))
|
|
4
|
+
const scope = process.argv.find((arg) => arg.startsWith('--scope='))?.split('=')[1] || 'smoke'
|
|
5
|
+
const selectedStack = process.argv.find((arg) => arg.startsWith('--stack='))?.split('=')[1]
|
|
6
|
+
if (!['none', 'smoke', 'stack', 'full'].includes(scope)) throw new Error('--scope must be none, smoke, stack, or full')
|
|
7
|
+
if (scope === 'stack' && !selectedStack) throw new Error('--stack is required for stack scope')
|
|
8
|
+
|
|
9
|
+
const cases = [
|
|
10
|
+
'nextjs-none', 'nextjs-supabase', 'nextjs-springboot', 'nextjs-postgres', 'nextjs-laravel', 'nextjs-fastapi',
|
|
11
|
+
'react-none', 'react-supabase', 'react-springboot', 'react-laravel', 'react-fastapi',
|
|
12
|
+
'react-native-none', 'react-native-supabase', 'react-native-springboot', 'react-native-laravel', 'react-native-fastapi',
|
|
13
|
+
'laravel-api', 'laravel-blade', 'laravel-livewire', 'laravel-inertia-react',
|
|
14
|
+
'fastapi-api',
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
function authChoices(caseName) {
|
|
18
|
+
const mobile = caseName.startsWith('react-native')
|
|
19
|
+
const nonBrowser = mobile || caseName === 'laravel-api' || caseName === 'fastapi-api'
|
|
20
|
+
const base = ['not-yet', 'none'].map((authentication) => ({ authentication, audience: nonBrowser ? 'multi-client' : 'website' }))
|
|
21
|
+
if (caseName.includes('supabase')) base.push({ authentication: 'yes', audience: mobile ? 'multi-client' : 'website' })
|
|
22
|
+
if (caseName.includes('springboot') || caseName.includes('laravel') || caseName.includes('fastapi')) {
|
|
23
|
+
if (caseName.startsWith('laravel-') && caseName !== 'laravel-api') base.push({ authentication: 'yes', audience: 'website' })
|
|
24
|
+
else if (mobile || caseName === 'laravel-api' || caseName === 'fastapi-api') base.push({ authentication: 'yes', audience: 'multi-client' })
|
|
25
|
+
else base.push({ authentication: 'yes', audience: 'website' }, { authentication: 'yes', audience: 'multi-client' })
|
|
26
|
+
}
|
|
27
|
+
return base
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const current = catalog.defaultProfile
|
|
31
|
+
const allProfiles = Object.entries(catalog.profiles).flatMap(([profile, versions]) =>
|
|
32
|
+
cases.flatMap((caseName) => ['small', 'medium', 'large'].flatMap((architecture) =>
|
|
33
|
+
authChoices(caseName).map((auth) => ({ profile, case: caseName, architecture, ...auth, node: versions.runtimes.node, java: versions.runtimes.java, php: versions.runtimes.php })))),
|
|
34
|
+
)
|
|
35
|
+
const currentFull = allProfiles.filter((entry) => entry.profile === current)
|
|
36
|
+
|
|
37
|
+
const smokeSelections = [
|
|
38
|
+
['nextjs-none', 'small', 'not-yet', 'website'],
|
|
39
|
+
['nextjs-supabase', 'medium', 'yes', 'website'],
|
|
40
|
+
['react-springboot', 'large', 'yes', 'website'],
|
|
41
|
+
['react-native-supabase', 'medium', 'yes', 'multi-client'],
|
|
42
|
+
['react-native-springboot', 'small', 'yes', 'multi-client'],
|
|
43
|
+
['laravel-api', 'medium', 'not-yet', 'multi-client'],
|
|
44
|
+
['laravel-blade', 'small', 'yes', 'website'],
|
|
45
|
+
['laravel-livewire', 'medium', 'none', 'website'],
|
|
46
|
+
['laravel-inertia-react', 'large', 'yes', 'website'],
|
|
47
|
+
['react-laravel', 'medium', 'yes', 'website'],
|
|
48
|
+
['nextjs-fastapi', 'small', 'none', 'website'],
|
|
49
|
+
['nextjs-fastapi', 'medium', 'yes', 'website'],
|
|
50
|
+
['fastapi-api', 'medium', 'not-yet', 'multi-client'],
|
|
51
|
+
['fastapi-api', 'large', 'yes', 'multi-client'],
|
|
52
|
+
]
|
|
53
|
+
const matchesSmoke = (entry) => smokeSelections.some(([caseName, architecture, authentication, audience]) =>
|
|
54
|
+
entry.case === caseName && entry.architecture === architecture && entry.authentication === authentication && entry.audience === audience)
|
|
55
|
+
const containerSelections = new Set([
|
|
56
|
+
'nextjs-none:small:not-yet:website',
|
|
57
|
+
'react-springboot:large:yes:website',
|
|
58
|
+
'laravel-inertia-react:large:yes:website',
|
|
59
|
+
'nextjs-fastapi:medium:yes:website',
|
|
60
|
+
])
|
|
61
|
+
const verifiesContainers = (entry) => entry.profile === current && containerSelections.has(
|
|
62
|
+
`${entry.case}:${entry.architecture}:${entry.authentication}:${entry.audience}`,
|
|
63
|
+
)
|
|
64
|
+
const smoke = currentFull.filter(matchesSmoke)
|
|
65
|
+
// A complete run exhaustively verifies the current profile and keeps every
|
|
66
|
+
// retained profile alive through the same representative compatibility lanes.
|
|
67
|
+
const full = [...currentFull, ...allProfiles.filter((entry) => entry.profile !== current && matchesSmoke(entry))]
|
|
68
|
+
|
|
69
|
+
function belongsToStack(caseName, stackName) {
|
|
70
|
+
if (stackName === 'none') return caseName.endsWith('-none')
|
|
71
|
+
if (stackName === 'react') return caseName.startsWith('react-')
|
|
72
|
+
if (stackName === 'react-native') return caseName.startsWith('react-native-')
|
|
73
|
+
if (['nextjs'].includes(stackName)) return caseName.startsWith(`${stackName}-`)
|
|
74
|
+
return caseName.includes(stackName)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stackCases = full.filter((entry) => belongsToStack(entry.case, selectedStack))
|
|
78
|
+
const stack = [...new Map([...stackCases, ...smoke].map((entry) => [JSON.stringify(entry), entry])).values()]
|
|
79
|
+
const selected = (scope === 'full' ? full : scope === 'stack' ? stack : smoke)
|
|
80
|
+
.map((entry) => ({
|
|
81
|
+
...entry,
|
|
82
|
+
native: scope === 'smoke' || matchesSmoke(entry),
|
|
83
|
+
containers: verifiesContainers(entry),
|
|
84
|
+
}))
|
|
85
|
+
const shards = Array.from({ length: 4 }, (_, index) => ({ shard: index + 1, cases: [] }))
|
|
86
|
+
const containerCases = selected.filter((entry) => entry.containers)
|
|
87
|
+
const nativeCases = selected.filter((entry) => entry.native && !entry.containers)
|
|
88
|
+
const contractCases = selected.filter((entry) => !entry.native)
|
|
89
|
+
containerCases.forEach((entry, index) => shards[index % shards.length].cases.push(entry))
|
|
90
|
+
nativeCases.forEach((entry, index) => shards[index % shards.length].cases.push(entry))
|
|
91
|
+
contractCases.forEach((entry) => {
|
|
92
|
+
const shortest = shards.reduce((best, shard) => shard.cases.length < best.cases.length ? shard : best)
|
|
93
|
+
shortest.cases.push(entry)
|
|
94
|
+
})
|
|
95
|
+
process.stdout.write(JSON.stringify(shards.filter(({ cases }) => cases.length)))
|
|
@@ -3,7 +3,7 @@ import os from 'node:os'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { spawnSync } from 'node:child_process'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { generateProject } from '../
|
|
6
|
+
import { generateProject } from '../src/engine/create-project.js'
|
|
7
7
|
|
|
8
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
9
9
|
const args = Object.fromEntries(process.argv.slice(2).map((arg) => {
|
|
@@ -25,6 +25,10 @@ const cases = {
|
|
|
25
25
|
'react-native-springboot': { frontend: 'react-native', backend: 'springboot', packageName: 'com.example' },
|
|
26
26
|
'react-native-none': { frontend: 'react-native', backend: 'none' },
|
|
27
27
|
'react-native-laravel': { frontend: 'react-native', backend: 'laravel', applicationShape: 'mobile' },
|
|
28
|
+
'react-native-fastapi': { frontend: 'react-native', backend: 'fastapi', applicationShape: 'mobile' },
|
|
29
|
+
'nextjs-fastapi': { frontend: 'nextjs', backend: 'fastapi', styling: 'tailwind' },
|
|
30
|
+
'react-fastapi': { frontend: 'react', backend: 'fastapi', styling: 'tailwind', applicationShape: 'separate' },
|
|
31
|
+
'fastapi-api': { frontend: 'no-frontend', backend: 'fastapi', applicationShape: 'api' },
|
|
28
32
|
'laravel-api': { frontend: 'no-frontend', backend: 'laravel', applicationShape: 'api' },
|
|
29
33
|
'laravel-blade': { frontend: 'laravel-ui', backend: 'laravel', applicationShape: 'fullstack', laravelUi: 'blade', styling: 'tailwind' },
|
|
30
34
|
'laravel-livewire': { frontend: 'laravel-ui', backend: 'laravel', applicationShape: 'fullstack', laravelUi: 'livewire', styling: 'tailwind' },
|
|
@@ -32,7 +36,7 @@ const cases = {
|
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
if (!args.profile || !cases[args.case]) {
|
|
35
|
-
throw new Error(`Usage:
|
|
39
|
+
throw new Error(`Usage: npm run verify:generated -- --profile=<id> --case=<${Object.keys(cases).join('|')}> [--architecture=small|medium|large] [--authentication=yes|not-yet|none] [--auth-audience=website|multi-client] [--native=true|false]`)
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const architecture = args.architecture || 'medium'
|
|
@@ -67,7 +71,7 @@ try {
|
|
|
67
71
|
authentication,
|
|
68
72
|
authAudience,
|
|
69
73
|
testing: selected.frontend === 'react-native' ? 'basic' : 'full',
|
|
70
|
-
docker: selected.frontend !== 'react-native' ||
|
|
74
|
+
docker: selected.frontend !== 'react-native' || ['laravel', 'fastapi'].includes(selected.backend),
|
|
71
75
|
makefile: false,
|
|
72
76
|
githubActions: true,
|
|
73
77
|
expectedConcerns: [],
|
|
@@ -80,6 +84,9 @@ try {
|
|
|
80
84
|
metadata.authentication.intent !== authentication || metadata.authentication.audience !== authAudience) {
|
|
81
85
|
throw new Error('Generated profile metadata does not match the requested matrix entry')
|
|
82
86
|
}
|
|
87
|
+
if (args.native === 'false') {
|
|
88
|
+
console.log(`Generated contract verified: ${projectName}`)
|
|
89
|
+
} else {
|
|
83
90
|
const packageRoot = selected.frontend === 'react' ? path.join(projectRoot, 'frontend') : projectRoot
|
|
84
91
|
const publicEnv = {
|
|
85
92
|
NEXT_PUBLIC_SUPABASE_URL: 'https://example.supabase.co',
|
|
@@ -94,7 +101,7 @@ try {
|
|
|
94
101
|
POSTGRES_USER: 'postgres',
|
|
95
102
|
POSTGRES_PASSWORD: 'compatibility-test',
|
|
96
103
|
POSTGRES_DB: projectName.replaceAll('-', '_'),
|
|
97
|
-
DATABASE_URL: `postgresql://postgres:compatibility-test@localhost:5432/${projectName.replaceAll('-', '_')}`,
|
|
104
|
+
DATABASE_URL: `postgresql+asyncpg://postgres:compatibility-test@localhost:5432/${projectName.replaceAll('-', '_')}`,
|
|
98
105
|
SPRING_PROFILES_ACTIVE: 'test',
|
|
99
106
|
OIDC_ISSUER_URI: 'http://localhost:9090/realms/app',
|
|
100
107
|
OIDC_AUDIENCE: 'api',
|
|
@@ -140,6 +147,22 @@ try {
|
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
if (selected.backend === 'fastapi') {
|
|
151
|
+
const backendRoot = selected.frontend === 'no-frontend' ? projectRoot : path.join(projectRoot, 'backend')
|
|
152
|
+
run('uv', ['sync'], backendRoot)
|
|
153
|
+
run('docker', ['compose', 'up', '-d', '--wait', 'db'], projectRoot, publicEnv)
|
|
154
|
+
try {
|
|
155
|
+
run('uv', ['run', 'ruff', 'check', '.'], backendRoot)
|
|
156
|
+
run('uv', ['run', 'ruff', 'format', '--check', '.'], backendRoot)
|
|
157
|
+
run('uv', ['run', 'mypy', '.'], backendRoot)
|
|
158
|
+
run('uv', ['run', 'pytest'], backendRoot, publicEnv)
|
|
159
|
+
run('uv', ['run', 'alembic', 'upgrade', 'head'], backendRoot, publicEnv)
|
|
160
|
+
run('uv', ['run', 'alembic', 'check'], backendRoot, publicEnv)
|
|
161
|
+
} finally {
|
|
162
|
+
run('docker', ['compose', 'down', '--volumes'], projectRoot, publicEnv)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
143
166
|
if (selected.frontend !== 'react-native') {
|
|
144
167
|
run('docker', ['compose', 'config'], projectRoot, publicEnv)
|
|
145
168
|
if (args.containers === 'true') {
|
|
@@ -147,6 +170,7 @@ try {
|
|
|
147
170
|
if (selected.backend === 'postgres') run('docker', ['build', '-t', `${projectName}:compat`, '.'], projectRoot, publicEnv)
|
|
148
171
|
}
|
|
149
172
|
}
|
|
173
|
+
}
|
|
150
174
|
} finally {
|
|
151
175
|
process.chdir(root)
|
|
152
176
|
await fs.remove(fixtureRoot)
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import fs from 'fs-extra'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { loadCatalog, resolveStack } from '../
|
|
5
|
-
import { buildRulesIndex } from '../
|
|
4
|
+
import { loadCatalog, resolveStack } from '../src/engine/load-library.js'
|
|
5
|
+
import { buildRulesIndex, resolvePlaybook } from '../src/engine/project-guidance.js'
|
|
6
6
|
|
|
7
7
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
8
|
-
const playbooksDir = path.join(root, '
|
|
8
|
+
const playbooksDir = path.join(root, 'library')
|
|
9
9
|
const catalog = await loadCatalog(playbooksDir)
|
|
10
10
|
const errors = []
|
|
11
11
|
|
|
12
12
|
for (const manifest of catalog.manifests) {
|
|
13
13
|
for (const entry of manifest._playbooks || []) {
|
|
14
14
|
const relative = typeof entry === 'string' ? entry : entry.file
|
|
15
|
-
if (!await fs.pathExists(
|
|
15
|
+
if (!await fs.pathExists(await resolvePlaybook(playbooksDir, relative))) {
|
|
16
16
|
errors.push(`${manifest.id}: missing playbook ${relative}`)
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -23,10 +23,10 @@ const authIntents = ['not-yet', 'none']
|
|
|
23
23
|
for (const frontend of catalog.frontends) {
|
|
24
24
|
for (const backend of frontend.appliesTo?.backend || []) {
|
|
25
25
|
const authCases = [...authIntents]
|
|
26
|
-
if (['supabase', 'springboot'].includes(backend)) authCases.push('yes')
|
|
26
|
+
if (['supabase', 'springboot', 'fastapi'].includes(backend)) authCases.push('yes')
|
|
27
27
|
for (const architecture of architectures) {
|
|
28
28
|
for (const authentication of authCases) {
|
|
29
|
-
const audiences = authentication === 'yes' &&
|
|
29
|
+
const audiences = authentication === 'yes' && ['springboot', 'fastapi'].includes(backend)
|
|
30
30
|
? ['website', 'multi-client']
|
|
31
31
|
: [frontend.platform === 'mobile' ? 'multi-client' : 'website']
|
|
32
32
|
for (const authAudience of audiences) {
|
|
@@ -48,8 +48,12 @@ const markdownFiles = (await fs.readdir(playbooksDir, { recursive: true, withFil
|
|
|
48
48
|
for (const file of markdownFiles) {
|
|
49
49
|
const content = await fs.readFile(file, 'utf8')
|
|
50
50
|
const relative = path.relative(playbooksDir, file)
|
|
51
|
-
const limit =
|
|
51
|
+
const limit = 200
|
|
52
52
|
if (content.split('\n').length > limit) errors.push(`${relative}: exceeds ${limit} lines`)
|
|
53
|
+
const headings = [...content.matchAll(/^#{1,2}\s+(.+)$/gm)].map((match) => match[1].trim().toLocaleLowerCase('en'))
|
|
54
|
+
for (const heading of new Set(headings.filter((value, index) => headings.indexOf(value) !== index))) {
|
|
55
|
+
errors.push(`${relative}: duplicate heading ${heading}`)
|
|
56
|
+
}
|
|
53
57
|
for (const match of content.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
54
58
|
const target = match[1].split('#')[0]
|
|
55
59
|
if (!target || /^(?:https?:|mailto:)/.test(target)) continue
|
|
@@ -64,8 +68,14 @@ for (const directory of [path.join(root, 'README.md'), path.join(root, 'docs'),
|
|
|
64
68
|
? (await fs.readdir(directory, { recursive: true, withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith('.md')).map((entry) => path.join(entry.parentPath, entry.name))
|
|
65
69
|
: [directory]
|
|
66
70
|
for (const file of files) {
|
|
71
|
+
if (path.relative(root, file).startsWith(`docs${path.sep}superpowers${path.sep}`)) continue
|
|
67
72
|
const content = await fs.readFile(file, 'utf8')
|
|
68
73
|
for (const stale of staleClaims) if (content.includes(stale)) errors.push(`${path.relative(root, file)}: stale reference ${stale}`)
|
|
74
|
+
for (const match of content.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
75
|
+
const target = match[1].split('#')[0]
|
|
76
|
+
if (!target || /^(?:https?:|mailto:)/.test(target)) continue
|
|
77
|
+
if (!await fs.pathExists(path.resolve(path.dirname(file), target))) errors.push(`${path.relative(root, file)}: broken link ${match[1]}`)
|
|
78
|
+
}
|
|
69
79
|
}
|
|
70
80
|
}
|
|
71
81
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
const [{ files = [] } = {}] = JSON.parse(execFileSync('npm', ['pack', '--dry-run', '--json'], { encoding: 'utf8' }))
|
|
4
|
+
const names = new Set(files.map(({ path }) => path))
|
|
5
|
+
const required = ['index.js', 'package.json', 'README.md', 'LICENSE', 'src/cli/main.js', 'library/tested-versions.json']
|
|
6
|
+
const missing = required.filter((name) => !names.has(name))
|
|
7
|
+
const forbidden = [...names].filter((name) => name.startsWith('.github/') || name.startsWith('tests/') || name.includes('.env'))
|
|
8
|
+
|
|
9
|
+
if (missing.length || forbidden.length) {
|
|
10
|
+
if (missing.length) console.error(`Missing package files: ${missing.join(', ')}`)
|
|
11
|
+
if (forbidden.length) console.error(`Forbidden package files: ${forbidden.join(', ')}`)
|
|
12
|
+
process.exitCode = 1
|
|
13
|
+
} else {
|
|
14
|
+
console.log(`Package contents verified (${names.size} files).`)
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url'
|
|
2
|
+
import impact from '../library/compatibility-impact.json' with { type: 'json' }
|
|
3
|
+
|
|
4
|
+
const DOCUMENTATION = /^(?:AGENTS\.md|README\.md|LICENSE|docs\/|\.github\/ISSUE_TEMPLATE\/|\.github\/PULL_REQUEST_TEMPLATE)/
|
|
5
|
+
const STACK_PATHS = Object.entries(impact.stacks)
|
|
6
|
+
|
|
7
|
+
export function classifyChanges(files) {
|
|
8
|
+
const changed = [...new Set(files.map((file) => file.trim()).filter(Boolean))]
|
|
9
|
+
if (!changed.length || changed.every((file) => DOCUMENTATION.test(file))) return { scope: 'none' }
|
|
10
|
+
if (changed.some((file) => impact.shared.some((prefix) => file.startsWith(prefix)))) return { scope: 'full' }
|
|
11
|
+
|
|
12
|
+
const stacks = new Set()
|
|
13
|
+
let hasOrdinaryChange = false
|
|
14
|
+
for (const file of changed) {
|
|
15
|
+
const matches = STACK_PATHS.filter(([, prefixes]) => prefixes.some((prefix) => file.startsWith(prefix))).map(([stack]) => stack)
|
|
16
|
+
if (matches.length) matches.forEach((stack) => stacks.add(stack))
|
|
17
|
+
else if (!DOCUMENTATION.test(file)) hasOrdinaryChange = true
|
|
18
|
+
}
|
|
19
|
+
if (!hasOrdinaryChange && stacks.size === 1) return { scope: 'stack', stack: [...stacks][0] }
|
|
20
|
+
return { scope: 'smoke' }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
let input = ''
|
|
25
|
+
for await (const chunk of process.stdin) input += chunk
|
|
26
|
+
process.stdout.write(JSON.stringify(classifyChanges(input.split(/\r?\n/))))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) await main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
const cases = JSON.parse(process.env.COMPATIBILITY_CASES || '[]')
|
|
4
|
+
if (!Array.isArray(cases) || !cases.length) throw new Error('COMPATIBILITY_CASES must contain at least one case')
|
|
5
|
+
|
|
6
|
+
for (const testCase of cases) {
|
|
7
|
+
const args = [
|
|
8
|
+
'checks/check-generated-project.js',
|
|
9
|
+
`--profile=${testCase.profile}`,
|
|
10
|
+
`--case=${testCase.case}`,
|
|
11
|
+
`--architecture=${testCase.architecture}`,
|
|
12
|
+
`--authentication=${testCase.authentication}`,
|
|
13
|
+
`--auth-audience=${testCase.audience}`,
|
|
14
|
+
`--native=${testCase.native !== false}`,
|
|
15
|
+
]
|
|
16
|
+
if (testCase.native !== false && testCase.containers && process.env.VERIFY_CONTAINERS === 'true') args.push('--containers=true')
|
|
17
|
+
console.log(`::group::${testCase.profile} ${testCase.case} ${testCase.architecture} ${testCase.authentication}`)
|
|
18
|
+
const result = spawnSync(process.execPath, args, { stdio: 'inherit' })
|
|
19
|
+
console.log('::endgroup::')
|
|
20
|
+
if (result.status !== 0) process.exit(result.status ?? 1)
|
|
21
|
+
}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
## Using create-win-project
|
|
4
|
+
|
|
5
|
+
- [Getting started](getting-started.md) — install the CLI and create the first project.
|
|
6
|
+
- [Production contract](production-contract.md) — guarantees and team-owned work.
|
|
7
|
+
- [Stacks and capabilities](capabilities.md) — supported combinations and conditional features.
|
|
8
|
+
- [Generated project](generated-project.md) — files and next steps after generation.
|
|
9
|
+
- [Compatibility and upgrades](compatibility.md) — versions, lockfiles, support, and reports.
|
|
10
|
+
- [Version 1 migration](migration-v2.md) — adopt version 2 without overwriting a project.
|
|
11
|
+
|
|
12
|
+
## Maintaining the generator
|
|
13
|
+
|
|
14
|
+
- [Architecture](maintainers/architecture.md)
|
|
15
|
+
- [Content model](maintainers/content-model.md)
|
|
16
|
+
- [CI strategy](maintainers/ci-strategy.md)
|
|
17
|
+
- [Dependency maintenance](maintainers/dependencies.md)
|
|
18
|
+
- [Contributing](maintainers/contributing.md)
|
|
19
|
+
|
|
20
|
+
The `library/` directory contains manifest-selected knowledge copied into generated projects for coding agents. It is not general user documentation.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Stacks and capabilities
|
|
2
|
+
|
|
3
|
+
Frontends include Next.js, React with Vite, Expo/React Native, and Laravel Blade, Livewire, or Inertia React. Server/data boundaries include none, Supabase, PostgreSQL through Prisma, Spring Boot, Laravel, and FastAPI (Python with uv, async SQLAlchemy/PostgreSQL, and Alembic). Unsupported pairings fail before files are written.
|
|
4
|
+
|
|
5
|
+
Authentication is intent-based. The generator selects Supabase Auth, a server-managed browser session, Sanctum SPA, or OIDC validation according to stack and audience. FastAPI uses OIDC bearer validation only; it does not offer session authentication.
|
|
6
|
+
|
|
7
|
+
## Conditional capabilities
|
|
8
|
+
|
|
9
|
+
- `--uploads=object-storage` adds private-upload authorization, limits, signature verification, generated names, quarantine/scanning, cleanup, and test expectations.
|
|
10
|
+
- `--background-jobs=queue` adds bounded retry, timeouts, idempotency, failed jobs, observability, and replay rules.
|
|
11
|
+
- `--offline=cache|sync` is mobile-only and defines ownership, keys, TTL, invalidation, privacy, reconnect states, and conflicts.
|
|
12
|
+
|
|
13
|
+
Shared server caching is not automatic. Add it only for a measured use case with recorded ownership, keys, TTL, invalidation, privacy, and multi-instance behavior.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Compatibility profiles and upgrades
|
|
2
|
+
|
|
3
|
+
Dated current and previous profiles own exact direct npm, Composer, and Python versions plus tested runtime and container versions. Definitions request package names only. Package managers validate peers, resolve transitive dependencies, and generate project-owned lockfiles (`package-lock.json`, `composer.lock`, `uv.lock`).
|
|
4
|
+
|
|
5
|
+
Profiles are tested starting points, not permanent locks. Support and successor dates are recorded in `library/tested-versions.json`; promotion requires generated-project coverage and migration review.
|
|
6
|
+
|
|
7
|
+
Existing projects are never modified automatically:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
create-win-project upgrade-report .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The report describes profile, runtime, security-contract, and migration differences without writing files. Review dependency release notes and resolved lockfile changes before upgrading.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Understanding a generated project
|
|
2
|
+
|
|
3
|
+
Every project contains runnable source, tests, stack-specific CI and security workflows, environment examples, setup and operations guides, plus production Docker or EAS files where applicable.
|
|
4
|
+
|
|
5
|
+
`create-win-project.profile.json` records schema version 2, stack, dated profile, architecture, authentication, production guarantees, capabilities, and runtimes. It enables read-only comparisons without controlling future project changes.
|
|
6
|
+
|
|
7
|
+
- `AGENTS.md` is the small always-on operating and authority contract.
|
|
8
|
+
- `RULES.md` maps tasks to exact selected playbook sections.
|
|
9
|
+
- `playbooks/` contains only knowledge applicable to the stack.
|
|
10
|
+
- `CONTEXT.md` records product goals, decisions, capabilities, and approved deviations.
|
|
11
|
+
|
|
12
|
+
Application behavior, tests, and framework configuration remain the source of truth when prose drifts.
|