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/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.
|
|
@@ -55,4 +55,4 @@ Manifests compose capabilities; `lib/scaffold.js` owns their executable minimum.
|
|
|
55
55
|
- `playbooks/`: only selected reusable guidance.
|
|
56
56
|
- `docs/`: setup, API, architecture, auth, and deployment for the generated product.
|
|
57
57
|
|
|
58
|
-
See `docs/
|
|
58
|
+
See `docs/maintainers/content-model.md` for authoring and deduplication rules and `docs/maintainers/architecture.md` for the generation pipeline.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"stacks": {
|
|
3
|
+
"nextjs": ["src/stacks/frontends/nextjs/", "library/stacks/nextjs/", "templates/agents/nextjs.", "templates/ci/nextjs."],
|
|
4
|
+
"react": ["src/stacks/frontends/react-vite/", "library/stacks/react-vite/", "templates/agents/react-vite.", "templates/ci/vite."],
|
|
5
|
+
"react-native": ["src/stacks/frontends/react-native/", "library/stacks/expo/", "templates/agents/react-native.", "templates/ci/expo."],
|
|
6
|
+
"springboot": ["src/stacks/backends/springboot/", "library/stacks/springboot/", "library/features/flyway/", "templates/ci/springboot."],
|
|
7
|
+
"fastapi": ["src/stacks/backends/fastapi/", "library/stacks/fastapi/", "library/features/sqlalchemy-alembic.md", "templates/ci/fastapi."],
|
|
8
|
+
"laravel": ["src/stacks/backends/laravel/", "library/stacks/laravel/", "library/features/laravel", "templates/ci/laravel."],
|
|
9
|
+
"supabase": ["src/stacks/backends/supabase/", "library/features/supabase/"],
|
|
10
|
+
"postgres": ["src/stacks/backends/postgres/", "library/features/postgresql/", "library/features/prisma/"],
|
|
11
|
+
"none": ["src/stacks/backends/none/"]
|
|
12
|
+
},
|
|
13
|
+
"shared": ["library/tested-versions.json", "lib/", "src/engine/", "src/stacks/shared/", "templates/docker/", "templates/gitignore/", "templates/makefile/", "checks/check-compatibility.js", "checks/check-generated-project.js"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Make commands
|
|
2
|
+
|
|
3
|
+
## Core Rules
|
|
4
|
+
|
|
5
|
+
- Targets wrap documented project commands; they do not hide different behavior.
|
|
6
|
+
- Declare non-file targets with `.PHONY`.
|
|
7
|
+
- Use tabs for recipes and `$(VARIABLE)` for configurable values.
|
|
8
|
+
- Keep the default target safe and read-only; `help` or `check` is preferred.
|
|
9
|
+
- Fail on command errors and preserve the underlying exit status.
|
|
10
|
+
|
|
11
|
+
Use small targets such as `install`, `dev`, `lint`, `test`, `build`, and `check`. Compose them instead of duplicating recipes.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "makefile",
|
|
3
|
+
"kind": "devops",
|
|
4
|
+
"label": "Makefile",
|
|
5
|
+
"playbooks": ["capabilities/devops/makefile/commands.md", "capabilities/devops/makefile/docker.md", "capabilities/devops/makefile/validation.md"],
|
|
6
|
+
"required": false,
|
|
7
|
+
"folders": [],
|
|
8
|
+
"concerns": [
|
|
9
|
+
{ "id": "makefile", "required": true, "playbook": "capabilities/devops/makefile/commands.md", "sections": ["Core Rules"] }
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Make and Docker
|
|
2
|
+
|
|
3
|
+
## Docker Targets
|
|
4
|
+
|
|
5
|
+
- Keep image building separate from container startup.
|
|
6
|
+
- `build` may run `docker compose build`; `run` must not rebuild implicitly.
|
|
7
|
+
- Use the generated Compose file as the source of service names and profiles.
|
|
8
|
+
- Never interpolate secrets into command output.
|
|
9
|
+
- Destructive volume or database targets require explicit names and confirmation.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Make validation
|
|
2
|
+
|
|
3
|
+
## Validation Targets
|
|
4
|
+
|
|
5
|
+
`check` should compose the same lint, typecheck, test, and build commands used by CI. Keep focused targets independently runnable, and avoid suppressing failures with leading `-`, `|| true`, or unconditional success messages.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SQLAlchemy and Alembic
|
|
2
|
+
|
|
3
|
+
## Session Ownership
|
|
4
|
+
|
|
5
|
+
Use SQLAlchemy 2 async sessions with `asyncpg`. Services own session and transaction
|
|
6
|
+
boundaries spanning multiple writes or read-modify-write behavior. Routers never manage
|
|
7
|
+
sessions directly; they receive a session dependency from shared core infrastructure.
|
|
8
|
+
|
|
9
|
+
Database unique/check/foreign-key constraints are the final concurrency boundary; map
|
|
10
|
+
expected constraint conflicts to stable application errors.
|
|
11
|
+
|
|
12
|
+
## Migrations
|
|
13
|
+
|
|
14
|
+
Alembic owns all schema changes. Generate revisions explicitly and apply them with
|
|
15
|
+
`alembic upgrade head` from the backend directory. Application startup must not
|
|
16
|
+
automatically migrate production databases. Verify migration configuration and upgrade
|
|
17
|
+
checks in CI before container builds.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Zod validation errors
|
|
2
|
+
|
|
3
|
+
## Safe Validation Errors
|
|
4
|
+
|
|
5
|
+
Convert issues to stable field/code structures at the boundary. Do not expose internal paths, secrets, provider payloads, or raw exception text. Log diagnostic context server-side with request correlation and privacy filtering.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Testing Zod schemas
|
|
2
|
+
|
|
3
|
+
## Schema Tests
|
|
4
|
+
|
|
5
|
+
Test valid boundaries, missing required values, wrong types, limits, unknown keys, and security-sensitive malformed input. Prefer small representative fixtures and assert stable error codes rather than complete library messages.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Runtime validation with Zod
|
|
2
|
+
|
|
3
|
+
## Agent Quick Reference
|
|
4
|
+
|
|
5
|
+
Parse untrusted values once at their owning boundary, use the inferred type internally, and return safe structured errors.
|
|
6
|
+
|
|
7
|
+
## Schema First, Always
|
|
8
|
+
|
|
9
|
+
Define the runtime contract before consuming external data. Use strict schemas for requests, environment variables, persisted data, and provider responses. Apply coercion only when the transport contract explicitly permits it.
|
|
10
|
+
|
|
11
|
+
## Zod Schema Placement
|
|
12
|
+
|
|
13
|
+
Place a schema at the boundary that owns the contract. Feature schemas stay with the feature; shared protocol schemas live in a dependency-neutral contract module. Export inferred types rather than duplicating shapes.
|
|
14
|
+
|
|
15
|
+
## Web Form (React Hook Form)
|
|
16
|
+
|
|
17
|
+
Use the schema resolver at submission boundaries, show field errors accessibly, and validate again on the server. Client validation improves feedback but never establishes trust.
|
|
18
|
+
|
|
19
|
+
## React Native Form (React Hook Form + Controller)
|
|
20
|
+
|
|
21
|
+
Wrap controlled native inputs with `Controller`, preserve accessible labels and errors, and validate the same transport contract again at the trusted server boundary.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "css-modules",
|
|
3
|
+
"kind": "styling",
|
|
4
|
+
"label": "CSS Modules",
|
|
5
|
+
"playbooks": ["styling/css-modules/theme.md", "styling/css-modules/responsive.md"],
|
|
6
|
+
"required": false,
|
|
7
|
+
"folders": [],
|
|
8
|
+
"concerns": [
|
|
9
|
+
{ "id": "styling-theme", "required": true, "playbook": "styling/css-modules/theme.md", "sections": ["Dark Mode with CSS Variables"] },
|
|
10
|
+
{ "id": "styling-responsive", "required": true, "playbook": "styling/css-modules/responsive.md", "sections": ["Responsive in Module File"] }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# CSS Modules responsive layout
|
|
2
|
+
|
|
3
|
+
## Responsive in Module File
|
|
4
|
+
|
|
5
|
+
Keep component breakpoints beside the component styles. Start with the narrow layout, add only measured breakpoints, and prefer container-aware layout when the component is reusable. Avoid global selectors and viewport assumptions inside modules.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# CSS Modules theming
|
|
2
|
+
|
|
3
|
+
## Dark Mode with CSS Variables
|
|
4
|
+
|
|
5
|
+
Define semantic color and spacing variables at the application theme boundary. Switch variable values through the selected theme mechanism; components consume semantic tokens and must not duplicate light/dark selectors.
|
|
6
|
+
|
|
7
|
+
Respect system preference by default, preserve explicit user choice, and maintain accessible contrast in every supported theme.
|