create-win-project 1.3.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 +21 -0
- package/README.md +187 -0
- package/ci/expo.yml +28 -0
- package/ci/laravel.yml +49 -0
- package/ci/nextjs.yml +29 -0
- package/ci/springboot.yml +33 -0
- package/ci/vite.yml +36 -0
- package/compatibility/profiles.json +237 -0
- package/docs/ARCHITECTURE.md +118 -0
- package/docs/CONTENT_MODEL.md +36 -0
- package/docs/DEPENDENCY_MAINTENANCE.md +31 -0
- package/index.js +431 -0
- package/lib/application-shapes.js +95 -0
- package/lib/banner.js +45 -0
- package/lib/catalog.js +343 -0
- package/lib/compatibility.js +116 -0
- package/lib/constants.js +3 -0
- package/lib/doctor.js +45 -0
- package/lib/files.js +158 -0
- package/lib/generator.js +366 -0
- package/lib/interview.js +77 -0
- package/lib/laravel-scaffold.js +3 -0
- package/lib/playbooks.js +172 -0
- package/lib/project-location.js +18 -0
- package/lib/scaffold.js +467 -0
- package/lib/stacks/context.js +27 -0
- package/lib/stacks/contract.js +142 -0
- package/lib/stacks/index.js +8 -0
- package/lib/stacks/laravel/architecture.js +27 -0
- package/lib/stacks/laravel/auth/oidc.js +13 -0
- package/lib/stacks/laravel/auth/public.js +28 -0
- package/lib/stacks/laravel/auth/sanctum.js +14 -0
- package/lib/stacks/laravel/auth/session.js +17 -0
- package/lib/stacks/laravel/composer.js +50 -0
- package/lib/stacks/laravel/generate.js +362 -0
- package/lib/stacks/laravel/index.js +18 -0
- package/lib/stacks/laravel/ui/blade.js +15 -0
- package/lib/stacks/laravel/ui/index.js +29 -0
- package/lib/stacks/laravel/ui/inertia-react.js +35 -0
- package/lib/stacks/laravel/ui/livewire.js +39 -0
- package/lib/stacks/laravel/ui/shared.js +14 -0
- package/lib/stacks/registry.js +42 -0
- package/lib/stacks/shared/contributions.js +25 -0
- package/lib/template.js +62 -0
- package/package.json +49 -0
- package/playbooks/INDEX.md +58 -0
- package/playbooks/capabilities/auth/oidc-resource-server.md +15 -0
- package/playbooks/capabilities/auth/spring-session.md +15 -0
- package/playbooks/capabilities/ci/github-actions.manifest.json +10 -0
- package/playbooks/capabilities/ci/github-actions.md +61 -0
- package/playbooks/capabilities/docker/docker.manifest.json +11 -0
- package/playbooks/capabilities/docker/overview.md +55 -0
- package/playbooks/capabilities/flyway/environments.md +5 -0
- package/playbooks/capabilities/flyway/flyway.manifest.json +12 -0
- package/playbooks/capabilities/flyway/migrations.md +7 -0
- package/playbooks/capabilities/flyway/testing.md +5 -0
- package/playbooks/capabilities/laravel/database.md +8 -0
- package/playbooks/capabilities/laravel/migrations.md +15 -0
- package/playbooks/capabilities/laravel/observability.md +8 -0
- package/playbooks/capabilities/laravel/oidc-resource-server.md +10 -0
- package/playbooks/capabilities/laravel/queues.md +8 -0
- package/playbooks/capabilities/laravel/sanctum-spa.md +8 -0
- package/playbooks/capabilities/laravel/scheduler.md +7 -0
- package/playbooks/capabilities/laravel/session-auth.md +8 -0
- package/playbooks/capabilities/laravel/storage-uploads.md +7 -0
- package/playbooks/capabilities/postgresql/architecture.md +5 -0
- package/playbooks/capabilities/postgresql/migrations.md +5 -0
- package/playbooks/capabilities/postgresql/postgresql.manifest.json +15 -0
- package/playbooks/capabilities/postgresql/schema-design.md +7 -0
- package/playbooks/capabilities/postgresql/security.md +5 -0
- package/playbooks/capabilities/postgresql/testing.md +5 -0
- package/playbooks/capabilities/prisma/architecture.md +5 -0
- package/playbooks/capabilities/prisma/migrations.md +11 -0
- package/playbooks/capabilities/prisma/prisma.manifest.json +14 -0
- package/playbooks/capabilities/prisma/runtime.md +5 -0
- package/playbooks/capabilities/prisma/schema.md +5 -0
- package/playbooks/capabilities/prisma/testing.md +5 -0
- package/playbooks/capabilities/supabase/architecture.md +5 -0
- package/playbooks/capabilities/supabase/authentication.md +5 -0
- package/playbooks/capabilities/supabase/expo.md +16 -0
- package/playbooks/capabilities/supabase/migrations.md +7 -0
- package/playbooks/capabilities/supabase/nextjs.md +24 -0
- package/playbooks/capabilities/supabase/rls.md +7 -0
- package/playbooks/capabilities/supabase/supabase.manifest.json +32 -0
- package/playbooks/capabilities/supabase/testing.md +5 -0
- package/playbooks/capabilities/supabase/vite.md +14 -0
- package/playbooks/concerns/axios.md +123 -0
- package/playbooks/concerns/next-safe-action.md +66 -0
- package/playbooks/concerns/next-themes.md +87 -0
- package/playbooks/concerns/nuqs.md +59 -0
- package/playbooks/concerns/t3-env.md +48 -0
- package/playbooks/concerns/tanstack-query.md +145 -0
- package/playbooks/concerns/zod.md +174 -0
- package/playbooks/concerns/zustand.md +93 -0
- package/playbooks/devops/makefile.manifest.json +10 -0
- package/playbooks/devops/makefile.md +556 -0
- package/playbooks/devops/pr-template.manifest.json +10 -0
- package/playbooks/devops/pr-template.md +106 -0
- package/playbooks/platform/laravel-ui/blade/architecture.md +5 -0
- package/playbooks/platform/laravel-ui/blade/runtime.md +5 -0
- package/playbooks/platform/laravel-ui/blade/security.md +5 -0
- package/playbooks/platform/laravel-ui/blade/structure.md +5 -0
- package/playbooks/platform/laravel-ui/blade/testing.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/architecture.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/runtime.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/security.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/structure.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/testing.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/architecture.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/runtime.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/security.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/structure.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/testing.md +5 -0
- package/playbooks/platform/mobile.manifest.json +17 -0
- package/playbooks/platform/mobile.md +15 -0
- package/playbooks/platform/web.manifest.json +17 -0
- package/playbooks/platform/web.md +17 -0
- package/playbooks/stack/expo/architecture.md +27 -0
- package/playbooks/stack/expo/runtime.md +14 -0
- package/playbooks/stack/expo/security.md +18 -0
- package/playbooks/stack/expo/structure.md +26 -0
- package/playbooks/stack/expo/testing.md +13 -0
- package/playbooks/stack/laravel/architecture.md +34 -0
- package/playbooks/stack/laravel/runtime.md +27 -0
- package/playbooks/stack/laravel/security.md +23 -0
- package/playbooks/stack/laravel/structure.md +29 -0
- package/playbooks/stack/laravel/testing.md +22 -0
- package/playbooks/stack/laravel-ui.manifest.json +42 -0
- package/playbooks/stack/laravel.manifest.json +35 -0
- package/playbooks/stack/nextjs/architecture.md +62 -0
- package/playbooks/stack/nextjs/runtime.md +31 -0
- package/playbooks/stack/nextjs/security.md +32 -0
- package/playbooks/stack/nextjs/structure.md +50 -0
- package/playbooks/stack/nextjs/testing.md +20 -0
- package/playbooks/stack/nextjs.manifest.json +49 -0
- package/playbooks/stack/no-frontend.manifest.json +25 -0
- package/playbooks/stack/none.manifest.json +21 -0
- package/playbooks/stack/react-native.manifest.json +49 -0
- package/playbooks/stack/react-vite/architecture.md +33 -0
- package/playbooks/stack/react-vite/runtime.md +13 -0
- package/playbooks/stack/react-vite/security.md +16 -0
- package/playbooks/stack/react-vite/structure.md +23 -0
- package/playbooks/stack/react-vite/testing.md +12 -0
- package/playbooks/stack/react-vite.manifest.json +46 -0
- package/playbooks/stack/springboot/architecture.md +40 -0
- package/playbooks/stack/springboot/runtime.md +28 -0
- package/playbooks/stack/springboot/security.md +28 -0
- package/playbooks/stack/springboot/structure.md +30 -0
- package/playbooks/stack/springboot/testing.md +22 -0
- package/playbooks/stack/springboot.manifest.json +38 -0
- package/playbooks/styling/css-modules-extensions.md +267 -0
- package/playbooks/styling/css-modules.manifest.json +11 -0
- package/playbooks/styling/native-styles.manifest.json +9 -0
- package/playbooks/styling/native-styles.md +29 -0
- package/playbooks/styling/tailwind-extensions.md +182 -0
- package/playbooks/styling/tailwind.manifest.json +11 -0
- package/playbooks/universal/accessibility.manifest.json +11 -0
- package/playbooks/universal/accessibility.md +45 -0
- package/playbooks/universal/coding-rules.manifest.json +12 -0
- package/playbooks/universal/coding-rules.md +281 -0
- package/playbooks/universal/error-handling.manifest.json +10 -0
- package/playbooks/universal/error-handling.md +21 -0
- package/playbooks/universal/git-conventions.manifest.json +10 -0
- package/playbooks/universal/git-conventions.md +186 -0
- package/playbooks/universal/observability.manifest.json +10 -0
- package/playbooks/universal/observability.md +29 -0
- package/playbooks/universal/security.manifest.json +11 -0
- package/playbooks/universal/security.md +30 -0
- package/playbooks/universal/typescript.manifest.json +11 -0
- package/playbooks/universal/typescript.md +272 -0
- package/scripts/compatibility-matrix.mjs +48 -0
- package/scripts/validate-content.mjs +76 -0
- package/scripts/verify-generated.mjs +153 -0
- package/templates/agents/nextjs.md +47 -0
- package/templates/agents/react-native.md +48 -0
- package/templates/agents/react-vite.md +48 -0
- package/templates/docker/compose/postgres.yml +29 -0
- package/templates/docker/compose/springboot.yml +69 -0
- package/templates/docker/compose/supabase.yml +18 -0
- package/templates/docker/compose-prod/springboot.yml +64 -0
- package/templates/docker/dockerfile/laravel-inertia.dev.dockerfile +21 -0
- package/templates/docker/dockerfile/laravel.dev.dockerfile +12 -0
- package/templates/docker/dockerfile/laravel.prod.dockerfile +13 -0
- package/templates/docker/dockerfile/nextjs.dev.dockerfile +6 -0
- package/templates/docker/dockerfile/nextjs.prod.dockerfile +18 -0
- package/templates/docker/dockerfile/springboot.dev.dockerfile +5 -0
- package/templates/docker/dockerfile/springboot.prod.dockerfile +10 -0
- package/templates/docker/dockerfile/vite.dev.dockerfile +6 -0
- package/templates/docker/dockerfile/vite.prod.dockerfile +12 -0
- package/templates/gitignore/base.gitignore +32 -0
- package/templates/gitignore/nextjs.gitignore +40 -0
- package/templates/gitignore/react-native.gitignore +43 -0
- package/templates/gitignore/react-vite.gitignore +39 -0
- package/templates/makefile/frontend.mk +31 -0
- package/templates/makefile/laravel.mk +47 -0
- package/templates/makefile/postgres.mk +40 -0
- package/templates/makefile/springboot.mk +125 -0
- package/templates/makefile/supabase.mk +42 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineStackAdapter } from '../contract.js'
|
|
2
|
+
|
|
3
|
+
export const laravelAdapter = defineStackAdapter({
|
|
4
|
+
id: 'laravel',
|
|
5
|
+
kind: 'backend',
|
|
6
|
+
label: 'Laravel (PHP)',
|
|
7
|
+
compatibleWith: {
|
|
8
|
+
frontend: ['nextjs', 'react', 'react-native', 'no-frontend', 'laravel-ui'],
|
|
9
|
+
},
|
|
10
|
+
capabilities: {
|
|
11
|
+
applicationShapes: ['fullstack', 'separate', 'api', 'mobile'],
|
|
12
|
+
architectureProfiles: ['small', 'medium', 'large'],
|
|
13
|
+
authenticationModels: ['public', 'undecided', 'laravel-session', 'sanctum-spa', 'laravel-oidc'],
|
|
14
|
+
runtime: 'php',
|
|
15
|
+
},
|
|
16
|
+
contributes: {},
|
|
17
|
+
})
|
|
18
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { buildLaravelAuthView, laravelLoginNavigation } from './shared.js'
|
|
2
|
+
|
|
3
|
+
export const bladeUi = Object.freeze({
|
|
4
|
+
id: 'blade',
|
|
5
|
+
label: 'Blade',
|
|
6
|
+
homeRoute: "Route::view('/', 'home');",
|
|
7
|
+
composerPackages: Object.freeze([]),
|
|
8
|
+
files(answers, stack) {
|
|
9
|
+
const login = laravelLoginNavigation(stack.authentication)
|
|
10
|
+
return {
|
|
11
|
+
...buildLaravelAuthView(stack.authentication),
|
|
12
|
+
'resources/views/home.blade.php': `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{{ config('app.name') }}</title></head><body>${login}<main><h1>${answers.projectName}</h1><p>Laravel Blade starter</p></main></body></html>\n`,
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { bladeUi } from './blade.js'
|
|
2
|
+
import { inertiaReactUi } from './inertia-react.js'
|
|
3
|
+
import { livewireUi } from './livewire.js'
|
|
4
|
+
|
|
5
|
+
export const laravelUis = Object.freeze([bladeUi, livewireUi, inertiaReactUi])
|
|
6
|
+
|
|
7
|
+
export function getLaravelUi(id) {
|
|
8
|
+
const ui = laravelUis.find((candidate) => candidate.id === id)
|
|
9
|
+
if (!ui) throw new Error(`Unsupported Laravel UI: ${id}`)
|
|
10
|
+
return ui
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function laravelUiPromptContribution(argument) {
|
|
14
|
+
return {
|
|
15
|
+
slot: 'stack-options',
|
|
16
|
+
questions: [{
|
|
17
|
+
type: 'list',
|
|
18
|
+
name: 'laravelUi',
|
|
19
|
+
message: 'How should Laravel render the website?',
|
|
20
|
+
choices: [
|
|
21
|
+
{ name: 'Blade (Recommended) — server-rendered pages with the fewest moving parts', value: bladeUi.id },
|
|
22
|
+
{ name: 'Livewire — interactive server-driven components with minimal JavaScript', value: livewireUi.id },
|
|
23
|
+
{ name: 'Inertia + React — React pages with Laravel routing and controllers', value: inertiaReactUi.id },
|
|
24
|
+
],
|
|
25
|
+
default: bladeUi.id,
|
|
26
|
+
when: (answers) => answers.frontend === 'laravel-ui' && !argument,
|
|
27
|
+
}],
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { packageVersion } from '../../../compatibility.js'
|
|
2
|
+
import { buildLaravelAuthView } from './shared.js'
|
|
3
|
+
|
|
4
|
+
const php = (value) => `${value.trim()}\n`
|
|
5
|
+
const json = (value) => `${JSON.stringify(value, null, 2)}\n`
|
|
6
|
+
|
|
7
|
+
export const inertiaReactUi = Object.freeze({
|
|
8
|
+
id: 'inertia-react',
|
|
9
|
+
label: 'Inertia + React',
|
|
10
|
+
homeRoute: "Route::get('/', fn () => Inertia\\Inertia::render('Home'));",
|
|
11
|
+
composerPackages: Object.freeze(['inertiajs/inertia-laravel']),
|
|
12
|
+
middlewareImport: 'use App\\Http\\Middleware\\HandleInertiaRequests;',
|
|
13
|
+
middleware: ' $middleware->web(append: [HandleInertiaRequests::class]);',
|
|
14
|
+
files(answers, stack) {
|
|
15
|
+
const npm = (name) => packageVersion(stack.profile, name, 'laravel-ui', 'Laravel Inertia scaffold')
|
|
16
|
+
return {
|
|
17
|
+
...buildLaravelAuthView(stack.authentication),
|
|
18
|
+
'package.json': json({ private: true, type: 'module', scripts: { dev: 'vite', build: 'vite build' }, dependencies: { '@inertiajs/react': npm('@inertiajs/react'), react: npm('react'), 'react-dom': npm('react-dom') }, devDependencies: { '@vitejs/plugin-react': npm('@vitejs/plugin-react'), 'laravel-vite-plugin': npm('laravel-vite-plugin'), vite: npm('vite') } }),
|
|
19
|
+
'vite.config.js': `import { defineConfig } from 'vite'\nimport laravel from 'laravel-vite-plugin'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({ plugins: [laravel({ input: 'resources/js/app.jsx', refresh: true }), react()] })\n`,
|
|
20
|
+
'resources/js/app.jsx': `import { createInertiaApp } from '@inertiajs/react'\nimport { createRoot } from 'react-dom/client'\n\nconst pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })\ncreateInertiaApp({ resolve: (name) => pages[\`./Pages/\${name}.jsx\`], setup({ el, App, props }) { createRoot(el).render(<App {...props} />) } })\n`,
|
|
21
|
+
'resources/js/Pages/Home.jsx': `export default function Home() { return <main><h1>${answers.projectName}</h1><p>Laravel + Inertia + React</p></main> }\n`,
|
|
22
|
+
'resources/views/app.blade.php': '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">@viteReactRefresh @vite(\'resources/js/app.jsx\') @inertiaHead</head><body>@inertia</body></html>\n',
|
|
23
|
+
'app/Http/Middleware/HandleInertiaRequests.php': php(`<?php
|
|
24
|
+
|
|
25
|
+
namespace App\\Http\\Middleware;
|
|
26
|
+
|
|
27
|
+
use Inertia\\Middleware;
|
|
28
|
+
|
|
29
|
+
final class HandleInertiaRequests extends Middleware
|
|
30
|
+
{
|
|
31
|
+
protected $rootView = 'app';
|
|
32
|
+
}`),
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { buildLaravelAuthView, laravelLoginNavigation } from './shared.js'
|
|
2
|
+
|
|
3
|
+
const php = (value) => `${value.trim()}\n`
|
|
4
|
+
|
|
5
|
+
export const livewireUi = Object.freeze({
|
|
6
|
+
id: 'livewire',
|
|
7
|
+
label: 'Livewire',
|
|
8
|
+
homeRoute: "Route::view('/', 'app');",
|
|
9
|
+
composerPackages: Object.freeze(['livewire/livewire']),
|
|
10
|
+
files(answers, stack) {
|
|
11
|
+
const login = laravelLoginNavigation(stack.authentication)
|
|
12
|
+
return {
|
|
13
|
+
...buildLaravelAuthView(stack.authentication),
|
|
14
|
+
'app/Livewire/HomePage.php': php(`<?php
|
|
15
|
+
|
|
16
|
+
namespace App\\Livewire;
|
|
17
|
+
|
|
18
|
+
use Illuminate\\View\\View;
|
|
19
|
+
use Livewire\\Component;
|
|
20
|
+
|
|
21
|
+
final class HomePage extends Component
|
|
22
|
+
{
|
|
23
|
+
public int $count = 0;
|
|
24
|
+
|
|
25
|
+
public function increment(): void
|
|
26
|
+
{
|
|
27
|
+
$this->count++;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public function render(): View
|
|
31
|
+
{
|
|
32
|
+
return view('livewire.home-page');
|
|
33
|
+
}
|
|
34
|
+
}`),
|
|
35
|
+
'resources/views/livewire/home-page.blade.php': `<main><h1>${answers.projectName}</h1><p>Count: {{ $count }}</p><button wire:click="increment">Increment</button></main>\n`,
|
|
36
|
+
'resources/views/app.blade.php': `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{{ config('app.name') }}</title>@livewireStyles</head><body>${login}<livewire:home-page />@livewireScripts</body></html>\n`,
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { usesLaravelSession } from '../auth/session.js'
|
|
2
|
+
|
|
3
|
+
export function buildLaravelAuthView(authentication) {
|
|
4
|
+
if (!usesLaravelSession(authentication)) return {}
|
|
5
|
+
return {
|
|
6
|
+
'resources/views/auth/login.blade.php': '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Log in</title></head><body><main><h1>Log in</h1><form method="POST" action="/login">@csrf<label>Email <input name="email" type="email" autocomplete="email" required></label><label>Password <input name="password" type="password" autocomplete="current-password" required></label><button>Log in</button></form></main></body></html>\n',
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function laravelLoginNavigation(authentication) {
|
|
11
|
+
return usesLaravelSession(authentication)
|
|
12
|
+
? '<nav>@auth <form method="POST" action="/logout">@csrf<button>Log out</button></form> @else <a href="/login">Log in</a> @endauth</nav>'
|
|
13
|
+
: ''
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isStackAdapter } from './contract.js'
|
|
2
|
+
|
|
3
|
+
export function createStackRegistry(initialAdapters = []) {
|
|
4
|
+
const adapters = new Map()
|
|
5
|
+
|
|
6
|
+
const registry = {
|
|
7
|
+
register(adapter) {
|
|
8
|
+
if (!isStackAdapter(adapter)) throw new Error('Only defined stack adapters can be registered')
|
|
9
|
+
if (adapters.has(adapter.id)) throw new Error(`Duplicate stack adapter id: ${adapter.id}`)
|
|
10
|
+
adapters.set(adapter.id, adapter)
|
|
11
|
+
return adapter
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
get(id) {
|
|
15
|
+
return adapters.get(id)
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
require(id) {
|
|
19
|
+
const adapter = adapters.get(id)
|
|
20
|
+
if (!adapter) throw new Error(`Unknown stack adapter: ${id}`)
|
|
21
|
+
return adapter
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
list(kind) {
|
|
25
|
+
const registered = [...adapters.values()]
|
|
26
|
+
return kind ? registered.filter((adapter) => adapter.kind === kind) : registered
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
supports(firstId, secondId) {
|
|
30
|
+
const first = registry.require(firstId)
|
|
31
|
+
const second = registry.require(secondId)
|
|
32
|
+
const firstConstraint = first.compatibleWith[second.kind]
|
|
33
|
+
const secondConstraint = second.compatibleWith[first.kind]
|
|
34
|
+
return (!firstConstraint || firstConstraint.includes(second.id)) &&
|
|
35
|
+
(!secondConstraint || secondConstraint.includes(first.id))
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const adapter of initialAdapters) registry.register(adapter)
|
|
40
|
+
return Object.freeze(registry)
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CONTRIBUTION_HOOKS, PROMPT_SLOTS } from '../contract.js'
|
|
2
|
+
|
|
3
|
+
export function collectContributions(adapters, hook, context) {
|
|
4
|
+
if (!CONTRIBUTION_HOOKS.includes(hook)) throw new Error(`Unknown contribution hook: ${hook}`)
|
|
5
|
+
return adapters.flatMap((adapter) => {
|
|
6
|
+
const result = adapter.contributes[hook](context)
|
|
7
|
+
if (!Array.isArray(result)) {
|
|
8
|
+
throw new Error(`${adapter.id} contribution ${hook} must return an array`)
|
|
9
|
+
}
|
|
10
|
+
return result
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function collectPromptContributions(adapters, context) {
|
|
15
|
+
const prompts = collectContributions(adapters, 'prompts', context)
|
|
16
|
+
for (const prompt of prompts) {
|
|
17
|
+
if (!prompt || !PROMPT_SLOTS.includes(prompt.slot) || !Array.isArray(prompt.questions)) {
|
|
18
|
+
throw new Error('Prompt contributions require a supported slot and a questions array')
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze(Object.fromEntries(PROMPT_SLOTS.map((slot) => [
|
|
22
|
+
slot,
|
|
23
|
+
prompts.filter((prompt) => prompt.slot === slot).flatMap((prompt) => prompt.questions),
|
|
24
|
+
])))
|
|
25
|
+
}
|
package/lib/template.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// ─── Template Variable Replacement ───────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import fs from 'fs-extra'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
|
|
6
|
+
export function render(content, vars) {
|
|
7
|
+
return content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function readTemplate(templatesDir, category, name, ext = '') {
|
|
11
|
+
if (!name) return null
|
|
12
|
+
const file = path.join(templatesDir, category, `${name}${ext}`)
|
|
13
|
+
if (await fs.pathExists(file)) return fs.readFile(file, 'utf-8')
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildVars(answers, stack) {
|
|
18
|
+
const packagePath = (answers.packageName ?? 'com.app').replace(/\./g, '/')
|
|
19
|
+
const profile = stack.profile
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
PROJECT_NAME: answers.projectName,
|
|
23
|
+
PROJECT_DESCRIPTION: answers.projectDescription,
|
|
24
|
+
PACKAGE_NAME: answers.packageName ?? 'com.app',
|
|
25
|
+
PACKAGE_PATH: packagePath,
|
|
26
|
+
STYLE_MODE: stack.styleId?.toUpperCase().replace(/-/g, '_') ?? 'TAILWIND',
|
|
27
|
+
STACK: stack.key,
|
|
28
|
+
PLATFORM: stack.platform,
|
|
29
|
+
ARCHITECTURE: stack.architecture?.toUpperCase() ?? 'MEDIUM',
|
|
30
|
+
YEAR: new Date().getFullYear().toString(),
|
|
31
|
+
FRONTEND_DIR: stack.frontendDir || '.',
|
|
32
|
+
FRONTEND_PORT: stack.frontendPort || '3000',
|
|
33
|
+
BACKEND_PORT: stack.backendPort || '8080',
|
|
34
|
+
ENV_PREFIX: stack.envPrefix || '',
|
|
35
|
+
SRC_DIR: stack.frontendDir ? `${stack.frontendDir}/src` : 'src',
|
|
36
|
+
CONSTRAINTS: (stack.constraints || []).join('\n- ') ? `- ${(stack.constraints || []).join('\n- ')}` : '- (none beyond universal rules)',
|
|
37
|
+
// Dockerfile paths are resolved inside build.context, so they are always
|
|
38
|
+
// relative to that context rather than to the repository root.
|
|
39
|
+
FRONTEND_DOCKERFILE: 'Dockerfile.dev',
|
|
40
|
+
FRONTEND_DOCKERFILE_PROD: 'Dockerfile',
|
|
41
|
+
FRONTEND_PROD_PORT: stack.frontendDir ? '80:80' : `${stack.frontendPort || '3000'}:${stack.frontendPort || '3000'}`,
|
|
42
|
+
NODE_VERSION: profile.runtimes.node,
|
|
43
|
+
JAVA_VERSION: profile.runtimes.java,
|
|
44
|
+
MAVEN_VERSION: profile.runtimes.maven,
|
|
45
|
+
SPRING_BOOT_VERSION: profile.springBoot,
|
|
46
|
+
SPRING_MODULITH_VERSION: profile.springModulith,
|
|
47
|
+
PHP_VERSION: profile.runtimes.php,
|
|
48
|
+
COMPOSER_VERSION: profile.runtimes.composer,
|
|
49
|
+
LARAVEL_VERSION: profile.composerPackages['laravel/framework'],
|
|
50
|
+
LARAVEL_DIR: ['laravel-ui', 'no-frontend'].includes(stack.frontendKey) ? '.' : 'backend',
|
|
51
|
+
TESTCONTAINERS_VERSION: profile.packages["testcontainers-junit-jupiter"] || profile.packages["testcontainers-postgresql"] || "1.19.8",
|
|
52
|
+
POSTGRES_VERSION: profile.runtimes.postgres,
|
|
53
|
+
NODE_IMAGE: `${profile.images.node.repository}:${profile.images.node.tag}`,
|
|
54
|
+
MAVEN_IMAGE: `${profile.images.maven.repository}:${profile.images.maven.tag}`,
|
|
55
|
+
JAVA_IMAGE: `${profile.images.java.repository}:${profile.images.java.tag}`,
|
|
56
|
+
POSTGRES_IMAGE: `${profile.images.postgres.repository}:${profile.images.postgres.tag}`,
|
|
57
|
+
NGINX_IMAGE: `${profile.images.nginx.repository}:${profile.images.nginx.tag}`,
|
|
58
|
+
PHP_IMAGE: `${profile.images.php.repository}:${profile.images.php.tag}`,
|
|
59
|
+
COMPOSER_IMAGE: `${profile.images.composer.repository}:${profile.images.composer.tag}`,
|
|
60
|
+
COMPATIBILITY_PROFILE: profile.id,
|
|
61
|
+
}
|
|
62
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-win-project",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Project scaffolding CLI by Win",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-win-project": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/itsw1n/create-win-project.git"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"lib",
|
|
17
|
+
"compatibility",
|
|
18
|
+
"playbooks",
|
|
19
|
+
"ci",
|
|
20
|
+
"templates",
|
|
21
|
+
"scripts",
|
|
22
|
+
"docs/ARCHITECTURE.md",
|
|
23
|
+
"docs/CONTENT_MODEL.md",
|
|
24
|
+
"docs/DEPENDENCY_MAINTENANCE.md",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"start": "node index.js",
|
|
30
|
+
"doctor": "node index.js doctor",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"validate:content": "node scripts/validate-content.mjs",
|
|
33
|
+
"matrix:smoke": "node scripts/compatibility-matrix.mjs --scope=smoke",
|
|
34
|
+
"matrix:full": "node scripts/compatibility-matrix.mjs --scope=full",
|
|
35
|
+
"verify:generated": "node scripts/verify-generated.mjs"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"chalk": "5.6.2",
|
|
42
|
+
"fs-extra": "11.4.0",
|
|
43
|
+
"inquirer": "9.3.8",
|
|
44
|
+
"ora": "7.0.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"vitest": "2.1.9"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Playbook Index
|
|
2
|
+
|
|
3
|
+
Playbooks are task-routed standards used by generated projects. Their co-located manifests decide when a playbook applies and which headings appear in generated `RULES.md`.
|
|
4
|
+
|
|
5
|
+
## Content groups
|
|
6
|
+
|
|
7
|
+
| Directory | Responsibility |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `universal/` | Only principles that remain true across every stack |
|
|
10
|
+
| `platform/` | Browser/web and installed-mobile behavior |
|
|
11
|
+
| `stack/` | Five stack-owned facets: architecture, structure, runtime, security, testing |
|
|
12
|
+
| `capabilities/` | Supabase, PostgreSQL, Prisma, Flyway, Docker, CI, and authentication-provider behavior |
|
|
13
|
+
| `styling/` | Styling-system rules selected by the frontend |
|
|
14
|
+
| `concerns/` | Optional/shared libraries such as query, state, validation, and HTTP clients |
|
|
15
|
+
| `devops/` | Optional CI, Docker, Makefile, and pull-request guidance |
|
|
16
|
+
|
|
17
|
+
## Composition
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
required universal + selected platform manifests
|
|
21
|
+
+ selected frontend
|
|
22
|
+
+ selected backend/database/migration
|
|
23
|
+
+ selected styling
|
|
24
|
+
+ enabled operational tooling
|
|
25
|
+
= resolved stack + generated RULES.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Supported pairs:
|
|
29
|
+
|
|
30
|
+
| Frontend | Backend/data choices |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Next.js | Supabase, Spring Boot, PostgreSQL + Prisma |
|
|
33
|
+
| React + Vite | Supabase, Spring Boot |
|
|
34
|
+
| Expo | Supabase, Spring Boot, bring-your-own API |
|
|
35
|
+
|
|
36
|
+
Expo uses React Native `StyleSheet` by default. Web projects can select Tailwind CSS or CSS Modules.
|
|
37
|
+
|
|
38
|
+
## Manifest contract
|
|
39
|
+
|
|
40
|
+
A stack manifest must declare all three architecture profiles. Any manifest may declare:
|
|
41
|
+
|
|
42
|
+
- `id`, `kind`, `label`, and compatibility;
|
|
43
|
+
- dependency names, scripts, templates, and conditional playbooks (versions live only in `compatibility/profiles.json`);
|
|
44
|
+
- semantic environment names and client-visible names;
|
|
45
|
+
- stack constraints;
|
|
46
|
+
- required and optional concerns, target playbook, headings, and applicability.
|
|
47
|
+
|
|
48
|
+
Manifests compose capabilities; `lib/scaffold.js` owns their executable minimum. Generated directories exist only when they contain a selected profile's real files. Adding a runnable capability requires both parts plus generated-output and compatibility-matrix coverage.
|
|
49
|
+
|
|
50
|
+
## Generated documentation
|
|
51
|
+
|
|
52
|
+
- `AGENTS.md`: small always-on operating contract.
|
|
53
|
+
- `CONTEXT.md`: product-specific goals, scope, and decisions.
|
|
54
|
+
- `RULES.md`: concern-to-heading router, not a second policy document.
|
|
55
|
+
- `playbooks/`: only selected reusable guidance.
|
|
56
|
+
- `docs/`: setup, API, architecture, auth, and deployment for the generated product.
|
|
57
|
+
|
|
58
|
+
See `docs/CONTENT_MODEL.md` for authoring and deduplication rules and `docs/ARCHITECTURE.md` for the generation pipeline.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# OIDC Resource Server
|
|
2
|
+
|
|
3
|
+
## Provider and API Responsibilities
|
|
4
|
+
|
|
5
|
+
The external OpenID Connect provider owns login, access-token and refresh-token issuance, rotation, revocation, recovery, and MFA. Spring is a resource server: it validates issuer, audience, signature, expiry, and claims on every protected API request. This generator does not mint custom JWTs.
|
|
6
|
+
|
|
7
|
+
Public clients use Authorization Code with PKCE through the provider SDK. Store native refresh material only in platform-protected secure storage. A browser application should prefer a backend-for-frontend or provider-supported secure session when long-lived credentials would otherwise be exposed to JavaScript.
|
|
8
|
+
|
|
9
|
+
## Authorization
|
|
10
|
+
|
|
11
|
+
Map only documented provider claims to application authorities. Never trust a client-supplied user ID or role, and enforce resource ownership beside data access. Keep CORS on an explicit origin allowlist; bearer-token APIs do not use cookie CSRF as their request-integrity mechanism.
|
|
12
|
+
|
|
13
|
+
## Required Tests
|
|
14
|
+
|
|
15
|
+
Test missing bearer tokens and invalid issuer, audience, signature, expiry, and required claims. Also test valid access, resource-level denial, logout/revocation expectations at the provider boundary, and CORS behavior. Stub the decoder in MVC slices; use provider-compatible signed fixtures at the security integration boundary.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Spring Server Session
|
|
2
|
+
|
|
3
|
+
## Session Ownership
|
|
4
|
+
|
|
5
|
+
Spring Security authenticates the website, rotates the session identifier, and sends only a Secure, HttpOnly, SameSite cookie. The server owns expiry and logout; the browser never stores or refreshes an access token.
|
|
6
|
+
|
|
7
|
+
Keep CSRF enabled for cookie-authenticated mutations. Replace the generated development user with the product identity store before production, configure HTTPS-only cookies, and use a shared Spring Session store before horizontally scaling stateful instances.
|
|
8
|
+
|
|
9
|
+
## Authorization
|
|
10
|
+
|
|
11
|
+
Authentication only proves who the caller is. Check roles and resource ownership beside each protected read or side effect. Deny by default and keep health endpoints as the only anonymous baseline unless a route is deliberately public.
|
|
12
|
+
|
|
13
|
+
## Required Tests
|
|
14
|
+
|
|
15
|
+
Test anonymous rejection, successful login, session-id rotation, authenticated access, CSRF rejection/acceptance, resource-level denial, expiry, logout invalidation, and safe redirect handling. Never assert only that a controller method was called.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "github-actions",
|
|
3
|
+
"kind": "devops",
|
|
4
|
+
"label": "GitHub Actions CI",
|
|
5
|
+
"required": false,
|
|
6
|
+
"folders": [".github/workflows"],
|
|
7
|
+
"concerns": [
|
|
8
|
+
{ "id": "ci", "required": true, "sections": ["Frontend CI", "Backend CI (Spring Boot)", "Next.js CI"] }
|
|
9
|
+
]
|
|
10
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# GitHub Actions CI Capability
|
|
2
|
+
|
|
3
|
+
Generated workflows run the same validation commands documented for local development. Keep CI a reproducible quality boundary, not a second build system.
|
|
4
|
+
|
|
5
|
+
## Core Rules
|
|
6
|
+
|
|
7
|
+
- Pin actions to maintained major versions or immutable commits according to the repository's supply-chain policy.
|
|
8
|
+
- Use the lockfile command (`npm ci`) and Maven's batch mode.
|
|
9
|
+
- Grant the workflow and each job the minimum `permissions` required; read-only is the default for validation jobs.
|
|
10
|
+
- Do not expose secrets to pull requests from forks or print environment values during debugging.
|
|
11
|
+
- Cache package downloads, not generated application output that can become stale.
|
|
12
|
+
- Cancel superseded runs on the same pull request when build time becomes significant.
|
|
13
|
+
- Protect the release branch with required checks and review rather than relying on branch names in prose.
|
|
14
|
+
|
|
15
|
+
## Frontend CI
|
|
16
|
+
|
|
17
|
+
The generated frontend job performs, in order:
|
|
18
|
+
|
|
19
|
+
1. checkout;
|
|
20
|
+
2. runtime setup with lockfile-aware npm caching;
|
|
21
|
+
3. `npm ci`;
|
|
22
|
+
4. lint where configured;
|
|
23
|
+
5. strict typecheck;
|
|
24
|
+
6. selected test level;
|
|
25
|
+
7. production build;
|
|
26
|
+
8. Playwright browser install and end-to-end tests when full testing is selected.
|
|
27
|
+
|
|
28
|
+
React + Vite commands run from `frontend/`. Next.js and Expo commands run from the repository root. Build-time public values may use CI variables/secrets for convenience, but `NEXT_PUBLIC_`, `VITE_`, and `EXPO_PUBLIC_` values are still publicly bundled and must never be credentials.
|
|
29
|
+
|
|
30
|
+
## Backend CI (Spring Boot)
|
|
31
|
+
|
|
32
|
+
The generated Spring job runs:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd backend
|
|
36
|
+
mvn --batch-mode test
|
|
37
|
+
mvn --batch-mode package -DskipTests
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Unit/MVC tests use the generated H2 test configuration and do not need invented JWT credentials or a PostgreSQL service. Add a PostgreSQL service only when a real integration-test profile exercises PostgreSQL-specific behavior; use the same `DATABASE_URL`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` contract as application configuration.
|
|
41
|
+
|
|
42
|
+
## Next.js CI
|
|
43
|
+
|
|
44
|
+
Next.js validates lint, types, selected tests, and `next build`. Supabase SSR builds receive only the public project URL and publishable key. Other Next.js backends do not receive placeholder Supabase values.
|
|
45
|
+
|
|
46
|
+
When full testing is selected, Playwright starts the generated development server. Give that step the same required public build/runtime values as the application build and keep test accounts/data isolated from production.
|
|
47
|
+
|
|
48
|
+
## Security and Releases
|
|
49
|
+
|
|
50
|
+
- Validation workflows should normally declare `permissions: contents: read`.
|
|
51
|
+
- Release/publish jobs require explicit additional permissions and should be separate from untrusted pull-request validation.
|
|
52
|
+
- Use protected environments for production deployment approval and environment-scoped secrets.
|
|
53
|
+
- Generate provenance/SBOM artifacts when the deployment risk warrants them.
|
|
54
|
+
- Keep deployment rollback documented and test it before an incident.
|
|
55
|
+
|
|
56
|
+
## Agent Rules
|
|
57
|
+
|
|
58
|
+
- Change local scripts and CI together so command behavior does not drift.
|
|
59
|
+
- Do not weaken a failing check to make a pull request green; fix the defect or document an approved policy change.
|
|
60
|
+
- If a job needs a secret, name its owner, rotation process, environments, and why an identity federation mechanism cannot replace it.
|
|
61
|
+
- Add path filters only after confirming changes to shared root configuration still trigger every affected job.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "docker",
|
|
3
|
+
"kind": "devops",
|
|
4
|
+
"label": "Docker",
|
|
5
|
+
"file": "capabilities/docker/overview.md",
|
|
6
|
+
"required": false,
|
|
7
|
+
"folders": [],
|
|
8
|
+
"concerns": [
|
|
9
|
+
{ "id": "docker", "required": true, "sections": ["Dev vs Prod Differences", "Dockerfiles"] }
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Docker Capability
|
|
2
|
+
|
|
3
|
+
Generated Docker files are executable configuration, not tutorial samples. Keep this playbook focused on decisions the files cannot express by themselves.
|
|
4
|
+
|
|
5
|
+
## Core Rules
|
|
6
|
+
|
|
7
|
+
- Pin major runtime versions and use small trusted base images.
|
|
8
|
+
- Copy dependency descriptors before application source so dependency layers remain cacheable.
|
|
9
|
+
- Use a non-root runtime user when the framework image and deployment permit it.
|
|
10
|
+
- Keep credentials out of images, build arguments, repository files, and Compose defaults. Inject them at runtime through the deployment platform.
|
|
11
|
+
- Add `.dockerignore` files for dependencies, build output, secrets, IDE files, and VCS metadata.
|
|
12
|
+
- Make health checks call a real application endpoint and distinguish readiness from liveness when the orchestrator supports both.
|
|
13
|
+
- Scan the final image and update deliberately; do not hide known findings by disabling scanners.
|
|
14
|
+
|
|
15
|
+
## Dev vs Prod Differences
|
|
16
|
+
|
|
17
|
+
| Concern | Development | Production |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Source | bind-mounted for fast feedback | copied into an immutable image |
|
|
20
|
+
| Command | framework/Maven dev server | optimized server or executable JAR |
|
|
21
|
+
| Dependencies | development dependencies available | runtime-only stage where practical |
|
|
22
|
+
| Ports | exposed for local access | published by the deployment platform |
|
|
23
|
+
| Credentials | local uncommitted environment | managed secret store |
|
|
24
|
+
| Restart | developer-controlled | orchestrator policy |
|
|
25
|
+
|
|
26
|
+
Do not deploy the development Compose file. Production needs explicit TLS termination, secret delivery, persistence/backup, resource limits, logging, probes, and rollout policy.
|
|
27
|
+
|
|
28
|
+
## Dockerfiles
|
|
29
|
+
|
|
30
|
+
The generated templates use multi-stage production builds:
|
|
31
|
+
|
|
32
|
+
- Next.js builds standalone output, then copies only the standalone server, static assets, and public files.
|
|
33
|
+
- Vite builds static assets, then serves them behind nginx with SPA fallback.
|
|
34
|
+
- Spring Boot builds with Maven and runs the executable JAR on a JRE image.
|
|
35
|
+
|
|
36
|
+
When editing a Dockerfile, verify both the image build and the container's real health endpoint. A successful compile alone does not prove the runtime stage contains all required files.
|
|
37
|
+
|
|
38
|
+
## Compose Boundaries
|
|
39
|
+
|
|
40
|
+
- `depends_on` controls startup ordering, not application readiness; use health conditions where a dependency must be ready.
|
|
41
|
+
- Use named volumes only for data that must persist. Never mount source into a production container.
|
|
42
|
+
- Bind databases to localhost in local development unless another host genuinely needs access.
|
|
43
|
+
- Keep frontend-public values separate from backend secrets. Public framework prefixes remain public inside containers.
|
|
44
|
+
- For managed Supabase, Compose should run the application client; it should not pretend a full Supabase platform exists locally unless the Supabase CLI owns that environment.
|
|
45
|
+
|
|
46
|
+
## Verification
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
docker compose config
|
|
50
|
+
docker compose build
|
|
51
|
+
docker compose up
|
|
52
|
+
docker compose ps
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then call the application health endpoint and exercise one real request across service boundaries. Inspect logs for secret leakage before considering the container setup complete.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Flyway Environments
|
|
2
|
+
|
|
3
|
+
Apply the same migration history through local, test, staging, and production. Production uses one
|
|
4
|
+
controlled migrator with least-privilege credentials, locking, backup/recovery, and reviewed commands.
|
|
5
|
+
Never enable clean/reset or repair checksums casually in shared environments.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "flyway",
|
|
3
|
+
"kind": "migration",
|
|
4
|
+
"label": "Flyway",
|
|
5
|
+
"playbooks": ["capabilities/flyway/migrations.md", "capabilities/flyway/environments.md", "capabilities/flyway/testing.md"],
|
|
6
|
+
"appliesTo": { "backend": ["springboot"] },
|
|
7
|
+
"required": true,
|
|
8
|
+
"folders": [],
|
|
9
|
+
"concerns": [
|
|
10
|
+
{ "id": "migration", "required": true, "playbook": "capabilities/flyway/migrations.md", "sections": ["Naming and Ownership"] }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Flyway Migrations
|
|
2
|
+
|
|
3
|
+
## Naming and Ownership
|
|
4
|
+
|
|
5
|
+
Use immutable ordered versioned migrations with descriptive names and repeatables only for truly
|
|
6
|
+
replaceable objects. Keep schema change and consuming code review together. Prefer explicit SQL,
|
|
7
|
+
expand/contract rollouts, bounded backfills, and forward fixes after deployment.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Flyway Testing
|
|
2
|
+
|
|
3
|
+
Run Flyway from empty PostgreSQL and supported previous baselines. Start the Spring context against
|
|
4
|
+
the migrated schema, verify constraints and important queries, and test risky expand/contract stages.
|
|
5
|
+
Checksum drift or out-of-order production history fails CI rather than being repaired automatically.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Laravel Database
|
|
2
|
+
|
|
3
|
+
PostgreSQL is the generated default. Eloquent owns ordinary persistence; query objects or
|
|
4
|
+
repositories are escalation tools, not model-by-model ceremony. Use foreign keys, unique
|
|
5
|
+
constraints, checks, appropriate nullability, indexes derived from real query paths, and
|
|
6
|
+
race-safe handling of constraint violations. Prevent N+1 queries and allowlist client
|
|
7
|
+
sorting/filtering. Factories own test data; seeders own deliberate local/demo baseline data.
|
|
8
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Laravel Migrations
|
|
2
|
+
|
|
3
|
+
## Development
|
|
4
|
+
|
|
5
|
+
Laravel migrations are the schema source of truth. Generate descriptive migrations,
|
|
6
|
+
review their SQL impact, test from an empty PostgreSQL database, and commit them with the
|
|
7
|
+
code that needs them. Never edit an already-deployed migration to change history.
|
|
8
|
+
|
|
9
|
+
## Production
|
|
10
|
+
|
|
11
|
+
Use reviewed `php artisan migrate --force` in a deployment job with concurrency control,
|
|
12
|
+
backups, monitoring, and a documented recovery path. Prefer expand/contract changes for
|
|
13
|
+
zero-downtime releases. Production reset/refresh commands are forbidden. Prefer a new
|
|
14
|
+
forward-fix migration after deployment; use `down()` only when rollback is verified safe.
|
|
15
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Laravel Observability
|
|
2
|
+
|
|
3
|
+
Emit structured logs with request/trace identifiers, stable event names, safe entity IDs,
|
|
4
|
+
latency, and outcome. Redact authentication material, secrets, uploads, and unnecessary
|
|
5
|
+
PII. Measure HTTP errors/latency, database saturation, cache behavior, queue depth/age,
|
|
6
|
+
retries, failed jobs, and scheduler outcomes. Health checks distinguish liveness from
|
|
7
|
+
dependency readiness without leaking configuration.
|
|
8
|
+
|