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.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/ci/expo.yml +28 -0
  4. package/ci/laravel.yml +49 -0
  5. package/ci/nextjs.yml +29 -0
  6. package/ci/springboot.yml +33 -0
  7. package/ci/vite.yml +36 -0
  8. package/compatibility/profiles.json +237 -0
  9. package/docs/ARCHITECTURE.md +118 -0
  10. package/docs/CONTENT_MODEL.md +36 -0
  11. package/docs/DEPENDENCY_MAINTENANCE.md +31 -0
  12. package/index.js +431 -0
  13. package/lib/application-shapes.js +95 -0
  14. package/lib/banner.js +45 -0
  15. package/lib/catalog.js +343 -0
  16. package/lib/compatibility.js +116 -0
  17. package/lib/constants.js +3 -0
  18. package/lib/doctor.js +45 -0
  19. package/lib/files.js +158 -0
  20. package/lib/generator.js +366 -0
  21. package/lib/interview.js +77 -0
  22. package/lib/laravel-scaffold.js +3 -0
  23. package/lib/playbooks.js +172 -0
  24. package/lib/project-location.js +18 -0
  25. package/lib/scaffold.js +467 -0
  26. package/lib/stacks/context.js +27 -0
  27. package/lib/stacks/contract.js +142 -0
  28. package/lib/stacks/index.js +8 -0
  29. package/lib/stacks/laravel/architecture.js +27 -0
  30. package/lib/stacks/laravel/auth/oidc.js +13 -0
  31. package/lib/stacks/laravel/auth/public.js +28 -0
  32. package/lib/stacks/laravel/auth/sanctum.js +14 -0
  33. package/lib/stacks/laravel/auth/session.js +17 -0
  34. package/lib/stacks/laravel/composer.js +50 -0
  35. package/lib/stacks/laravel/generate.js +362 -0
  36. package/lib/stacks/laravel/index.js +18 -0
  37. package/lib/stacks/laravel/ui/blade.js +15 -0
  38. package/lib/stacks/laravel/ui/index.js +29 -0
  39. package/lib/stacks/laravel/ui/inertia-react.js +35 -0
  40. package/lib/stacks/laravel/ui/livewire.js +39 -0
  41. package/lib/stacks/laravel/ui/shared.js +14 -0
  42. package/lib/stacks/registry.js +42 -0
  43. package/lib/stacks/shared/contributions.js +25 -0
  44. package/lib/template.js +62 -0
  45. package/package.json +49 -0
  46. package/playbooks/INDEX.md +58 -0
  47. package/playbooks/capabilities/auth/oidc-resource-server.md +15 -0
  48. package/playbooks/capabilities/auth/spring-session.md +15 -0
  49. package/playbooks/capabilities/ci/github-actions.manifest.json +10 -0
  50. package/playbooks/capabilities/ci/github-actions.md +61 -0
  51. package/playbooks/capabilities/docker/docker.manifest.json +11 -0
  52. package/playbooks/capabilities/docker/overview.md +55 -0
  53. package/playbooks/capabilities/flyway/environments.md +5 -0
  54. package/playbooks/capabilities/flyway/flyway.manifest.json +12 -0
  55. package/playbooks/capabilities/flyway/migrations.md +7 -0
  56. package/playbooks/capabilities/flyway/testing.md +5 -0
  57. package/playbooks/capabilities/laravel/database.md +8 -0
  58. package/playbooks/capabilities/laravel/migrations.md +15 -0
  59. package/playbooks/capabilities/laravel/observability.md +8 -0
  60. package/playbooks/capabilities/laravel/oidc-resource-server.md +10 -0
  61. package/playbooks/capabilities/laravel/queues.md +8 -0
  62. package/playbooks/capabilities/laravel/sanctum-spa.md +8 -0
  63. package/playbooks/capabilities/laravel/scheduler.md +7 -0
  64. package/playbooks/capabilities/laravel/session-auth.md +8 -0
  65. package/playbooks/capabilities/laravel/storage-uploads.md +7 -0
  66. package/playbooks/capabilities/postgresql/architecture.md +5 -0
  67. package/playbooks/capabilities/postgresql/migrations.md +5 -0
  68. package/playbooks/capabilities/postgresql/postgresql.manifest.json +15 -0
  69. package/playbooks/capabilities/postgresql/schema-design.md +7 -0
  70. package/playbooks/capabilities/postgresql/security.md +5 -0
  71. package/playbooks/capabilities/postgresql/testing.md +5 -0
  72. package/playbooks/capabilities/prisma/architecture.md +5 -0
  73. package/playbooks/capabilities/prisma/migrations.md +11 -0
  74. package/playbooks/capabilities/prisma/prisma.manifest.json +14 -0
  75. package/playbooks/capabilities/prisma/runtime.md +5 -0
  76. package/playbooks/capabilities/prisma/schema.md +5 -0
  77. package/playbooks/capabilities/prisma/testing.md +5 -0
  78. package/playbooks/capabilities/supabase/architecture.md +5 -0
  79. package/playbooks/capabilities/supabase/authentication.md +5 -0
  80. package/playbooks/capabilities/supabase/expo.md +16 -0
  81. package/playbooks/capabilities/supabase/migrations.md +7 -0
  82. package/playbooks/capabilities/supabase/nextjs.md +24 -0
  83. package/playbooks/capabilities/supabase/rls.md +7 -0
  84. package/playbooks/capabilities/supabase/supabase.manifest.json +32 -0
  85. package/playbooks/capabilities/supabase/testing.md +5 -0
  86. package/playbooks/capabilities/supabase/vite.md +14 -0
  87. package/playbooks/concerns/axios.md +123 -0
  88. package/playbooks/concerns/next-safe-action.md +66 -0
  89. package/playbooks/concerns/next-themes.md +87 -0
  90. package/playbooks/concerns/nuqs.md +59 -0
  91. package/playbooks/concerns/t3-env.md +48 -0
  92. package/playbooks/concerns/tanstack-query.md +145 -0
  93. package/playbooks/concerns/zod.md +174 -0
  94. package/playbooks/concerns/zustand.md +93 -0
  95. package/playbooks/devops/makefile.manifest.json +10 -0
  96. package/playbooks/devops/makefile.md +556 -0
  97. package/playbooks/devops/pr-template.manifest.json +10 -0
  98. package/playbooks/devops/pr-template.md +106 -0
  99. package/playbooks/platform/laravel-ui/blade/architecture.md +5 -0
  100. package/playbooks/platform/laravel-ui/blade/runtime.md +5 -0
  101. package/playbooks/platform/laravel-ui/blade/security.md +5 -0
  102. package/playbooks/platform/laravel-ui/blade/structure.md +5 -0
  103. package/playbooks/platform/laravel-ui/blade/testing.md +5 -0
  104. package/playbooks/platform/laravel-ui/inertia-react/architecture.md +5 -0
  105. package/playbooks/platform/laravel-ui/inertia-react/runtime.md +5 -0
  106. package/playbooks/platform/laravel-ui/inertia-react/security.md +5 -0
  107. package/playbooks/platform/laravel-ui/inertia-react/structure.md +5 -0
  108. package/playbooks/platform/laravel-ui/inertia-react/testing.md +5 -0
  109. package/playbooks/platform/laravel-ui/livewire/architecture.md +5 -0
  110. package/playbooks/platform/laravel-ui/livewire/runtime.md +5 -0
  111. package/playbooks/platform/laravel-ui/livewire/security.md +5 -0
  112. package/playbooks/platform/laravel-ui/livewire/structure.md +5 -0
  113. package/playbooks/platform/laravel-ui/livewire/testing.md +5 -0
  114. package/playbooks/platform/mobile.manifest.json +17 -0
  115. package/playbooks/platform/mobile.md +15 -0
  116. package/playbooks/platform/web.manifest.json +17 -0
  117. package/playbooks/platform/web.md +17 -0
  118. package/playbooks/stack/expo/architecture.md +27 -0
  119. package/playbooks/stack/expo/runtime.md +14 -0
  120. package/playbooks/stack/expo/security.md +18 -0
  121. package/playbooks/stack/expo/structure.md +26 -0
  122. package/playbooks/stack/expo/testing.md +13 -0
  123. package/playbooks/stack/laravel/architecture.md +34 -0
  124. package/playbooks/stack/laravel/runtime.md +27 -0
  125. package/playbooks/stack/laravel/security.md +23 -0
  126. package/playbooks/stack/laravel/structure.md +29 -0
  127. package/playbooks/stack/laravel/testing.md +22 -0
  128. package/playbooks/stack/laravel-ui.manifest.json +42 -0
  129. package/playbooks/stack/laravel.manifest.json +35 -0
  130. package/playbooks/stack/nextjs/architecture.md +62 -0
  131. package/playbooks/stack/nextjs/runtime.md +31 -0
  132. package/playbooks/stack/nextjs/security.md +32 -0
  133. package/playbooks/stack/nextjs/structure.md +50 -0
  134. package/playbooks/stack/nextjs/testing.md +20 -0
  135. package/playbooks/stack/nextjs.manifest.json +49 -0
  136. package/playbooks/stack/no-frontend.manifest.json +25 -0
  137. package/playbooks/stack/none.manifest.json +21 -0
  138. package/playbooks/stack/react-native.manifest.json +49 -0
  139. package/playbooks/stack/react-vite/architecture.md +33 -0
  140. package/playbooks/stack/react-vite/runtime.md +13 -0
  141. package/playbooks/stack/react-vite/security.md +16 -0
  142. package/playbooks/stack/react-vite/structure.md +23 -0
  143. package/playbooks/stack/react-vite/testing.md +12 -0
  144. package/playbooks/stack/react-vite.manifest.json +46 -0
  145. package/playbooks/stack/springboot/architecture.md +40 -0
  146. package/playbooks/stack/springboot/runtime.md +28 -0
  147. package/playbooks/stack/springboot/security.md +28 -0
  148. package/playbooks/stack/springboot/structure.md +30 -0
  149. package/playbooks/stack/springboot/testing.md +22 -0
  150. package/playbooks/stack/springboot.manifest.json +38 -0
  151. package/playbooks/styling/css-modules-extensions.md +267 -0
  152. package/playbooks/styling/css-modules.manifest.json +11 -0
  153. package/playbooks/styling/native-styles.manifest.json +9 -0
  154. package/playbooks/styling/native-styles.md +29 -0
  155. package/playbooks/styling/tailwind-extensions.md +182 -0
  156. package/playbooks/styling/tailwind.manifest.json +11 -0
  157. package/playbooks/universal/accessibility.manifest.json +11 -0
  158. package/playbooks/universal/accessibility.md +45 -0
  159. package/playbooks/universal/coding-rules.manifest.json +12 -0
  160. package/playbooks/universal/coding-rules.md +281 -0
  161. package/playbooks/universal/error-handling.manifest.json +10 -0
  162. package/playbooks/universal/error-handling.md +21 -0
  163. package/playbooks/universal/git-conventions.manifest.json +10 -0
  164. package/playbooks/universal/git-conventions.md +186 -0
  165. package/playbooks/universal/observability.manifest.json +10 -0
  166. package/playbooks/universal/observability.md +29 -0
  167. package/playbooks/universal/security.manifest.json +11 -0
  168. package/playbooks/universal/security.md +30 -0
  169. package/playbooks/universal/typescript.manifest.json +11 -0
  170. package/playbooks/universal/typescript.md +272 -0
  171. package/scripts/compatibility-matrix.mjs +48 -0
  172. package/scripts/validate-content.mjs +76 -0
  173. package/scripts/verify-generated.mjs +153 -0
  174. package/templates/agents/nextjs.md +47 -0
  175. package/templates/agents/react-native.md +48 -0
  176. package/templates/agents/react-vite.md +48 -0
  177. package/templates/docker/compose/postgres.yml +29 -0
  178. package/templates/docker/compose/springboot.yml +69 -0
  179. package/templates/docker/compose/supabase.yml +18 -0
  180. package/templates/docker/compose-prod/springboot.yml +64 -0
  181. package/templates/docker/dockerfile/laravel-inertia.dev.dockerfile +21 -0
  182. package/templates/docker/dockerfile/laravel.dev.dockerfile +12 -0
  183. package/templates/docker/dockerfile/laravel.prod.dockerfile +13 -0
  184. package/templates/docker/dockerfile/nextjs.dev.dockerfile +6 -0
  185. package/templates/docker/dockerfile/nextjs.prod.dockerfile +18 -0
  186. package/templates/docker/dockerfile/springboot.dev.dockerfile +5 -0
  187. package/templates/docker/dockerfile/springboot.prod.dockerfile +10 -0
  188. package/templates/docker/dockerfile/vite.dev.dockerfile +6 -0
  189. package/templates/docker/dockerfile/vite.prod.dockerfile +12 -0
  190. package/templates/gitignore/base.gitignore +32 -0
  191. package/templates/gitignore/nextjs.gitignore +40 -0
  192. package/templates/gitignore/react-native.gitignore +43 -0
  193. package/templates/gitignore/react-vite.gitignore +39 -0
  194. package/templates/makefile/frontend.mk +31 -0
  195. package/templates/makefile/laravel.mk +47 -0
  196. package/templates/makefile/postgres.mk +40 -0
  197. package/templates/makefile/springboot.mk +125 -0
  198. package/templates/makefile/supabase.mk +42 -0
@@ -0,0 +1,272 @@
1
+ # TypeScript Rules (Universal)
2
+
3
+ Applies to every TypeScript project.
4
+
5
+ ---
6
+
7
+ ## Strict Mode — Always On
8
+ ```json
9
+ // tsconfig.json
10
+ {
11
+ "compilerOptions": {
12
+ "strict": true,
13
+ "noUncheckedIndexedAccess": true,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "exactOptionalPropertyTypes": true
17
+ }
18
+ }
19
+ ```
20
+
21
+ These flags catch real bugs. Never disable them.
22
+
23
+ ---
24
+
25
+ ## No `any`
26
+ - Never use `any` unless absolutely unavoidable
27
+ - When unavoidable: add `// reason:` comment explaining why
28
+
29
+ ```typescript
30
+ // ❌
31
+ const data: any = fetchUser()
32
+ function process(input: any) { ... }
33
+
34
+ // ✅
35
+ const data: unknown = fetchUser()
36
+ function process(input: UserInput) { ... }
37
+
38
+ // ✅ justified any with comment
39
+ // reason: third-party library has no types and @types package doesn't exist
40
+ const chart = new (window as any).ChartLib()
41
+ ```
42
+
43
+ ### Alternatives to `any`
44
+ | Instead of `any` | Use |
45
+ |------------------|-------------------------|
46
+ | Unknown data | `unknown` |
47
+ | Flexible object | `Record<string, unknown>` |
48
+ | Multiple types | Union `string \| number` |
49
+ | Optional | `T \| undefined` |
50
+ | Nullable | `T \| null` |
51
+
52
+ ---
53
+
54
+ ## Type vs Interface
55
+ ```typescript
56
+ // Interface — for objects that may be extended or implemented
57
+ interface User {
58
+ id: string
59
+ name: string
60
+ email: string
61
+ }
62
+
63
+ // Type — for unions, intersections, primitives, tuples
64
+ type UserId = string
65
+ type UserRole = 'ADMIN' | 'USER' | 'MODERATOR'
66
+ type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }
67
+ type UserWithRole = User & { role: UserRole }
68
+ ```
69
+
70
+ ### Rule
71
+ - Objects → `interface` (extends naturally, better error messages)
72
+ - Everything else → `type`
73
+ - Never use `I` prefix for interfaces (`IUser` → `User`)
74
+
75
+ ---
76
+
77
+ ## Discriminated Unions for State
78
+ ```typescript
79
+ // ❌ scattered boolean flags
80
+ type State = {
81
+ isLoading: boolean
82
+ isError: boolean
83
+ isSuccess: boolean
84
+ data?: User
85
+ error?: Error
86
+ }
87
+
88
+ // ✅ discriminated union — mutually exclusive states
89
+ type State =
90
+ | { status: 'idle' }
91
+ | { status: 'loading' }
92
+ | { status: 'success'; data: User }
93
+ | { status: 'error'; error: Error }
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Unknown Over Any for Errors
99
+ ```typescript
100
+ // ❌
101
+ } catch (error: any) {
102
+ console.log(error.message)
103
+ }
104
+
105
+ // ✅
106
+ } catch (error: unknown) {
107
+ const message = error instanceof Error ? error.message : 'Unknown error'
108
+ logger.error(message)
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Generics
115
+ Use when genuinely reusable — not for ceremony.
116
+
117
+ ```typescript
118
+ // ✅ genuinely reusable
119
+ function first<T>(array: T[]): T | undefined {
120
+ return array[0]
121
+ }
122
+
123
+ // ✅ reusable operation result inside the application
124
+ type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }
125
+
126
+ // ❌ unnecessary generic
127
+ function getUserName<T extends User>(user: T): string {
128
+ return user.name
129
+ // just use: function getUserName(user: User): string
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Zod for Runtime Validation
136
+
137
+ Use Zod for runtime validation **when** this project has runtime validation needs (form input, API responses, external data). If the project has no such need, this section does not apply — the rules are optional.
138
+
139
+ TypeScript types are compile-time only. Use Zod at runtime boundaries.
140
+
141
+ ```typescript
142
+ // Define schema
143
+ const UserSchema = z.object({
144
+ id: z.string().uuid(),
145
+ name: z.string().min(1),
146
+ email: z.string().email(),
147
+ role: z.enum(['ADMIN', 'USER']),
148
+ })
149
+
150
+ // Infer type from schema — single source of truth
151
+ type User = z.infer<typeof UserSchema>
152
+
153
+ // Validate at runtime
154
+ const user = UserSchema.parse(apiResponse.data)
155
+ ```
156
+
157
+ ### When to use Zod
158
+ - Form validation (via React Hook Form resolver)
159
+ - API response parsing
160
+ - Environment variable validation (t3-env)
161
+ - Any external data entering the system
162
+
163
+ ---
164
+
165
+ ## Where Types Live
166
+
167
+ ### Decision tree
168
+ ```
169
+ Is this type used across multiple features?
170
+ → src/types/[name].ts
171
+
172
+ Is this type specific to one feature?
173
+ → src/features/[name]/types/index.ts
174
+
175
+ Is this type a Zod schema + inferred type?
176
+ → src/features/[name]/schemas/[name].schema.ts
177
+
178
+ Is this type shared by several transport clients?
179
+ → src/types/transport.ts
180
+ ```
181
+
182
+ ### Global types (src/types/)
183
+ ```typescript
184
+ // src/types/transport.ts
185
+ // Successful endpoints return their normal DTO. Model only genuinely shared shapes.
186
+ export type ProblemDetails = { type: string; title: string; status: number; detail?: string; traceId?: string }
187
+
188
+ // src/types/pagination.ts
189
+ export type PaginatedResponse<T> = {
190
+ data: T[]
191
+ total: number
192
+ page: number
193
+ limit: number
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Type Assertions
200
+ Avoid `as` unless justified.
201
+
202
+ ```typescript
203
+ // ❌ lying to TypeScript
204
+ const user = data as User
205
+
206
+ // ✅ validate first, then trust
207
+ const user = UserSchema.parse(data)
208
+
209
+ // ✅ justified assertion with comment
210
+ // reason: DOM ref is always set before this handler fires
211
+ const input = inputRef.current as HTMLInputElement
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Enums vs Union Types
217
+ Prefer union types over enums for most cases.
218
+
219
+ ```typescript
220
+ // ❌ enum (compiles to weird JS, harder to iterate)
221
+ enum UserRole {
222
+ ADMIN = 'ADMIN',
223
+ USER = 'USER',
224
+ }
225
+
226
+ // ✅ const object + union type (tree-shakeable, readable compiled output)
227
+ export const ROLES = {
228
+ ADMIN: 'ADMIN',
229
+ USER: 'USER',
230
+ } as const
231
+
232
+ export type UserRole = typeof ROLES[keyof typeof ROLES]
233
+ // → 'ADMIN' | 'USER'
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Null vs Undefined
239
+ ```typescript
240
+ // Use null: explicitly absent (API returns null, user cleared a field)
241
+ type User = { avatar: string | null }
242
+
243
+ // Use undefined: optionally present (optional function param, optional object key)
244
+ type Options = { timeout?: number }
245
+
246
+ // Never mix without reason — pick one for each case and be consistent
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Agent Rules
252
+ ```
253
+ strict: true — never disable, never add ts-ignore without // reason:
254
+
255
+ Type a new variable?
256
+ → Use the most specific type possible
257
+ → Infer from Zod schema if validating external data
258
+ → Use unknown for error catch blocks
259
+
260
+ New shared type?
261
+ → If used in 2+ features → src/types/
262
+ → If feature-specific → features/[name]/types/
263
+
264
+ New form?
265
+ → Zod schema first → infer type → pass to React Hook Form
266
+
267
+ Avoid:
268
+ → any (use unknown)
269
+ → type assertions without reason comment
270
+ → enums (use const object + union type)
271
+ → I prefix on interfaces
272
+ ```
@@ -0,0 +1,48 @@
1
+ import fs from 'fs-extra'
2
+
3
+ const catalog = await fs.readJson(new URL('../compatibility/profiles.json', import.meta.url))
4
+ const scope = process.argv.find((arg) => arg.startsWith('--scope='))?.split('=')[1] || 'smoke'
5
+ if (!['smoke', 'full'].includes(scope)) throw new Error('--scope must be smoke or full')
6
+
7
+ const cases = [
8
+ 'nextjs-none', 'nextjs-supabase', 'nextjs-springboot', 'nextjs-postgres', 'nextjs-laravel',
9
+ 'react-none', 'react-supabase', 'react-springboot', 'react-laravel',
10
+ 'react-native-none', 'react-native-supabase', 'react-native-springboot', 'react-native-laravel',
11
+ 'laravel-api', 'laravel-blade', 'laravel-livewire', 'laravel-inertia-react',
12
+ ]
13
+
14
+ function authChoices(caseName) {
15
+ const mobile = caseName.startsWith('react-native')
16
+ const nonBrowser = mobile || caseName === 'laravel-api'
17
+ const base = ['not-yet', 'none'].map((authentication) => ({ authentication, audience: nonBrowser ? 'multi-client' : 'website' }))
18
+ if (caseName.includes('supabase')) base.push({ authentication: 'yes', audience: mobile ? 'multi-client' : 'website' })
19
+ if (caseName.includes('springboot') || caseName.includes('laravel')) {
20
+ if (caseName.startsWith('laravel-') && caseName !== 'laravel-api') base.push({ authentication: 'yes', audience: 'website' })
21
+ else if (mobile || caseName === 'laravel-api') base.push({ authentication: 'yes', audience: 'multi-client' })
22
+ else base.push({ authentication: 'yes', audience: 'website' }, { authentication: 'yes', audience: 'multi-client' })
23
+ }
24
+ return base
25
+ }
26
+
27
+ const current = catalog.defaultProfile
28
+ const full = Object.entries(catalog.profiles).filter(([profile]) => profile === current).flatMap(([profile, versions]) =>
29
+ cases.flatMap((caseName) => ['small', 'medium', 'large'].flatMap((architecture) =>
30
+ authChoices(caseName).map((auth) => ({ profile, case: caseName, architecture, ...auth, node: versions.runtimes.node, java: versions.runtimes.java, php: versions.runtimes.php })))),
31
+ )
32
+
33
+ const smokeSelections = [
34
+ ['nextjs-none', 'small', 'not-yet', 'website'],
35
+ ['nextjs-supabase', 'medium', 'yes', 'website'],
36
+ ['react-springboot', 'large', 'yes', 'website'],
37
+ ['react-native-supabase', 'medium', 'yes', 'multi-client'],
38
+ ['react-native-springboot', 'small', 'yes', 'multi-client'],
39
+ ['laravel-api', 'medium', 'not-yet', 'multi-client'],
40
+ ['laravel-blade', 'small', 'yes', 'website'],
41
+ ['laravel-livewire', 'medium', 'none', 'website'],
42
+ ['laravel-inertia-react', 'large', 'yes', 'website'],
43
+ ['react-laravel', 'medium', 'yes', 'website'],
44
+ ]
45
+ const smoke = full.filter((entry) => entry.profile === current && smokeSelections.some(([caseName, architecture, authentication, audience]) =>
46
+ entry.case === caseName && entry.architecture === architecture && entry.authentication === authentication && entry.audience === audience))
47
+
48
+ process.stdout.write(JSON.stringify(scope === 'full' ? full : smoke))
@@ -0,0 +1,76 @@
1
+ import fs from 'fs-extra'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { loadCatalog, resolveStack } from '../lib/catalog.js'
5
+ import { buildRulesIndex } from '../lib/playbooks.js'
6
+
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
8
+ const playbooksDir = path.join(root, 'playbooks')
9
+ const catalog = await loadCatalog(playbooksDir)
10
+ const errors = []
11
+
12
+ for (const manifest of catalog.manifests) {
13
+ for (const entry of manifest._playbooks || []) {
14
+ const relative = typeof entry === 'string' ? entry : entry.file
15
+ if (!await fs.pathExists(path.join(playbooksDir, relative))) {
16
+ errors.push(`${manifest.id}: missing playbook ${relative}`)
17
+ }
18
+ }
19
+ }
20
+
21
+ const architectures = ['small', 'medium', 'large']
22
+ const authIntents = ['not-yet', 'none']
23
+ for (const frontend of catalog.frontends) {
24
+ for (const backend of frontend.appliesTo?.backend || []) {
25
+ const authCases = [...authIntents]
26
+ if (['supabase', 'springboot'].includes(backend)) authCases.push('yes')
27
+ for (const architecture of architectures) {
28
+ for (const authentication of authCases) {
29
+ const audiences = authentication === 'yes' && backend === 'springboot'
30
+ ? ['website', 'multi-client']
31
+ : [frontend.platform === 'mobile' ? 'multi-client' : 'website']
32
+ for (const authAudience of audiences) {
33
+ const stack = resolveStack({
34
+ frontend: frontend.id, backend, architecture, authentication, authAudience,
35
+ styling: frontend.stylingOptions?.[0], testing: 'basic', githubActions: true,
36
+ }, catalog)
37
+ try { await buildRulesIndex(stack, catalog, playbooksDir) }
38
+ catch (error) { errors.push(`${frontend.id}/${backend}/${architecture}/${authentication}/${authAudience}: ${error.message}`) }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ const markdownFiles = (await fs.readdir(playbooksDir, { recursive: true, withFileTypes: true }))
46
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
47
+ .map((entry) => path.join(entry.parentPath, entry.name))
48
+ for (const file of markdownFiles) {
49
+ const content = await fs.readFile(file, 'utf8')
50
+ const relative = path.relative(playbooksDir, file)
51
+ const limit = relative.startsWith('stack/') || relative.startsWith('platform/') || relative.startsWith('capabilities/') ? 250 : 650
52
+ if (content.split('\n').length > limit) errors.push(`${relative}: exceeds ${limit} lines`)
53
+ for (const match of content.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
54
+ const target = match[1].split('#')[0]
55
+ if (!target || /^(?:https?:|mailto:)/.test(target)) continue
56
+ if (!await fs.pathExists(path.resolve(path.dirname(file), target))) errors.push(`${relative}: broken link ${match[1]}`)
57
+ }
58
+ }
59
+
60
+ const staleClaims = ['stack/nextjs.md', 'stack/react-vite.md', 'stack/react-native.md', 'stack/springboot.md', 'database/supabase.md', 'folder-structure.md']
61
+ for (const directory of [path.join(root, 'README.md'), path.join(root, 'docs'), playbooksDir]) {
62
+ const stats = await fs.stat(directory)
63
+ const files = stats.isDirectory()
64
+ ? (await fs.readdir(directory, { recursive: true, withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith('.md')).map((entry) => path.join(entry.parentPath, entry.name))
65
+ : [directory]
66
+ for (const file of files) {
67
+ const content = await fs.readFile(file, 'utf8')
68
+ for (const stale of staleClaims) if (content.includes(stale)) errors.push(`${path.relative(root, file)}: stale reference ${stale}`)
69
+ }
70
+ }
71
+
72
+ if (errors.length) {
73
+ console.error(errors.join('\n'))
74
+ process.exit(1)
75
+ }
76
+ console.log(`Validated ${catalog.manifests.length} manifests and ${markdownFiles.length} playbooks across the architecture/authentication matrix.`)
@@ -0,0 +1,153 @@
1
+ import fs from 'fs-extra'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { spawnSync } from 'node:child_process'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { generateProject } from '../lib/generator.js'
7
+
8
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
9
+ const args = Object.fromEntries(process.argv.slice(2).map((arg) => {
10
+ const [key, value = 'true'] = arg.replace(/^--/, '').split('=')
11
+ return [key, value]
12
+ }))
13
+
14
+ const cases = {
15
+ 'nextjs-none': { frontend: 'nextjs', backend: 'none', styling: 'tailwind' },
16
+ 'nextjs-supabase': { frontend: 'nextjs', backend: 'supabase', styling: 'tailwind' },
17
+ 'nextjs-springboot': { frontend: 'nextjs', backend: 'springboot', styling: 'css-modules', packageName: 'com.example' },
18
+ 'nextjs-postgres': { frontend: 'nextjs', backend: 'postgres', styling: 'tailwind' },
19
+ 'nextjs-laravel': { frontend: 'nextjs', backend: 'laravel', styling: 'tailwind', applicationShape: 'separate' },
20
+ 'react-supabase': { frontend: 'react', backend: 'supabase', styling: 'tailwind' },
21
+ 'react-none': { frontend: 'react', backend: 'none', styling: 'css-modules' },
22
+ 'react-springboot': { frontend: 'react', backend: 'springboot', styling: 'css-modules', packageName: 'com.example' },
23
+ 'react-laravel': { frontend: 'react', backend: 'laravel', styling: 'tailwind', applicationShape: 'separate' },
24
+ 'react-native-supabase': { frontend: 'react-native', backend: 'supabase' },
25
+ 'react-native-springboot': { frontend: 'react-native', backend: 'springboot', packageName: 'com.example' },
26
+ 'react-native-none': { frontend: 'react-native', backend: 'none' },
27
+ 'react-native-laravel': { frontend: 'react-native', backend: 'laravel', applicationShape: 'mobile' },
28
+ 'laravel-api': { frontend: 'no-frontend', backend: 'laravel', applicationShape: 'api' },
29
+ 'laravel-blade': { frontend: 'laravel-ui', backend: 'laravel', applicationShape: 'fullstack', laravelUi: 'blade', styling: 'tailwind' },
30
+ 'laravel-livewire': { frontend: 'laravel-ui', backend: 'laravel', applicationShape: 'fullstack', laravelUi: 'livewire', styling: 'tailwind' },
31
+ 'laravel-inertia-react': { frontend: 'laravel-ui', backend: 'laravel', applicationShape: 'fullstack', laravelUi: 'inertia-react', styling: 'tailwind' },
32
+ }
33
+
34
+ if (!args.profile || !cases[args.case]) {
35
+ throw new Error(`Usage: node scripts/verify-generated.mjs --profile=<id> --case=<${Object.keys(cases).join('|')}> [--architecture=small|medium|large] [--authentication=yes|not-yet|none] [--auth-audience=website|multi-client]`)
36
+ }
37
+
38
+ const architecture = args.architecture || 'medium'
39
+ const authentication = args.authentication || 'not-yet'
40
+ const authAudience = args['auth-audience'] || (cases[args.case].frontend === 'react-native' ? 'multi-client' : 'website')
41
+ if (!['small', 'medium', 'large'].includes(architecture)) throw new Error(`Invalid architecture: ${architecture}`)
42
+ if (!['yes', 'not-yet', 'none'].includes(authentication)) throw new Error(`Invalid authentication: ${authentication}`)
43
+ if (!['website', 'multi-client'].includes(authAudience)) throw new Error(`Invalid auth audience: ${authAudience}`)
44
+
45
+ function run(command, commandArgs, cwd, extraEnv = {}) {
46
+ const result = spawnSync(command, commandArgs, {
47
+ cwd,
48
+ env: { ...process.env, ...extraEnv },
49
+ encoding: 'utf8',
50
+ stdio: 'inherit',
51
+ })
52
+ if (result.error) throw result.error
53
+ if (result.status !== 0) throw new Error(`${command} ${commandArgs.join(' ')} failed with status ${result.status}`)
54
+ }
55
+
56
+ const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'create-win-compat-'))
57
+ const selected = cases[args.case]
58
+ const projectName = `${args.case.replaceAll('react-native', 'expo')}-${architecture}-${authentication.replaceAll('-', '')}-${authAudience}`
59
+
60
+ try {
61
+ process.chdir(fixtureRoot)
62
+ await generateProject({
63
+ projectName,
64
+ projectDescription: `Compatibility verification for ${args.case}`,
65
+ compatibilityProfile: args.profile,
66
+ architecture,
67
+ authentication,
68
+ authAudience,
69
+ testing: selected.frontend === 'react-native' ? 'basic' : 'full',
70
+ docker: selected.frontend !== 'react-native' || selected.backend === 'laravel',
71
+ makefile: false,
72
+ githubActions: true,
73
+ expectedConcerns: [],
74
+ ...selected,
75
+ }, root)
76
+
77
+ const projectRoot = path.join(fixtureRoot, projectName)
78
+ const metadata = await fs.readJson(path.join(projectRoot, 'create-win-project.profile.json'))
79
+ if (metadata.compatibilityProfile.id !== args.profile || metadata.architectureProfile !== architecture ||
80
+ metadata.authentication.intent !== authentication || metadata.authentication.audience !== authAudience) {
81
+ throw new Error('Generated profile metadata does not match the requested matrix entry')
82
+ }
83
+ const packageRoot = selected.frontend === 'react' ? path.join(projectRoot, 'frontend') : projectRoot
84
+ const publicEnv = {
85
+ NEXT_PUBLIC_SUPABASE_URL: 'https://example.supabase.co',
86
+ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: 'test-publishable-key',
87
+ VITE_SUPABASE_URL: 'https://example.supabase.co',
88
+ VITE_SUPABASE_PUBLISHABLE_KEY: 'test-publishable-key',
89
+ EXPO_PUBLIC_SUPABASE_URL: 'https://example.supabase.co',
90
+ EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY: 'test-publishable-key',
91
+ NEXT_PUBLIC_API_URL: 'http://localhost:8080',
92
+ VITE_API_URL: 'http://localhost:8080',
93
+ EXPO_PUBLIC_API_URL: 'http://localhost:8080',
94
+ POSTGRES_USER: 'postgres',
95
+ POSTGRES_PASSWORD: 'compatibility-test',
96
+ POSTGRES_DB: projectName.replaceAll('-', '_'),
97
+ DATABASE_URL: `postgresql://postgres:compatibility-test@localhost:5432/${projectName.replaceAll('-', '_')}`,
98
+ SPRING_PROFILES_ACTIVE: 'test',
99
+ OIDC_ISSUER_URI: 'http://localhost:9090/realms/app',
100
+ OIDC_AUDIENCE: 'api',
101
+ AUTH0_DOMAIN: 'example.auth0.com',
102
+ AUTH0_AUDIENCE: 'https://api.example.test',
103
+ SPRING_SECURITY_USER_NAME: 'compat-user',
104
+ SPRING_SECURITY_USER_PASSWORD: 'compat-password',
105
+ }
106
+
107
+ if (await fs.pathExists(path.join(packageRoot, 'package.json'))) {
108
+ run('npm', ['install'], packageRoot)
109
+ const packageJson = await fs.readJson(path.join(packageRoot, 'package.json'))
110
+ if (packageJson.scripts.lint) run('npm', ['run', 'lint'], packageRoot, publicEnv)
111
+ if (packageJson.scripts.typecheck) run('npm', ['run', 'typecheck'], packageRoot, publicEnv)
112
+ if (packageJson.scripts.test) run('npm', ['test', '--', ...(selected.frontend === 'react-native' ? ['--runInBand'] : [])], packageRoot, publicEnv)
113
+
114
+ if (selected.frontend === 'react-native') {
115
+ run('npx', ['expo', 'install', '--check'], packageRoot, { ...publicEnv, CI: '1' })
116
+ run('npm', ['run', 'build', '--', '--platform', 'web'], packageRoot, publicEnv)
117
+ } else {
118
+ run('npm', ['run', 'build'], packageRoot, publicEnv)
119
+ if (packageJson.scripts['test:e2e']) {
120
+ run('npx', ['playwright', 'install', ...(process.env.CI ? ['--with-deps'] : []), 'chromium'], packageRoot)
121
+ run('npm', ['run', 'test:e2e'], packageRoot, publicEnv)
122
+ }
123
+ }
124
+ }
125
+
126
+ if (selected.backend === 'springboot') {
127
+ const backendRoot = path.join(projectRoot, 'backend')
128
+ run('./mvnw', ['--batch-mode', 'test'], backendRoot)
129
+ run('./mvnw', ['--batch-mode', 'package', '-DskipTests'], backendRoot)
130
+ }
131
+
132
+ if (selected.backend === 'laravel') {
133
+ const backendRoot = ['laravel-ui', 'no-frontend'].includes(selected.frontend) ? projectRoot : path.join(projectRoot, 'backend')
134
+ run('composer', ['install', '--no-interaction', '--prefer-dist'], backendRoot)
135
+ run('docker', ['compose', 'up', '-d', '--wait', 'db'], projectRoot, publicEnv)
136
+ try {
137
+ run('composer', ['check'], backendRoot, { ...publicEnv, DB_HOST: '127.0.0.1', DB_PORT: '5432', DB_DATABASE: publicEnv.POSTGRES_DB, DB_USERNAME: publicEnv.POSTGRES_USER, DB_PASSWORD: publicEnv.POSTGRES_PASSWORD, APP_KEY: 'base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' })
138
+ } finally {
139
+ run('docker', ['compose', 'down', '--volumes'], projectRoot, publicEnv)
140
+ }
141
+ }
142
+
143
+ if (selected.frontend !== 'react-native') {
144
+ run('docker', ['compose', 'config'], projectRoot, publicEnv)
145
+ if (args.containers === 'true') {
146
+ run('docker', ['compose', 'build'], projectRoot, publicEnv)
147
+ if (selected.backend === 'postgres') run('docker', ['build', '-t', `${projectName}:compat`, '.'], projectRoot, publicEnv)
148
+ }
149
+ }
150
+ } finally {
151
+ process.chdir(root)
152
+ await fs.remove(fixtureRoot)
153
+ }
@@ -0,0 +1,47 @@
1
+ # AGENTS.md
2
+
3
+ > Small operating contract for agents. Project intent lives in `CONTEXT.md`; task guidance is routed through `RULES.md`.
4
+
5
+ ## Project
6
+
7
+ {{PROJECT_NAME}} — {{PROJECT_DESCRIPTION}}
8
+
9
+ - Stack: `{{STACK}}`
10
+ - Platform: `{{PLATFORM}}`
11
+ - Architecture profile: `{{ARCHITECTURE}}`
12
+
13
+ ## Commands
14
+
15
+ - Install: `npm install` initially; use `npm ci` after committing the lockfile.
16
+ - Develop: `npm run dev`
17
+ - Validate: `npm run lint && npm run typecheck && npm run test --if-present && npm run build`
18
+ - End-to-end: `npm run test:e2e --if-present`
19
+ - Spring backend, when present: `cd backend && ./mvnw --batch-mode test` (`mvnw.cmd` on Windows)
20
+
21
+ ## Required workflow
22
+
23
+ 1. Read `CONTEXT.md` and inspect neighboring implementation and tests.
24
+ 2. Find the concern in `RULES.md`; open only its linked playbook section.
25
+ 3. State assumptions when product behavior is ambiguous.
26
+ 4. Make the smallest coherent change and add risk-appropriate tests.
27
+ 5. Run lint, typecheck, relevant tests, and a production build before completion.
28
+
29
+ ## Always-on constraints
30
+
31
+ {{CONSTRAINTS}}
32
+
33
+ - Validate untrusted input at the server boundary.
34
+ - Authenticate and authorize separately; enforce authorization near data and side effects.
35
+ - Never expose server secrets through public environment prefixes or client modules.
36
+
37
+ ## Authority boundaries
38
+
39
+ - Do not deploy, publish, merge, push, send messages, or modify production data unless explicitly asked.
40
+ - Do not delete user work or weaken tests/security controls to make a check pass.
41
+
42
+ ## Definition of done
43
+
44
+ - Acceptance behavior works and has appropriate coverage.
45
+ - Validation commands pass, or the exact blocker is reported.
46
+ - Errors do not leak secrets, personal data, or internal details.
47
+ - Relevant architecture, API, and environment documentation is updated.
@@ -0,0 +1,48 @@
1
+ # AGENTS.md
2
+
3
+ > Small operating contract for agents. Project intent lives in `CONTEXT.md`; task guidance is routed through `RULES.md`.
4
+
5
+ ## Project
6
+
7
+ {{PROJECT_NAME}} — {{PROJECT_DESCRIPTION}}
8
+
9
+ - Stack: `{{STACK}}`
10
+ - Platform: `{{PLATFORM}}`
11
+
12
+ ## Commands
13
+
14
+ - Install: `npm install` initially; use `npm ci` after committing the lockfile.
15
+ - Develop: `npm run dev`
16
+ - Typecheck: `npm run typecheck`
17
+ - Tests: `npm test -- --runInBand` when configured.
18
+ - Export check: `npm run build -- --platform web`
19
+ - Spring backend, when present: `cd backend && ./mvnw --batch-mode test` (`mvnw.cmd` on Windows)
20
+
21
+ ## Required workflow
22
+
23
+ 1. Read `CONTEXT.md` and inspect neighboring screens, hooks, services, and tests.
24
+ 2. Find the concern in `RULES.md`; open only its linked playbook section.
25
+ 3. State assumptions when behavior is ambiguous.
26
+ 4. Make the smallest coherent change and test it on the affected platform.
27
+ 5. Run typecheck, relevant tests, and an export/build check.
28
+
29
+ ## Always-on constraints
30
+
31
+ {{CONSTRAINTS}}
32
+
33
+ - Keep screens thin; side effects belong in hooks and services.
34
+ - Store sensitive credentials with platform secure storage, never AsyncStorage.
35
+ - Public-prefixed Expo values are bundled into the application and are never secrets.
36
+ - Client navigation guards are user experience, not server authorization.
37
+
38
+ ## Authority boundaries
39
+
40
+ - Do not publish builds, deploy, merge, push, or modify production data unless explicitly asked.
41
+ - Do not add native modules or eject without explaining the impact.
42
+
43
+ ## Definition of done
44
+
45
+ - Acceptance behavior works on the affected platform with appropriate tests.
46
+ - Typecheck, tests, and export/build pass, or the exact blocker is reported.
47
+ - Loading, offline, error, safe-area, keyboard, and accessibility behavior are considered.
48
+ - Relevant docs are updated.