create-win-project 1.3.0 → 1.4.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/README.md +19 -28
- package/{scripts/compatibility-matrix.mjs → checks/check-compatibility.js} +24 -6
- package/{scripts/verify-generated.mjs → checks/check-generated-project.js} +2 -2
- package/{scripts/validate-content.mjs → checks/check-library.js} +5 -5
- package/checks/classify-changes.js +39 -0
- package/docs/ARCHITECTURE.md +83 -40
- package/docs/CONTENT_MODEL.md +3 -3
- package/docs/CONTRIBUTING.md +154 -0
- package/docs/DEPENDENCY_MAINTENANCE.md +1 -1
- package/index.js +2 -429
- package/{playbooks → library}/INDEX.md +4 -4
- package/{playbooks/capabilities/ci/github-actions.manifest.json → library/development-tools/ci/definition.json} +1 -0
- package/{playbooks/devops/makefile.manifest.json → library/development-tools/devops/makefile/definition.json} +1 -0
- package/{playbooks/devops/pr-template.manifest.json → library/development-tools/devops/pr-template/definition.json} +1 -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/{compatibility/profiles.json → library/tested-versions.json} +4 -0
- package/{playbooks/universal/accessibility.manifest.json → library/universal/accessibility/definition.json} +1 -1
- package/{playbooks/universal/coding-rules.manifest.json → library/universal/coding-rules/definition.json} +1 -0
- package/{playbooks/universal/error-handling.manifest.json → library/universal/error-handling/definition.json} +1 -0
- package/{playbooks/universal/git-conventions.manifest.json → library/universal/git-conventions/definition.json} +1 -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/{playbooks/universal/typescript.manifest.json → library/universal/typescript/definition.json} +1 -0
- package/package.json +12 -11
- package/public/logo.svg +19 -0
- package/src/cli/arguments.js +52 -0
- package/src/cli/display.js +220 -0
- package/src/cli/main.js +185 -0
- package/src/cli/navigation.js +156 -0
- package/src/cli/questions.js +187 -0
- package/src/cli/system-check.js +138 -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} +5 -6
- package/{lib/playbooks.js → src/engine/project-guidance.js} +25 -3
- package/{lib/template.js → src/engine/render-templates.js} +8 -0
- package/src/engine/write-files.js +34 -0
- package/{lib/stacks/registry.js → src/stacks/available-stacks.js} +23 -1
- package/src/stacks/backends/README.md +5 -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 +241 -0
- package/{lib/generator.js → src/stacks/create-project.js} +78 -50
- 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 +25 -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/{lib → src}/stacks/shared/contributions.js +1 -1
- package/src/stacks/shared/environment.js +30 -0
- package/src/stacks/shared/javascript-package.js +111 -0
- package/src/stacks/shared/testing-files.js +16 -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/springboot.yml +1 -1
- 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/capabilities → library/development-tools}/ci/github-actions.md +0 -0
- /package/{playbooks/devops → library/development-tools/devops/makefile}/makefile.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/zod.md +0 -0
- /package/{playbooks → library/optional-features}/concerns/zustand.md +0 -0
- /package/{playbooks/styling → library/optional-features/styling/css-modules}/css-modules-extensions.md +0 -0
- /package/{playbooks/styling/css-modules.manifest.json → library/optional-features/styling/css-modules/definition.json} +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/react-native.manifest.json → library/stacks/expo/definition.json} +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/nextjs.manifest.json → library/stacks/nextjs/definition.json} +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/no-frontend.manifest.json → library/stacks/no-frontend/definition.json} +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/react-vite.manifest.json → library/stacks/react-vite/definition.json} +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/coding-rules}/coding-rules.md +0 -0
- /package/{playbooks/universal → library/universal/error-handling}/error-handling.md +0 -0
- /package/{playbooks/universal → library/universal/git-conventions}/git-conventions.md +0 -0
- /package/{playbooks/universal → library/universal/observability}/observability.md +0 -0
- /package/{playbooks/universal → library/universal/security}/security.md +0 -0
- /package/{playbooks/universal → library/universal/typescript}/typescript.md +0 -0
- /package/{lib/files.js → src/engine/project-files.js} +0 -0
- /package/{lib → src/engine}/project-location.js +0 -0
- /package/{lib/application-shapes.js → src/engine/project-shapes.js} +0 -0
- /package/{lib/compatibility.js → src/engine/tested-versions.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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
See `docs/ARCHITECTURE.md` for completed source ownership and `docs/DEPENDENCY_MAINTENANCE.md` for profile promotion. This file is the workflow, not the design.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
1. Fork and create a feature branch from `dev`.
|
|
8
|
+
2. `npm ci` — Node 22.14+ with npm 11.19+ required (tested on Node 24).
|
|
9
|
+
3. `npm test` must stay green. No global `prettier`/`eslint`/`typescript` needed — they are generated inside your project.
|
|
10
|
+
|
|
11
|
+
## Adding a stack — plan first, then declare, then build
|
|
12
|
+
|
|
13
|
+
A stack is **planned content first, then a declaration in `library/`, then executable code in `src/stacks/`**. `library/` never contains business entities — it declares *what* the stack supports; `src/stacks/<id>/create-files.js` implements *how* those files are generated. Do not start with code; start with the content model.
|
|
14
|
+
|
|
15
|
+
### 0. Plan your stack's content (before touching `library/`)
|
|
16
|
+
|
|
17
|
+
Answer these 5 questions and write them down (a short RFC or issue is enough). If you can't answer them, you can't write a `definition.json` yet.
|
|
18
|
+
|
|
19
|
+
**a. Identity & compatibility:** What `id` (`django`, `nuxt`), `kind` (`frontend`|`backend`|`data`), `label` (`Django (Python)`), and which existing stacks can it pair with? Example: `nextjs` pairs with `none,postgres,supabase,springboot,laravel`; `react-native` pairs with `none,supabase,springboot,laravel`; `laravel` pairs with `nextjs,react,react-native,no-frontend,laravel-ui`.
|
|
20
|
+
|
|
21
|
+
**b. Shapes:** Which **applicationShapes** does it enable? Shapes are the *deployment layout*, not the framework. They determine valid `frontend + backend` combos via `lib/application-shapes.js` → `src/engine/project-shapes.js`:
|
|
22
|
+
|
|
23
|
+
| Shape | Meaning | Example combos |
|
|
24
|
+
|-------|---------|---------------|
|
|
25
|
+
| `fullstack` | One project owns website + server | `nextjs+none`, `nextjs+postgres`, `laravel-ui+laravel` |
|
|
26
|
+
| `separate` | Two apps via API: frontend + backend API | `nextjs+springboot`, `react+supabase`, `react+laravel` |
|
|
27
|
+
| `api` | Backend API only, no generated website | `no-frontend+springboot`, `no-frontend+laravel` |
|
|
28
|
+
| `mobile` | Expo app + API/managed data | `react-native+supabase`, `react-native+springboot` |
|
|
29
|
+
| `frontend` | Browser-only, no generated server | `react+none` |
|
|
30
|
+
|
|
31
|
+
You don't invent a new shape — you pick from these 5. If `nextjs+django` should be fullstack, add `django` to `fullstack`'s `VALID_COMBINATIONS`. `inferApplicationShape({frontend,backend})` will then infer it; `validateApplicationShape` rejects invalid pairings.
|
|
32
|
+
|
|
33
|
+
**c. Architecture & auth:** Which `architectureProfiles` (`small,medium,large` — all 3 required for frontends/backends) and which `authenticationModels` (`public,undecided,supabase,session,oidc,laravel-session,sanctum-spa,laravel-oidc`)? Example: `supabase` → `supabase`, `springboot` website → `session`, `springboot` multi-client → `oidc`.
|
|
34
|
+
|
|
35
|
+
**d. Playbooks & concerns:** Which 5 facets does the stack need? Every stack needs `architecture.md, structure.md, runtime.md, security.md, testing.md` under `playbooks/stack/<id>/`. Which optional concerns (validation `zod`, state `zustand`, etc.) will it surface? Each concern has one home — don't duplicate prose in `AGENTS.md`.
|
|
36
|
+
|
|
37
|
+
**e. Dependencies & env:** Which npm/composer package *names* and *scripts* will this stack generate, and which env var *names* (semantic, no prefix)? Example: `deps: ["next","react"]`, `env: ["DATABASE_URL"]`, `clientEnv: ["API_URL"]` — the engine will prefix `API_URL` once (`NEXT_PUBLIC_API_URL`). Never put a version here.
|
|
38
|
+
|
|
39
|
+
If a new stack needs a runtime (`node`, `java`, `php`), add its version to `library/tested-versions.json` (current profile), not to the definition.
|
|
40
|
+
|
|
41
|
+
### 1. Declare capability in `library/` (no executable logic yet)
|
|
42
|
+
|
|
43
|
+
- Add `library/stacks/<id>/definition.json`:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"id": "django",
|
|
48
|
+
"kind": "backend",
|
|
49
|
+
"label": "Django (Python)",
|
|
50
|
+
"appliesTo": { "frontend": ["nextjs","react","no-frontend"] },
|
|
51
|
+
"architectureProfiles": ["small","medium","large"],
|
|
52
|
+
"playbooks": ["stack/django/architecture.md","stack/django/structure.md","stack/django/runtime.md","stack/django/security.md","stack/django/testing.md"],
|
|
53
|
+
"deps": ["django"],
|
|
54
|
+
"env": ["DATABASE_URL"],
|
|
55
|
+
"clientEnv": [],
|
|
56
|
+
"concerns": [{ "id": "validation", "required": false, "when": "runtime validation needed", "sections": ["Zod for Runtime Validation"] }]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `deps`/`devDeps` are **names only** — never versions or ranges. Exact versions live only in `library/tested-versions.json`.
|
|
61
|
+
- `env` are semantic names (`DATABASE_URL`); `clientEnv` subset gets the frontend prefix (`NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`).
|
|
62
|
+
|
|
63
|
+
- Add `playbooks/stack/<id>/{architecture,structure,runtime,security,testing}.md` — the five facets. Keep each file focused; route concerns via definition `concerns[]`, not duplication. Check `docs/CONTENT_MODEL.md` — every concern section must match a Markdown heading (numbered headings normalized).
|
|
64
|
+
|
|
65
|
+
- Run: `node checks/check-library.js` — catches duplicate ids, unknown dep names, `clientEnv` not in `env`, missing labels, heading mismatches.
|
|
66
|
+
|
|
67
|
+
### 2. Create stack directory (one place, owns everything specific to that stack)
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
src/stacks/<frontends|backends>/<id>/
|
|
71
|
+
index.js # defineStackAdapter({ id, kind, label, compatibleWith, capabilities, contributes })
|
|
72
|
+
create-files.js # (answers, context) -> FileMap — ALL files this stack generates (pure function)
|
|
73
|
+
dependencies.js # optional: { deps: [], devDeps: [], scripts: {} } — names/scripts only
|
|
74
|
+
environment.js # optional: ["API_URL","DATABASE_URL"] — semantic env names
|
|
75
|
+
auth/ # for Laravel-style stacks: session.js, sanctum.js, oidc.js, public.js
|
|
76
|
+
ui/ # for Laravel-style stacks: blade.js, livewire.js, inertia-react.js, shared.js
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use `src/stacks/backends/laravel/` as the reference (13 files, 363-line `create-files.js` + `auth/` + `ui/` + `composer.js` + `architecture.js`). `src/stacks/frontends/nextjs/` is the minimal vertical.
|
|
80
|
+
|
|
81
|
+
`create-files.js` receives a read-only context from `src/stacks/available-stacks.js` + `src/engine/load-library.js` — it returns contributions, it never writes files or installs tools directly.
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
// src/stacks/backends/django/index.js
|
|
85
|
+
import { defineStackAdapter } from '../../rules.js'
|
|
86
|
+
export const djangoAdapter = defineStackAdapter({
|
|
87
|
+
id: 'django', kind: 'backend', label: 'Django (Python)',
|
|
88
|
+
compatibleWith: { frontend: ['nextjs','react','no-frontend'] },
|
|
89
|
+
capabilities: { applicationShapes: ['fullstack','api'], architectureProfiles: ['small','medium','large'], authenticationModels: ['public','undecided','session'] },
|
|
90
|
+
contributes: {
|
|
91
|
+
environment: () => ['DATABASE_URL'],
|
|
92
|
+
install: () => [{ cwd: 'backend', command: 'pip', args: ['install','-r','requirements.txt'] }],
|
|
93
|
+
docker: () => [{ template: 'django', developmentPath: 'Dockerfile.dev' }],
|
|
94
|
+
ci: () => [{ template: 'django', path: '.github/workflows/ci-backend.yml' }],
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. Register once (explicit, no scanning)
|
|
100
|
+
|
|
101
|
+
`src/stacks/available-stacks.js` — add one import and one array entry:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import { djangoAdapter } from './backends/django/index.js'
|
|
105
|
+
export const stackRegistry = createStackRegistry([..., djangoAdapter])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This is the **only** list of available adapters. Adding a file never silently activates a stack.
|
|
109
|
+
|
|
110
|
+
### 4. Add tests and matrix entries
|
|
111
|
+
|
|
112
|
+
- `tests/stacks/<id>/` — at least: files exist, env prefix, large-arch boundary, auth metadata.
|
|
113
|
+
- `checks/check-compatibility.js` — add `'<frontend>-<backend>'` to `cases[]` and to `smokeSelections[]`.
|
|
114
|
+
- `templates/` — add `docker/dockerfile/django.dev.dockerfile`, `templates/ci/django.yml` etc. as static templates.
|
|
115
|
+
|
|
116
|
+
### 5. Verify (focused, not full matrix every time)
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm test
|
|
120
|
+
node checks/check-library.js
|
|
121
|
+
node checks/check-compatibility.js --scope=stack --stack=<id> | head
|
|
122
|
+
npm run verify:generated -- --profile=$(jq -r .defaultProfile library/tested-versions.json) --case=<frontend>-<backend> --architecture=medium --authentication=yes
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- `verify:generated` compares byte-identical output via `generateProject` vs legacy shim for your new stack.
|
|
126
|
+
- Run `npm run matrix:smoke` (10 smoke projects) before any `feature -> dev` PR.
|
|
127
|
+
- Full matrix `npm run matrix:full` (every profile x stack x arch x auth, with install/build/Maven/Expo/Compose checks) is only required on `dev -> main` promotion per `docs/CI_STRATEGY.md`.
|
|
128
|
+
|
|
129
|
+
### 6. Docs (only if ownership changed)
|
|
130
|
+
|
|
131
|
+
Update `docs/ARCHITECTURE.md` only if source ownership or the generation pipeline changed — not for every stack. If you added concerns/playbooks, verify `docs/CONTENT_MODEL.md` checklist (concern has one home, `RULES.md` sections resolve).
|
|
132
|
+
|
|
133
|
+
## Testing and PRs
|
|
134
|
+
|
|
135
|
+
- Required checks per `docs/CI_STRATEGY.md`:
|
|
136
|
+
- `feature -> dev`: `quality` + `compatibility-gate` (repository tests + 10 smoke projects)
|
|
137
|
+
- `dev -> main`: `quality` + `compatibility-gate` (every profile x stack x arch x applicable auth)
|
|
138
|
+
|
|
139
|
+
- Contribution history: use merge commits for feature branches so history remains visible. Do not require individual matrix job names; require the stable `compatibility-gate` aggregator.
|
|
140
|
+
|
|
141
|
+
- `tests/architecture-boundaries.test.js` enforces `src/engine` not importing `cli`/`stacks/frontends|backends` and `src/stacks` not importing `cli`/`engine` (except `shared/scaffold` allowlist). `tests/architecture/no-lib-legacy.test.js` bans `src/** -> lib/**` imports and one-line wrapper regressions (no `lib/` exists).
|
|
142
|
+
|
|
143
|
+
## Version ownership
|
|
144
|
+
|
|
145
|
+
Exact versions belong only to `library/tested-versions.json`. Never add a version to a `definition.json` or a `create-files.js`. `packageVersion(profile, name, capability)` and `composerPackageVersion(profile, name)` are the only version sources; they reject ranges and unknown package requests. See `docs/DEPENDENCY_MAINTENANCE.md` for current/previous profile promotion and Renovate flow.
|
|
146
|
+
|
|
147
|
+
## Docs workflow
|
|
148
|
+
|
|
149
|
+
- `AGENTS.md` — tiny always-on contract; keep short.
|
|
150
|
+
- `RULES.md` — lazy index `concern -> playbook §`; generated per project, read only what you touch.
|
|
151
|
+
- `playbooks/` — reusable standards; add the five facets per stack (`architecture.md`, `structure.md`, `runtime.md`, `security.md`, `testing.md`).
|
|
152
|
+
- `docs/guides/` inside generated projects — architecture, API, setup, deployment for *this* product.
|
|
153
|
+
|
|
154
|
+
Do not advertise a capability solely because a playbook mentions it. A generated capability must have executable configuration and contract coverage. For questions, read `docs/ARCHITECTURE.md` first, then this file.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Dependency maintenance
|
|
2
2
|
|
|
3
|
-
`
|
|
3
|
+
`library/tested-versions.json` is the only owner of versions emitted into generated applications. Stack and concern definitions declare package names and capabilities, never ranges or versions. Direct npm dependencies are generated as exact versions, and consumers commit the lockfile created by their first install.
|
|
4
4
|
|
|
5
5
|
## Profile policy
|
|
6
6
|
|
package/index.js
CHANGED
|
@@ -1,431 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
import ora from 'ora'
|
|
5
|
-
import path from 'path'
|
|
6
|
-
import { fileURLToPath } from 'url'
|
|
7
|
-
import { spawnSync } from 'node:child_process'
|
|
8
|
-
import { generateProject } from './lib/generator.js'
|
|
9
|
-
import { loadCompatibility } from './lib/compatibility.js'
|
|
10
|
-
import { printDoctor } from './lib/doctor.js'
|
|
11
|
-
import { configurationDecisionChoices, promptWithBack } from './lib/interview.js'
|
|
12
|
-
import { projectLocationNotice } from './lib/project-location.js'
|
|
13
|
-
import { w1nBanner } from './lib/banner.js'
|
|
14
|
-
import {
|
|
15
|
-
loadCatalog, resolveStack,
|
|
16
|
-
stylingChoicesFor, architectureChoicesFor,
|
|
17
|
-
} from './lib/catalog.js'
|
|
18
|
-
import {
|
|
19
|
-
APPLICATION_SHAPES,
|
|
20
|
-
applicationShapeChoices,
|
|
21
|
-
backendChoicesForShape,
|
|
22
|
-
frontendChoicesForShape,
|
|
23
|
-
} from './lib/application-shapes.js'
|
|
24
|
-
import { laravelUiPromptContribution, laravelUis } from './lib/stacks/laravel/ui/index.js'
|
|
2
|
+
import { runCli } from './src/cli/main.js'
|
|
25
3
|
|
|
26
|
-
|
|
27
|
-
const cliArgs = process.argv.slice(2)
|
|
28
|
-
const profileArg = cliArgs.find((arg) => arg.startsWith('--profile='))?.split('=')[1]
|
|
29
|
-
const shapeArg = cliArgs.find((arg) => arg.startsWith('--shape='))?.split('=')[1]
|
|
30
|
-
const frontendValue = cliArgs.find((arg) => arg.startsWith('--frontend='))?.split('=')[1]
|
|
31
|
-
const backendArg = cliArgs.find((arg) => arg.startsWith('--backend='))?.split('=')[1]
|
|
32
|
-
const frontendAliases = { vite: 'react', expo: 'react-native', none: 'no-frontend' }
|
|
33
|
-
const frontendArg = frontendAliases[frontendValue] || frontendValue
|
|
34
|
-
const architectureArg = cliArgs.find((arg) => arg.startsWith('--architecture='))?.split('=')[1]
|
|
35
|
-
const authenticationArg = cliArgs.find((arg) => arg.startsWith('--authentication='))?.split('=')[1]
|
|
36
|
-
const authAudienceArg = cliArgs.find((arg) => arg.startsWith('--auth-audience='))?.split('=')[1]
|
|
37
|
-
const laravelUiArg = cliArgs.find((arg) => arg.startsWith('--laravel-ui='))?.split('=')[1]
|
|
38
|
-
if (shapeArg && !APPLICATION_SHAPES[shapeArg]) {
|
|
39
|
-
throw new Error('--shape must be fullstack, separate, api, mobile, or frontend')
|
|
40
|
-
}
|
|
41
|
-
if (architectureArg && !['small', 'medium', 'large'].includes(architectureArg)) {
|
|
42
|
-
throw new Error('--architecture must be small, medium, or large')
|
|
43
|
-
}
|
|
44
|
-
if (authenticationArg && !['yes', 'not-yet', 'none'].includes(authenticationArg)) {
|
|
45
|
-
throw new Error('--authentication must be yes, not-yet, or none')
|
|
46
|
-
}
|
|
47
|
-
if (authAudienceArg && !['website', 'multi-client'].includes(authAudienceArg)) {
|
|
48
|
-
throw new Error('--auth-audience must be website or multi-client')
|
|
49
|
-
}
|
|
50
|
-
if (laravelUiArg && !laravelUis.some((ui) => ui.id === laravelUiArg)) {
|
|
51
|
-
throw new Error('--laravel-ui must be blade, livewire, or inertia-react')
|
|
52
|
-
}
|
|
53
|
-
const wantsInstall = cliArgs.includes('--install')
|
|
54
|
-
const skipsInstall = cliArgs.includes('--no-install')
|
|
55
|
-
if (wantsInstall && skipsInstall) throw new Error('Use either --install or --no-install, not both')
|
|
56
|
-
const { profile } = await loadCompatibility(
|
|
57
|
-
path.join(__dirname, 'compatibility/profiles.json'),
|
|
58
|
-
profileArg,
|
|
59
|
-
)
|
|
60
|
-
if (cliArgs[0] === 'doctor' || cliArgs.includes('--doctor')) {
|
|
61
|
-
printDoctor(profile)
|
|
62
|
-
process.exit(0)
|
|
63
|
-
}
|
|
64
|
-
const catalog = await loadCatalog(path.join(__dirname, 'playbooks'), profile)
|
|
65
|
-
|
|
66
|
-
// ─── Banner ──────────────────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
console.log('')
|
|
69
|
-
console.log(w1nBanner())
|
|
70
|
-
console.log('')
|
|
71
|
-
console.log(chalk.gray(' Production-ready project scaffolding'))
|
|
72
|
-
console.log(chalk.gray(` Compatibility profile: ${profile.id} (${profile.status})`))
|
|
73
|
-
console.log('')
|
|
74
|
-
|
|
75
|
-
// ─── Interview ───────────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
const questions = [
|
|
78
|
-
{
|
|
79
|
-
type: 'list',
|
|
80
|
-
name: 'applicationShape',
|
|
81
|
-
message: 'What kind of application are you building?',
|
|
82
|
-
choices: applicationShapeChoices(),
|
|
83
|
-
default: 'fullstack',
|
|
84
|
-
when: () => !shapeArg,
|
|
85
|
-
},
|
|
86
|
-
// ── Always-on ──────────────────────────────────────────────────────────
|
|
87
|
-
{
|
|
88
|
-
type: 'input',
|
|
89
|
-
name: 'projectName',
|
|
90
|
-
message: 'Project name?',
|
|
91
|
-
default: 'my-project',
|
|
92
|
-
validate: (v) => {
|
|
93
|
-
if (!v.trim()) return 'Project name is required'
|
|
94
|
-
if (!/^[a-z0-9-]+$/.test(v)) return 'Use lowercase letters, numbers, and hyphens only'
|
|
95
|
-
return true
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
type: 'input',
|
|
100
|
-
name: 'projectDescription',
|
|
101
|
-
message: 'One-line description?',
|
|
102
|
-
default: 'A new application',
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
// ── Stack selection (catalog-driven) ───────────────────────────────────
|
|
106
|
-
{
|
|
107
|
-
type: 'list',
|
|
108
|
-
name: 'frontend',
|
|
109
|
-
message: 'Which application framework or frontend?',
|
|
110
|
-
choices: (a) => frontendChoicesForShape(shapeArg || a.applicationShape, catalog),
|
|
111
|
-
when: () => !frontendArg,
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
type: 'list',
|
|
115
|
-
name: 'backend',
|
|
116
|
-
message: 'Which backend or data service?',
|
|
117
|
-
choices: (a) => backendChoicesForShape(shapeArg || a.applicationShape, a.frontend, catalog),
|
|
118
|
-
when: () => !backendArg,
|
|
119
|
-
},
|
|
120
|
-
...laravelUiPromptContribution(laravelUiArg).questions,
|
|
121
|
-
|
|
122
|
-
// ── Styling: only shown when frontend has >1 option (catalog-driven) ───
|
|
123
|
-
{
|
|
124
|
-
type: 'list',
|
|
125
|
-
name: 'styling',
|
|
126
|
-
message: 'Styling approach?',
|
|
127
|
-
choices: (a) => stylingChoicesFor(catalog, a.frontend),
|
|
128
|
-
when: (a) => stylingChoicesFor(catalog, a.frontend).length > 1,
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
// ── One architecture profile, interpreted natively by every stack ──────
|
|
132
|
-
{
|
|
133
|
-
type: 'list',
|
|
134
|
-
name: 'architecture',
|
|
135
|
-
message: 'Architecture? (Medium is recommended for most long-term applications)',
|
|
136
|
-
choices: (a) => {
|
|
137
|
-
const supported = architectureChoicesFor(catalog, a.frontend, a.backend)
|
|
138
|
-
return [
|
|
139
|
-
{ name: 'Medium (Recommended) — clear feature, service, and data boundaries', value: 'medium' },
|
|
140
|
-
{ name: 'Small — fewer layers for prototypes and simple applications', value: 'small' },
|
|
141
|
-
{ name: 'Large — enforced boundaries for complex domains and larger teams', value: 'large' },
|
|
142
|
-
].filter((choice) => supported.includes(choice.value))
|
|
143
|
-
},
|
|
144
|
-
default: 'medium',
|
|
145
|
-
when: () => !architectureArg,
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
// ── Authentication intent, expressed without protocol jargon ───────────
|
|
149
|
-
{
|
|
150
|
-
type: 'list',
|
|
151
|
-
name: 'authentication',
|
|
152
|
-
message: 'Does your application need user login?',
|
|
153
|
-
choices: (a) => {
|
|
154
|
-
const choices = []
|
|
155
|
-
if (a.backend === 'supabase' || a.backend === 'springboot' || a.backend === 'laravel') {
|
|
156
|
-
choices.push({ name: 'Yes — generate authentication appropriate for this stack', value: 'yes' })
|
|
157
|
-
}
|
|
158
|
-
choices.push(
|
|
159
|
-
{ name: 'Not yet (Recommended) — add guidance without pretending login exists', value: 'not-yet' },
|
|
160
|
-
{ name: 'No — this application is intentionally public and has no user accounts', value: 'none' },
|
|
161
|
-
)
|
|
162
|
-
return choices
|
|
163
|
-
},
|
|
164
|
-
default: 'not-yet',
|
|
165
|
-
when: () => !authenticationArg,
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
type: 'list',
|
|
169
|
-
name: 'authAudience',
|
|
170
|
-
message: 'Where will users access the application?',
|
|
171
|
-
choices: [
|
|
172
|
-
{ name: 'Website only — use a secure server-managed browser session', value: 'website' },
|
|
173
|
-
{ name: 'Website and mobile — use a trusted identity provider for every client', value: 'multi-client' },
|
|
174
|
-
],
|
|
175
|
-
default: 'website',
|
|
176
|
-
when: (a) => ['springboot', 'laravel'].includes(a.backend) && a.frontend !== 'laravel-ui' && (authenticationArg || a.authentication) === 'yes' && !authAudienceArg,
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
// ── Testing ────────────────────────────────────────────────────────────
|
|
180
|
-
{
|
|
181
|
-
type: 'list',
|
|
182
|
-
name: 'testing',
|
|
183
|
-
message: 'Testing setup?',
|
|
184
|
-
choices: (a) => {
|
|
185
|
-
const fe = catalog.byId[a.frontend]
|
|
186
|
-
if (fe?.platform === 'mobile') {
|
|
187
|
-
return [
|
|
188
|
-
{ name: 'Basic (Jest + React Native Testing Library)', value: 'basic' },
|
|
189
|
-
{ name: 'None', value: 'none' },
|
|
190
|
-
]
|
|
191
|
-
}
|
|
192
|
-
return [
|
|
193
|
-
{ name: 'Full (Vitest + React Testing Library + Playwright)', value: 'full' },
|
|
194
|
-
{ name: 'Basic (Vitest + React Testing Library)', value: 'basic' },
|
|
195
|
-
{ name: 'None', value: 'none' },
|
|
196
|
-
]
|
|
197
|
-
},
|
|
198
|
-
default: (a) => {
|
|
199
|
-
const fe = catalog.byId[a.frontend]
|
|
200
|
-
return fe?.platform === 'mobile' ? 'basic' : 'full'
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
// ── DevOps extras ──────────────────────────────────────────────────────
|
|
205
|
-
{
|
|
206
|
-
type: 'confirm',
|
|
207
|
-
name: 'docker',
|
|
208
|
-
message: 'Include Docker?',
|
|
209
|
-
default: true,
|
|
210
|
-
when: (a) => {
|
|
211
|
-
const fe = catalog.byId[a.frontend]
|
|
212
|
-
const be = catalog.byId[a.backend]
|
|
213
|
-
return (be?.needsDocker ?? fe?.needsDocker ?? false)
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
type: 'confirm',
|
|
218
|
-
name: 'makefile',
|
|
219
|
-
message: 'Include Makefile?',
|
|
220
|
-
default: true,
|
|
221
|
-
when: (a) => {
|
|
222
|
-
// Makefile is useful for web stacks; less relevant for bare mobile
|
|
223
|
-
const fe = catalog.byId[a.frontend]
|
|
224
|
-
return fe?.platform !== 'mobile'
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
type: 'confirm',
|
|
229
|
-
name: 'githubActions',
|
|
230
|
-
message: 'Include GitHub Actions CI?',
|
|
231
|
-
default: true,
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
type: 'confirm',
|
|
235
|
-
name: 'installDependencies',
|
|
236
|
-
message: 'Install project dependencies and create the lockfile now?',
|
|
237
|
-
default: true,
|
|
238
|
-
when: () => !wantsInstall && !skipsInstall,
|
|
239
|
-
},
|
|
240
|
-
|
|
241
|
-
// ── Spring Boot specific ───────────────────────────────────────────────
|
|
242
|
-
{
|
|
243
|
-
type: 'input',
|
|
244
|
-
name: 'packageName',
|
|
245
|
-
message: 'Java package name? (e.g. com.yourname)',
|
|
246
|
-
default: 'com.app',
|
|
247
|
-
when: (a) => a.backend === 'springboot',
|
|
248
|
-
validate: (v) => {
|
|
249
|
-
if (!v.trim()) return 'Package name is required'
|
|
250
|
-
if (!/^[a-z]+(\.[a-z]+)+$/.test(v)) return 'Use format: com.yourname'
|
|
251
|
-
return true
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
|
|
255
|
-
// ── Optional concerns (filtered to current stack) ──────────────────────
|
|
256
|
-
{
|
|
257
|
-
type: 'checkbox',
|
|
258
|
-
name: 'expectedConcerns',
|
|
259
|
-
message: 'Expected optional concerns? (advisory only — all stay available)',
|
|
260
|
-
choices: (a) => {
|
|
261
|
-
const stack = resolveStack({ ...a, styling: a.styling || catalog.byId[a.frontend]?.stylingOptions?.[0] }, catalog)
|
|
262
|
-
const opts = new Set()
|
|
263
|
-
for (const c of stack.concerns) if (!c.required) opts.add(c.id)
|
|
264
|
-
return [...opts].map((id) => ({ name: id, value: id }))
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
]
|
|
268
|
-
let answers = {
|
|
269
|
-
applicationShape: shapeArg,
|
|
270
|
-
frontend: frontendArg,
|
|
271
|
-
backend: backendArg,
|
|
272
|
-
}
|
|
273
|
-
let stack
|
|
274
|
-
while (true) {
|
|
275
|
-
answers = await promptWithBack(inquirer, questions, answers)
|
|
276
|
-
answers.compatibilityProfile = profile.id
|
|
277
|
-
answers.applicationShape = shapeArg || answers.applicationShape
|
|
278
|
-
answers.architecture = architectureArg || answers.architecture || 'medium'
|
|
279
|
-
answers.authentication = authenticationArg || answers.authentication || 'not-yet'
|
|
280
|
-
answers.authAudience = authAudienceArg || answers.authAudience || (catalog.byId[answers.frontend]?.platform === 'mobile' ? 'multi-client' : 'website')
|
|
281
|
-
answers.laravelUi = laravelUiArg || answers.laravelUi || (answers.frontend === 'laravel-ui' ? 'blade' : undefined)
|
|
282
|
-
if (wantsInstall) answers.installDependencies = true
|
|
283
|
-
if (skipsInstall) answers.installDependencies = false
|
|
284
|
-
|
|
285
|
-
// ── Auto-resolve docker for stacks that need it ───────────────────────────────
|
|
286
|
-
if (resolveStack({ ...answers, styling: answers.styling || catalog.byId[answers.frontend]?.stylingOptions?.[0] || 'tailwind' }, catalog).needsDocker) {
|
|
287
|
-
answers.docker = answers.docker ?? true
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
stack = resolveStack({ ...answers, styling: answers.styling || catalog.byId[answers.frontend]?.stylingOptions?.[0] || 'tailwind' }, catalog)
|
|
291
|
-
|
|
292
|
-
// ─── Confirm ─────────────────────────────────────────────────────────────────
|
|
293
|
-
|
|
294
|
-
console.log('')
|
|
295
|
-
console.log(chalk.bold(' Summary'))
|
|
296
|
-
console.log(chalk.gray(' ───────────────────────────'))
|
|
297
|
-
console.log(` ${chalk.cyan('Name:')} ${answers.projectName}`)
|
|
298
|
-
console.log(` ${chalk.cyan('Stack:')} ${stack.label}`)
|
|
299
|
-
console.log(` ${chalk.cyan('Shape:')} ${APPLICATION_SHAPES[stack.applicationShape].label}`)
|
|
300
|
-
console.log(` ${chalk.cyan('Platform:')} ${stack.platform}`)
|
|
301
|
-
if (stack.styleId) {
|
|
302
|
-
console.log(` ${chalk.cyan('Styling:')} ${catalog.byId[stack.styleId]?.label || stack.styleId}`)
|
|
303
|
-
}
|
|
304
|
-
if (answers.laravelUi) console.log(` ${chalk.cyan('Laravel UI:')} ${answers.laravelUi}`)
|
|
305
|
-
console.log(` ${chalk.cyan('Architecture:')} ${stack.architecture[0].toUpperCase()}${stack.architecture.slice(1)}`)
|
|
306
|
-
console.log(` ${chalk.cyan('Authentication:')} ${stack.authentication}`)
|
|
307
|
-
console.log(` ${chalk.cyan('Testing:')} ${answers.testing}`)
|
|
308
|
-
if (stack.platform !== 'mobile') {
|
|
309
|
-
console.log(` ${chalk.cyan('Docker:')} ${answers.docker ? 'yes' : 'no'}`)
|
|
310
|
-
console.log(` ${chalk.cyan('Makefile:')} ${answers.makefile ? 'yes' : 'no'}`)
|
|
311
|
-
}
|
|
312
|
-
console.log(` ${chalk.cyan('CI/CD:')} ${answers.githubActions ? 'yes' : 'no'}`)
|
|
313
|
-
console.log(` ${chalk.cyan('Install deps:')} ${answers.installDependencies ? 'yes' : 'no'}`)
|
|
314
|
-
if (answers.packageName) {
|
|
315
|
-
console.log(` ${chalk.cyan('Package:')} ${answers.packageName}`)
|
|
316
|
-
}
|
|
317
|
-
if (stack.constraints.length) {
|
|
318
|
-
console.log('')
|
|
319
|
-
console.log(chalk.gray(' Key constraints:'))
|
|
320
|
-
for (const rule of stack.constraints.slice(0, 3)) {
|
|
321
|
-
console.log(chalk.gray(` • ${rule}`))
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
console.log('')
|
|
325
|
-
|
|
326
|
-
const { decision } = await inquirer.prompt([{
|
|
327
|
-
type: 'list', name: 'decision', message: 'Ready?', choices: configurationDecisionChoices(),
|
|
328
|
-
}])
|
|
329
|
-
|
|
330
|
-
if (decision === 'back') {
|
|
331
|
-
continue
|
|
332
|
-
}
|
|
333
|
-
if (decision === 'cancel') {
|
|
334
|
-
console.log(chalk.yellow('\n Cancelled.\n'))
|
|
335
|
-
process.exit(0)
|
|
336
|
-
}
|
|
337
|
-
break
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ─── Generate ────────────────────────────────────────────────────────────────
|
|
341
|
-
|
|
342
|
-
console.log('')
|
|
343
|
-
const spinner = ora('Scaffolding project...').start()
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
await generateProject(answers, __dirname)
|
|
347
|
-
spinner.succeed(chalk.green('Project created!'))
|
|
348
|
-
const locationNotice = projectLocationNotice({ cwd: process.cwd(), cliRoot: __dirname, projectName: answers.projectName })
|
|
349
|
-
if (locationNotice) {
|
|
350
|
-
console.log('')
|
|
351
|
-
console.log(chalk.yellow.bold(' Project location note'))
|
|
352
|
-
console.log(chalk.yellow(` ${locationNotice.message}`))
|
|
353
|
-
console.log(chalk.gray(` Created at: ${locationNotice.generatedPath}`))
|
|
354
|
-
console.log(chalk.gray(` Suggested destination: ${locationNotice.suggestedPath}`))
|
|
355
|
-
console.log(chalk.gray(` Linux/macOS: mv "${locationNotice.generatedPath}" "${locationNotice.suggestedPath}"`))
|
|
356
|
-
console.log(chalk.gray(` Windows: cut the generated folder in File Explorer and paste it into your projects folder.`))
|
|
357
|
-
}
|
|
358
|
-
if (answers.installDependencies) {
|
|
359
|
-
const projectRoot = path.join(process.cwd(), answers.projectName)
|
|
360
|
-
const steps = []
|
|
361
|
-
if (stack.backendKey === 'laravel') {
|
|
362
|
-
const laravelRoot = ['laravel-ui', 'no-frontend'].includes(stack.frontendKey) ? projectRoot : path.join(projectRoot, 'backend')
|
|
363
|
-
steps.push({ command: process.platform === 'win32' ? 'composer.bat' : 'composer', args: ['install'], cwd: laravelRoot, retry: `cd ${path.relative(process.cwd(), laravelRoot)} && composer install` })
|
|
364
|
-
}
|
|
365
|
-
const needsNpm = stack.frontendKey !== 'no-frontend' && (stack.frontendKey !== 'laravel-ui' || answers.laravelUi === 'inertia-react')
|
|
366
|
-
if (needsNpm) {
|
|
367
|
-
const npmRoot = stack.frontendKey === 'react' ? path.join(projectRoot, 'frontend') : projectRoot
|
|
368
|
-
steps.push({ command: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: ['install'], cwd: npmRoot, retry: `cd ${path.relative(process.cwd(), npmRoot)} && npm install` })
|
|
369
|
-
}
|
|
370
|
-
const installSpinner = ora('Installing exact dependencies and creating lockfiles...').start()
|
|
371
|
-
const failed = steps.find((step) => spawnSync(step.command, step.args, { cwd: step.cwd, stdio: 'inherit', shell: false }).status !== 0)
|
|
372
|
-
if (failed) {
|
|
373
|
-
installSpinner.warn(chalk.yellow('Project created, but dependency installation did not finish.'))
|
|
374
|
-
console.log(chalk.yellow(` Retry with: ${failed.retry}`))
|
|
375
|
-
} else {
|
|
376
|
-
installSpinner.succeed(chalk.green('Dependencies installed and lockfiles created.'))
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
console.log('')
|
|
380
|
-
console.log(chalk.bold(` Next steps:`))
|
|
381
|
-
console.log(chalk.gray(` cd ${answers.projectName}`))
|
|
382
|
-
|
|
383
|
-
if (stack.backendKey === 'laravel') {
|
|
384
|
-
const laravelDir = ['laravel-ui', 'no-frontend'].includes(stack.frontendKey) ? '' : 'backend/'
|
|
385
|
-
console.log(chalk.gray(` cp ${laravelDir}.env.example ${laravelDir}.env`))
|
|
386
|
-
if (answers.makefile) {
|
|
387
|
-
console.log(chalk.gray(` make setup # first run only`))
|
|
388
|
-
console.log(chalk.gray(` make run # later runs; never rebuilds`))
|
|
389
|
-
} else {
|
|
390
|
-
console.log(chalk.gray(` docker compose build`))
|
|
391
|
-
console.log(chalk.gray(` docker compose up -d`))
|
|
392
|
-
}
|
|
393
|
-
} else if (stack.frontendKey === 'react') {
|
|
394
|
-
if (stack.backendKey === 'springboot') {
|
|
395
|
-
console.log(chalk.gray(` cp .env.example .env # Docker/backend values`))
|
|
396
|
-
}
|
|
397
|
-
console.log(chalk.gray(` cd frontend`))
|
|
398
|
-
console.log(chalk.gray(` cp .env.example .env`))
|
|
399
|
-
if (!answers.installDependencies) console.log(chalk.gray(` npm install`))
|
|
400
|
-
} else {
|
|
401
|
-
console.log(chalk.gray(` cp .env.example ${stack.isMobile ? '.env' : '.env.local'}`))
|
|
402
|
-
if (!answers.installDependencies) console.log(chalk.gray(` npm install`))
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (stack.backendKey === 'laravel') {
|
|
406
|
-
// Laravel commands were printed above because its root differs by application shape.
|
|
407
|
-
} else if (stack.isMobile) {
|
|
408
|
-
console.log(chalk.gray(` npx expo start`))
|
|
409
|
-
} else if (answers.makefile) {
|
|
410
|
-
if (stack.frontendKey === 'react') {
|
|
411
|
-
console.log(chalk.gray(` cd ..`))
|
|
412
|
-
}
|
|
413
|
-
console.log(chalk.gray(` make dev`))
|
|
414
|
-
} else if (stack.backendKey === 'supabase') {
|
|
415
|
-
console.log(chalk.gray(` npm run supabase:start`))
|
|
416
|
-
console.log(chalk.gray(` npm run dev`))
|
|
417
|
-
} else if (stack.frontendKey === 'react') {
|
|
418
|
-
console.log(chalk.gray(` npm run dev`))
|
|
419
|
-
} else {
|
|
420
|
-
console.log(chalk.gray(` docker compose up -d db`))
|
|
421
|
-
console.log(chalk.gray(` npm run dev`))
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
console.log('')
|
|
425
|
-
console.log(chalk.cyan(` Read RULES.md before starting — it maps every playbook for this stack.`))
|
|
426
|
-
console.log('')
|
|
427
|
-
} catch (err) {
|
|
428
|
-
spinner.fail(chalk.red('Failed to generate project'))
|
|
429
|
-
console.error(err)
|
|
430
|
-
process.exit(1)
|
|
431
|
-
}
|
|
4
|
+
await runCli()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Playbook Index
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guidance files are task-routed standards used by generated projects. Their co-located definitions decide when guidance applies and which headings appear in generated `RULES.md`.
|
|
4
4
|
|
|
5
5
|
## Content groups
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ Playbooks are task-routed standards used by generated projects. Their co-located
|
|
|
17
17
|
## Composition
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
|
-
required universal + selected platform
|
|
20
|
+
required universal + selected platform definitions
|
|
21
21
|
+ selected frontend
|
|
22
22
|
+ selected backend/database/migration
|
|
23
23
|
+ selected styling
|
|
@@ -37,10 +37,10 @@ Expo uses React Native `StyleSheet` by default. Web projects can select Tailwind
|
|
|
37
37
|
|
|
38
38
|
## Manifest contract
|
|
39
39
|
|
|
40
|
-
A stack
|
|
40
|
+
A stack definition must declare all three architecture profiles. Any definition may declare:
|
|
41
41
|
|
|
42
42
|
- `id`, `kind`, `label`, and compatibility;
|
|
43
|
-
- dependency names, scripts, templates, and conditional playbooks (versions live only in `
|
|
43
|
+
- dependency names, scripts, templates, and conditional playbooks (versions live only in `library/tested-versions.json`);
|
|
44
44
|
- semantic environment names and client-visible names;
|
|
45
45
|
- stack constraints;
|
|
46
46
|
- required and optional concerns, target playbook, headings, and applicability.
|