@better-fullstack/types 2.6.0 → 2.6.2
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/dist/{defaults-7VECSARb.d.mts → defaults-CZeSVSu8.d.mts} +3 -3
- package/dist/defaults-CZeSVSu8.d.mts.map +1 -0
- package/dist/{defaults-Bp46Ajyg.mjs → defaults-DTS30ICD.mjs} +2 -2
- package/dist/defaults-DTS30ICD.mjs.map +1 -0
- package/dist/defaults.d.mts +3 -3
- package/dist/defaults.mjs +1 -1
- package/dist/index.d.mts +1258 -11
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +289 -9
- package/dist/index.mjs.map +1 -1
- package/dist/json-schema.d.mts +324 -324
- package/dist/json-schema.d.mts.map +1 -1
- package/dist/json-schema.mjs +2 -2
- package/dist/json-schema.mjs.map +1 -1
- package/dist/{schemas-CY0MaZmU.d.mts → schemas-82xJXQrZ.d.mts} +680 -598
- package/dist/schemas-82xJXQrZ.d.mts.map +1 -0
- package/dist/{schemas-CH1UszP5.mjs → schemas-cgmdgudQ.mjs} +41 -4
- package/dist/schemas-cgmdgudQ.mjs.map +1 -0
- package/dist/schemas.d.mts +2 -2
- package/dist/schemas.mjs +2 -2
- package/dist/{stack-graph-DkJ944IX.d.mts → stack-graph-DG53F1Q2.d.mts} +26 -4
- package/dist/stack-graph-DG53F1Q2.d.mts.map +1 -0
- package/dist/{stack-graph-AWbdUrN3.mjs → stack-graph-Oj-P0cij.mjs} +91 -14
- package/dist/stack-graph-Oj-P0cij.mjs.map +1 -0
- package/dist/stack-graph.d.mts +4 -4
- package/dist/stack-graph.mjs +3 -3
- package/dist/{stack-translation-C9yYLNwM.d.mts → stack-translation-0OwfhcBf.d.mts} +59 -9
- package/dist/stack-translation-0OwfhcBf.d.mts.map +1 -0
- package/dist/{stack-translation-Cn6zGJ5q.mjs → stack-translation-49iba9M5.mjs} +161 -30
- package/dist/stack-translation-49iba9M5.mjs.map +1 -0
- package/dist/stack-translation.d.mts +4 -4
- package/dist/stack-translation.mjs +3 -3
- package/dist/telemetry-C9qQ80l9.mjs +2270 -0
- package/dist/telemetry-C9qQ80l9.mjs.map +1 -0
- package/dist/telemetry-CUtrUTe9.d.mts +36 -0
- package/dist/telemetry-CUtrUTe9.d.mts.map +1 -0
- package/dist/telemetry.d.mts +2 -0
- package/dist/telemetry.mjs +6 -0
- package/dist/{types-fGT5qf7O.d.mts → types-CfEIdtDo.d.mts} +3 -3
- package/dist/types-CfEIdtDo.d.mts.map +1 -0
- package/dist/types.d.mts +2 -2
- package/package.json +5 -1
- package/dist/defaults-7VECSARb.d.mts.map +0 -1
- package/dist/defaults-Bp46Ajyg.mjs.map +0 -1
- package/dist/schemas-CH1UszP5.mjs.map +0 -1
- package/dist/schemas-CY0MaZmU.d.mts.map +0 -1
- package/dist/stack-graph-AWbdUrN3.mjs.map +0 -1
- package/dist/stack-graph-DkJ944IX.d.mts.map +0 -1
- package/dist/stack-translation-C9yYLNwM.d.mts.map +0 -1
- package/dist/stack-translation-Cn6zGJ5q.mjs.map +0 -1
- package/dist/types-fGT5qf7O.d.mts.map +0 -1
|
@@ -0,0 +1,2270 @@
|
|
|
1
|
+
import { Ht as EcosystemSchema, Oi as ProjectConfigSchema, eo as StackPartRoleSchema } from "./schemas-cgmdgudQ.mjs";
|
|
2
|
+
import { S as stackSelectionToProjectConfig, bt as getCategoryOptionIds, g as normalizeStackSelection, l as cloneDefaultStackSelection, pt as OPTION_CATEGORY_METADATA, xt as getCategoryOrderForEcosystem } from "./stack-translation-49iba9M5.mjs";
|
|
3
|
+
import { a as STACK_TOOL_DEFINITIONS, c as formatStackPartSpec, g as legacyProjectConfigToStackParts, w as validateStackParts } from "./stack-graph-Oj-P0cij.mjs";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/capabilities/evidence.ts
|
|
7
|
+
const CAPABILITY_EVIDENCE_SCHEMA_VERSION = 1;
|
|
8
|
+
const CAPABILITY_EVIDENCE_LEVEL_IDS = [
|
|
9
|
+
"listed",
|
|
10
|
+
"generated",
|
|
11
|
+
"build-verified",
|
|
12
|
+
"runtime-verified"
|
|
13
|
+
];
|
|
14
|
+
const CapabilityEvidenceLevelSchema = z.enum(CAPABILITY_EVIDENCE_LEVEL_IDS);
|
|
15
|
+
const CAPABILITY_EVIDENCE_LEVELS = [
|
|
16
|
+
{
|
|
17
|
+
id: "listed",
|
|
18
|
+
label: "Listed",
|
|
19
|
+
proves: "The Stack Part exists in the canonical schema and can be checked for compatibility.",
|
|
20
|
+
doesNotProve: "The generator emits files or that a generated project installs, builds, or runs.",
|
|
21
|
+
requiredEvidence: ["Canonical schema entry", "Executable compatibility coverage"]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "generated",
|
|
25
|
+
label: "Generated",
|
|
26
|
+
proves: "The current generator emits the declared files and structural assertions pass for an exact version.",
|
|
27
|
+
doesNotProve: "Dependencies install or that the generated project builds or runs.",
|
|
28
|
+
requiredEvidence: [
|
|
29
|
+
"Listed evidence",
|
|
30
|
+
"SHA-bound generator receipt",
|
|
31
|
+
"Structural assertions"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "build-verified",
|
|
36
|
+
label: "Build verified",
|
|
37
|
+
proves: "A clean generated project installs dependencies and completes its declared compile, build, type-check, or test stages on recorded toolchains.",
|
|
38
|
+
doesNotProve: "A server starts or that a user-visible protocol and behavior work at runtime.",
|
|
39
|
+
requiredEvidence: [
|
|
40
|
+
"Generated evidence",
|
|
41
|
+
"Clean install",
|
|
42
|
+
"Declared build stages",
|
|
43
|
+
"Exact commit and toolchain receipt"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "runtime-verified",
|
|
48
|
+
label: "Runtime verified",
|
|
49
|
+
proves: "A build-verified project starts and passes declared live protocol and behavior assertions.",
|
|
50
|
+
doesNotProve: "Behavior outside the recorded recipe, assertions, environment, or evidence age.",
|
|
51
|
+
requiredEvidence: [
|
|
52
|
+
"Build-verified evidence",
|
|
53
|
+
"Live process or device exercise",
|
|
54
|
+
"Declared protocol assertions",
|
|
55
|
+
"Declared behavior assertions"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
function capabilityEvidenceLevel(proof) {
|
|
60
|
+
if (!proof.listed) return null;
|
|
61
|
+
if (!proof.generated) return "listed";
|
|
62
|
+
if (!proof.buildVerified) return "generated";
|
|
63
|
+
if (!proof.runtimeVerified) return "build-verified";
|
|
64
|
+
return "runtime-verified";
|
|
65
|
+
}
|
|
66
|
+
function capCapabilityEvidenceLevel(claimed, allowed) {
|
|
67
|
+
if (!claimed || !allowed) return null;
|
|
68
|
+
const claimedIndex = CAPABILITY_EVIDENCE_LEVEL_IDS.indexOf(claimed);
|
|
69
|
+
const allowedIndex = CAPABILITY_EVIDENCE_LEVEL_IDS.indexOf(allowed);
|
|
70
|
+
return CAPABILITY_EVIDENCE_LEVEL_IDS[Math.min(claimedIndex, allowedIndex)] ?? null;
|
|
71
|
+
}
|
|
72
|
+
function capabilityEvidenceAtLeast(actual, required) {
|
|
73
|
+
return actual !== null && CAPABILITY_EVIDENCE_LEVEL_IDS.indexOf(actual) >= CAPABILITY_EVIDENCE_LEVEL_IDS.indexOf(required);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/capabilities/capability-inventory.ts
|
|
78
|
+
const CAPABILITY_INVENTORY_SCHEMA_VERSION = 1;
|
|
79
|
+
const CAPABILITY_RECEIPT_SCHEMA_VERSION = 1;
|
|
80
|
+
const CAPABILITY_RECEIPT_MAX_AGE_DAYS = 30;
|
|
81
|
+
const CAPABILITY_MATURITY_IDS = [
|
|
82
|
+
"stable",
|
|
83
|
+
"experimental",
|
|
84
|
+
"quarantined"
|
|
85
|
+
];
|
|
86
|
+
const CAPABILITY_FRESHNESS_IDS = [
|
|
87
|
+
"unverified",
|
|
88
|
+
"current",
|
|
89
|
+
"stale",
|
|
90
|
+
"producer-mismatch",
|
|
91
|
+
"failed",
|
|
92
|
+
"quarantined"
|
|
93
|
+
];
|
|
94
|
+
const MOBILE_RUNTIME_FLAGS = [
|
|
95
|
+
"--part",
|
|
96
|
+
"mobile:react-native:native-bare",
|
|
97
|
+
"--part",
|
|
98
|
+
"backend:typescript:hono",
|
|
99
|
+
"--part",
|
|
100
|
+
"backend.runtime:typescript:bun",
|
|
101
|
+
"--part",
|
|
102
|
+
"backend.api:typescript:trpc",
|
|
103
|
+
"--addons",
|
|
104
|
+
"none",
|
|
105
|
+
"--examples",
|
|
106
|
+
"none",
|
|
107
|
+
"--ai-docs",
|
|
108
|
+
"none",
|
|
109
|
+
"--package-manager",
|
|
110
|
+
"bun",
|
|
111
|
+
"--no-install",
|
|
112
|
+
"--no-git"
|
|
113
|
+
];
|
|
114
|
+
const GOLDEN_RUNTIME_RECIPES = [
|
|
115
|
+
{
|
|
116
|
+
id: "typescript",
|
|
117
|
+
name: "React and Hono local service",
|
|
118
|
+
definitionVersion: 1,
|
|
119
|
+
sourceBuildProofCaseId: "typescript",
|
|
120
|
+
projectName: "proof-typescript",
|
|
121
|
+
generationInputs: { preset: "react-hono" },
|
|
122
|
+
requiredToolchains: ["node", "bun"],
|
|
123
|
+
buildSteps: [
|
|
124
|
+
"scaffold",
|
|
125
|
+
"install",
|
|
126
|
+
"build",
|
|
127
|
+
"typecheck"
|
|
128
|
+
],
|
|
129
|
+
stackParts: [
|
|
130
|
+
"frontend:typescript:tanstack-router",
|
|
131
|
+
"backend:typescript:hono",
|
|
132
|
+
"backend.runtime:typescript:bun"
|
|
133
|
+
],
|
|
134
|
+
coveredOptions: [
|
|
135
|
+
{
|
|
136
|
+
ecosystem: "typescript",
|
|
137
|
+
category: "webFrontend",
|
|
138
|
+
optionId: "tanstack-router"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
ecosystem: "typescript",
|
|
142
|
+
category: "backend",
|
|
143
|
+
optionId: "hono"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
ecosystem: "typescript",
|
|
147
|
+
category: "runtime",
|
|
148
|
+
optionId: "bun"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
ecosystem: "typescript",
|
|
152
|
+
category: "api",
|
|
153
|
+
optionId: "graphql-yoga"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
ecosystem: "typescript",
|
|
157
|
+
category: "database",
|
|
158
|
+
optionId: "sqlite"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
ecosystem: "typescript",
|
|
162
|
+
category: "orm",
|
|
163
|
+
optionId: "drizzle"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
ecosystem: "typescript",
|
|
167
|
+
category: "auth",
|
|
168
|
+
optionId: "better-auth"
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
runtime: {
|
|
172
|
+
name: "Hono GraphQL and Better Auth boundaries",
|
|
173
|
+
processCwd: ".",
|
|
174
|
+
setupCommands: [[
|
|
175
|
+
"bun",
|
|
176
|
+
"run",
|
|
177
|
+
"db:push"
|
|
178
|
+
]],
|
|
179
|
+
command: [
|
|
180
|
+
"bun",
|
|
181
|
+
"run",
|
|
182
|
+
"--cwd",
|
|
183
|
+
"apps/server",
|
|
184
|
+
"dev"
|
|
185
|
+
],
|
|
186
|
+
request: {
|
|
187
|
+
url: "http://127.0.0.1:3000/graphql",
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: { "Content-Type": "application/json" },
|
|
190
|
+
body: "{\"query\":\"{ health }\"}"
|
|
191
|
+
},
|
|
192
|
+
expectedStatus: 200,
|
|
193
|
+
bodyIncludes: ["\"health\":\"OK\""],
|
|
194
|
+
followupAssertions: [{
|
|
195
|
+
name: "Better Auth email sign-up",
|
|
196
|
+
request: {
|
|
197
|
+
url: "http://127.0.0.1:3000/api/auth/sign-up/email",
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: { "Content-Type": "application/json" },
|
|
200
|
+
body: "{\"name\":\"Runtime Proof\",\"email\":\"runtime-proof@example.test\",\"password\":\"runtime-proof-password\"}"
|
|
201
|
+
},
|
|
202
|
+
expectedStatus: 200,
|
|
203
|
+
bodyIncludes: ["runtime-proof@example.test"]
|
|
204
|
+
}],
|
|
205
|
+
timeoutMs: 12e4,
|
|
206
|
+
limitation: "Exercises the generated Hono process and HTTP boundary, not browser rendering."
|
|
207
|
+
},
|
|
208
|
+
maintainer: "@Marve10s"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "react-native",
|
|
212
|
+
name: "React Native with a Hono service",
|
|
213
|
+
definitionVersion: 1,
|
|
214
|
+
sourceBuildProofCaseId: "react-native",
|
|
215
|
+
projectName: "proof-react-native",
|
|
216
|
+
generationInputs: { flags: MOBILE_RUNTIME_FLAGS },
|
|
217
|
+
requiredToolchains: [
|
|
218
|
+
"node",
|
|
219
|
+
"bun",
|
|
220
|
+
"bunx"
|
|
221
|
+
],
|
|
222
|
+
buildSteps: [
|
|
223
|
+
"scaffold",
|
|
224
|
+
"install",
|
|
225
|
+
"typecheck",
|
|
226
|
+
"build",
|
|
227
|
+
"backend-build"
|
|
228
|
+
],
|
|
229
|
+
stackParts: ["mobile:react-native:native-bare", "backend:typescript:hono"],
|
|
230
|
+
coveredOptions: [
|
|
231
|
+
{
|
|
232
|
+
ecosystem: "react-native",
|
|
233
|
+
category: "nativeFrontend",
|
|
234
|
+
optionId: "native-bare"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
ecosystem: "react-native",
|
|
238
|
+
category: "mobileNavigation",
|
|
239
|
+
optionId: "expo-router"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
ecosystem: "typescript",
|
|
243
|
+
category: "backend",
|
|
244
|
+
optionId: "hono"
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
runtimeCoveredOptions: [{
|
|
248
|
+
ecosystem: "typescript",
|
|
249
|
+
category: "backend",
|
|
250
|
+
optionId: "hono"
|
|
251
|
+
}],
|
|
252
|
+
runtime: {
|
|
253
|
+
name: "Generated mobile backend response",
|
|
254
|
+
processCwd: "apps/server",
|
|
255
|
+
command: [
|
|
256
|
+
"bun",
|
|
257
|
+
"run",
|
|
258
|
+
"dev"
|
|
259
|
+
],
|
|
260
|
+
request: {
|
|
261
|
+
url: "http://127.0.0.1:3000/",
|
|
262
|
+
method: "GET"
|
|
263
|
+
},
|
|
264
|
+
expectedStatus: 200,
|
|
265
|
+
bodyIncludes: ["OK"],
|
|
266
|
+
timeoutMs: 12e4,
|
|
267
|
+
limitation: "Exercises the generated backend boundary. It does not launch a native device UI."
|
|
268
|
+
},
|
|
269
|
+
maintainer: "@Marve10s"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: "rust",
|
|
273
|
+
name: "Axum and SeaORM on local SQLite",
|
|
274
|
+
definitionVersion: 1,
|
|
275
|
+
sourceBuildProofCaseId: "rust",
|
|
276
|
+
projectName: "proof-rust",
|
|
277
|
+
generationInputs: { preset: "rust-axum-seaorm" },
|
|
278
|
+
requiredToolchains: ["node", "cargo"],
|
|
279
|
+
buildSteps: [
|
|
280
|
+
"scaffold",
|
|
281
|
+
"fetch",
|
|
282
|
+
"build"
|
|
283
|
+
],
|
|
284
|
+
stackParts: ["backend:rust:axum", "backend.orm:rust:sea-orm"],
|
|
285
|
+
coveredOptions: [
|
|
286
|
+
{
|
|
287
|
+
ecosystem: "rust",
|
|
288
|
+
category: "rustWebFramework",
|
|
289
|
+
optionId: "axum"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
ecosystem: "rust",
|
|
293
|
+
category: "rustOrm",
|
|
294
|
+
optionId: "sea-orm"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
ecosystem: "rust",
|
|
298
|
+
category: "rustCli",
|
|
299
|
+
optionId: "clap"
|
|
300
|
+
}
|
|
301
|
+
],
|
|
302
|
+
runtime: {
|
|
303
|
+
name: "Clap start and check commands against an Axum database health response",
|
|
304
|
+
processCwd: ".",
|
|
305
|
+
setupCommands: [["./target/debug/cli", "info"]],
|
|
306
|
+
command: [
|
|
307
|
+
"./target/debug/cli",
|
|
308
|
+
"start",
|
|
309
|
+
"--host",
|
|
310
|
+
"127.0.0.1",
|
|
311
|
+
"--port",
|
|
312
|
+
"3000"
|
|
313
|
+
],
|
|
314
|
+
env: {
|
|
315
|
+
DATABASE_URL: "sqlite://runtime-proof.db?mode=rwc",
|
|
316
|
+
PORT: "3000"
|
|
317
|
+
},
|
|
318
|
+
request: {
|
|
319
|
+
url: "http://127.0.0.1:3000/health",
|
|
320
|
+
method: "GET"
|
|
321
|
+
},
|
|
322
|
+
expectedStatus: 200,
|
|
323
|
+
bodyIncludes: ["\"status\":\"ok\"", "\"database\":\"connected\""],
|
|
324
|
+
followupCommands: [{
|
|
325
|
+
name: "Clap health check command",
|
|
326
|
+
command: [
|
|
327
|
+
"./target/debug/cli",
|
|
328
|
+
"check",
|
|
329
|
+
"--url",
|
|
330
|
+
"http://127.0.0.1:3000/health"
|
|
331
|
+
],
|
|
332
|
+
outputIncludes: ["\"status\":\"ok\"", "\"database\":\"connected\""]
|
|
333
|
+
}],
|
|
334
|
+
timeoutMs: 18e4,
|
|
335
|
+
limitation: "Exercises the generated Clap info, start, and check commands with Axum and SeaORM on local SQLite, not a network database."
|
|
336
|
+
},
|
|
337
|
+
maintainer: "@Marve10s"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: "python",
|
|
341
|
+
name: "FastAPI and SQLAlchemy local service",
|
|
342
|
+
definitionVersion: 1,
|
|
343
|
+
sourceBuildProofCaseId: "python",
|
|
344
|
+
projectName: "proof-python",
|
|
345
|
+
generationInputs: { preset: "python-fastapi-sqlalchemy" },
|
|
346
|
+
requiredToolchains: [
|
|
347
|
+
"node",
|
|
348
|
+
"python",
|
|
349
|
+
"uv"
|
|
350
|
+
],
|
|
351
|
+
buildSteps: [
|
|
352
|
+
"scaffold",
|
|
353
|
+
"install",
|
|
354
|
+
"compile-check"
|
|
355
|
+
],
|
|
356
|
+
stackParts: ["backend:python:fastapi", "backend.orm:python:sqlalchemy"],
|
|
357
|
+
coveredOptions: [
|
|
358
|
+
{
|
|
359
|
+
ecosystem: "python",
|
|
360
|
+
category: "pythonWebFramework",
|
|
361
|
+
optionId: "fastapi"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
ecosystem: "python",
|
|
365
|
+
category: "pythonOrm",
|
|
366
|
+
optionId: "sqlalchemy"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
ecosystem: "python",
|
|
370
|
+
category: "pythonValidation",
|
|
371
|
+
optionId: "pydantic"
|
|
372
|
+
}
|
|
373
|
+
],
|
|
374
|
+
runtime: {
|
|
375
|
+
name: "FastAPI health response",
|
|
376
|
+
processCwd: ".",
|
|
377
|
+
command: [
|
|
378
|
+
"uv",
|
|
379
|
+
"run",
|
|
380
|
+
"uvicorn",
|
|
381
|
+
"app.main:app",
|
|
382
|
+
"--host",
|
|
383
|
+
"127.0.0.1",
|
|
384
|
+
"--port",
|
|
385
|
+
"8000"
|
|
386
|
+
],
|
|
387
|
+
request: {
|
|
388
|
+
url: "http://127.0.0.1:8000/health",
|
|
389
|
+
method: "GET"
|
|
390
|
+
},
|
|
391
|
+
expectedStatus: 200,
|
|
392
|
+
bodyIncludes: ["\"status\":\"healthy\""],
|
|
393
|
+
timeoutMs: 12e4,
|
|
394
|
+
limitation: "Exercises FastAPI through HTTP. It does not prove external service selections."
|
|
395
|
+
},
|
|
396
|
+
maintainer: "@Marve10s"
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "go",
|
|
400
|
+
name: "Gin and GORM local service",
|
|
401
|
+
definitionVersion: 1,
|
|
402
|
+
sourceBuildProofCaseId: "go",
|
|
403
|
+
projectName: "proof-go",
|
|
404
|
+
generationInputs: { preset: "go-gin-gorm" },
|
|
405
|
+
requiredToolchains: ["node", "go"],
|
|
406
|
+
buildSteps: [
|
|
407
|
+
"scaffold",
|
|
408
|
+
"mod-tidy",
|
|
409
|
+
"build"
|
|
410
|
+
],
|
|
411
|
+
stackParts: ["backend:go:gin", "backend.orm:go:gorm"],
|
|
412
|
+
coveredOptions: [
|
|
413
|
+
{
|
|
414
|
+
ecosystem: "go",
|
|
415
|
+
category: "goWebFramework",
|
|
416
|
+
optionId: "gin"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
ecosystem: "go",
|
|
420
|
+
category: "goOrm",
|
|
421
|
+
optionId: "gorm"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
ecosystem: "go",
|
|
425
|
+
category: "goLogging",
|
|
426
|
+
optionId: "zap"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
ecosystem: "go",
|
|
430
|
+
category: "goApi",
|
|
431
|
+
optionId: "grpc-go"
|
|
432
|
+
}
|
|
433
|
+
],
|
|
434
|
+
runtime: {
|
|
435
|
+
name: "Gin health response with local GORM storage",
|
|
436
|
+
processCwd: ".",
|
|
437
|
+
setupCommands: [[
|
|
438
|
+
"go",
|
|
439
|
+
"test",
|
|
440
|
+
"./proto",
|
|
441
|
+
"-run",
|
|
442
|
+
"TestGreeterRPC"
|
|
443
|
+
]],
|
|
444
|
+
command: [
|
|
445
|
+
"go",
|
|
446
|
+
"run",
|
|
447
|
+
"cmd/server/main.go"
|
|
448
|
+
],
|
|
449
|
+
env: { PORT: "8080" },
|
|
450
|
+
request: {
|
|
451
|
+
url: "http://127.0.0.1:8080/health",
|
|
452
|
+
method: "GET"
|
|
453
|
+
},
|
|
454
|
+
expectedStatus: 200,
|
|
455
|
+
bodyIncludes: ["\"status\":\"ok\"", "Server is running"],
|
|
456
|
+
timeoutMs: 12e4,
|
|
457
|
+
limitation: "Exercises Gin and GORM with local SQLite, not a network database."
|
|
458
|
+
},
|
|
459
|
+
maintainer: "@Marve10s"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: "java",
|
|
463
|
+
name: "Spring Boot and jOOQ local service",
|
|
464
|
+
definitionVersion: 1,
|
|
465
|
+
sourceBuildProofCaseId: "java",
|
|
466
|
+
projectName: "proof-java",
|
|
467
|
+
generationInputs: { preset: "java-spring-maven" },
|
|
468
|
+
requiredToolchains: ["node", "java"],
|
|
469
|
+
buildSteps: ["scaffold", "test"],
|
|
470
|
+
stackParts: ["backend:java:spring-boot", "backend.buildTool:java:maven"],
|
|
471
|
+
coveredOptions: [
|
|
472
|
+
{
|
|
473
|
+
ecosystem: "java",
|
|
474
|
+
category: "javaWebFramework",
|
|
475
|
+
optionId: "spring-boot"
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
ecosystem: "java",
|
|
479
|
+
category: "javaBuildTool",
|
|
480
|
+
optionId: "maven"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
ecosystem: "java",
|
|
484
|
+
category: "javaOrm",
|
|
485
|
+
optionId: "jooq"
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
ecosystem: "java",
|
|
489
|
+
category: "javaLibraries",
|
|
490
|
+
optionId: "spring-actuator"
|
|
491
|
+
}
|
|
492
|
+
],
|
|
493
|
+
runtime: {
|
|
494
|
+
name: "Spring health and Actuator boundary",
|
|
495
|
+
processCwd: ".",
|
|
496
|
+
command: ["./mvnw", "spring-boot:run"],
|
|
497
|
+
env: { PORT: "8080" },
|
|
498
|
+
request: {
|
|
499
|
+
url: "http://127.0.0.1:8080/actuator/health",
|
|
500
|
+
method: "GET"
|
|
501
|
+
},
|
|
502
|
+
expectedStatus: 200,
|
|
503
|
+
bodyIncludes: ["\"status\":\"UP\""],
|
|
504
|
+
timeoutMs: 18e4,
|
|
505
|
+
limitation: "Exercises Spring Boot and Actuator with the generated local H2 configuration."
|
|
506
|
+
},
|
|
507
|
+
maintainer: "@Marve10s"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: "elixir",
|
|
511
|
+
name: "Phoenix API on local SQLite",
|
|
512
|
+
definitionVersion: 1,
|
|
513
|
+
sourceBuildProofCaseId: "elixir",
|
|
514
|
+
projectName: "proof-elixir",
|
|
515
|
+
generationInputs: { preset: "elixir-phoenix-api" },
|
|
516
|
+
requiredToolchains: ["node", "mix"],
|
|
517
|
+
buildSteps: [
|
|
518
|
+
"scaffold",
|
|
519
|
+
"setup-hex",
|
|
520
|
+
"setup-rebar",
|
|
521
|
+
"install",
|
|
522
|
+
"compile",
|
|
523
|
+
"test"
|
|
524
|
+
],
|
|
525
|
+
stackParts: ["backend:elixir:phoenix", "backend.orm:elixir:ecto_sqlite3"],
|
|
526
|
+
coveredOptions: [
|
|
527
|
+
{
|
|
528
|
+
ecosystem: "elixir",
|
|
529
|
+
category: "elixirWebFramework",
|
|
530
|
+
optionId: "phoenix"
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
ecosystem: "elixir",
|
|
534
|
+
category: "elixirOrm",
|
|
535
|
+
optionId: "ecto_sqlite3"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
ecosystem: "elixir",
|
|
539
|
+
category: "elixirApi",
|
|
540
|
+
optionId: "rest"
|
|
541
|
+
}
|
|
542
|
+
],
|
|
543
|
+
runtime: {
|
|
544
|
+
name: "Phoenix health response after migrations",
|
|
545
|
+
processCwd: ".",
|
|
546
|
+
setupCommands: [["mix", "ecto.create"], ["mix", "ecto.migrate"]],
|
|
547
|
+
command: ["mix", "phx.server"],
|
|
548
|
+
env: {
|
|
549
|
+
PORT: "4000",
|
|
550
|
+
DATABASE_PATH: "runtime-proof.db"
|
|
551
|
+
},
|
|
552
|
+
request: {
|
|
553
|
+
url: "http://127.0.0.1:4000/api/health",
|
|
554
|
+
method: "GET"
|
|
555
|
+
},
|
|
556
|
+
expectedStatus: 200,
|
|
557
|
+
bodyIncludes: ["\"status\":\"ok\""],
|
|
558
|
+
timeoutMs: 18e4,
|
|
559
|
+
limitation: "Exercises Phoenix and Ecto migrations against local SQLite."
|
|
560
|
+
},
|
|
561
|
+
maintainer: "@Marve10s"
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
id: "dotnet",
|
|
565
|
+
name: "ASP.NET Minimal API and EF Core local service",
|
|
566
|
+
definitionVersion: 1,
|
|
567
|
+
sourceBuildProofCaseId: "dotnet",
|
|
568
|
+
projectName: "proof-dotnet",
|
|
569
|
+
generationInputs: { preset: "dotnet-minimal-efcore" },
|
|
570
|
+
requiredToolchains: ["node", "dotnet"],
|
|
571
|
+
buildSteps: [
|
|
572
|
+
"scaffold",
|
|
573
|
+
"restore",
|
|
574
|
+
"build",
|
|
575
|
+
"test"
|
|
576
|
+
],
|
|
577
|
+
stackParts: ["backend:dotnet:aspnet-minimal", "backend.orm:dotnet:ef-core"],
|
|
578
|
+
coveredOptions: [
|
|
579
|
+
{
|
|
580
|
+
ecosystem: "dotnet",
|
|
581
|
+
category: "dotnetWebFramework",
|
|
582
|
+
optionId: "aspnet-minimal"
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
ecosystem: "dotnet",
|
|
586
|
+
category: "dotnetOrm",
|
|
587
|
+
optionId: "ef-core"
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
ecosystem: "dotnet",
|
|
591
|
+
category: "dotnetApi",
|
|
592
|
+
optionId: "minimal-api"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
ecosystem: "dotnet",
|
|
596
|
+
category: "dotnetObservability",
|
|
597
|
+
optionId: "health-checks"
|
|
598
|
+
}
|
|
599
|
+
],
|
|
600
|
+
runtime: {
|
|
601
|
+
name: "ASP.NET health response",
|
|
602
|
+
processCwd: ".",
|
|
603
|
+
command: [
|
|
604
|
+
"dotnet",
|
|
605
|
+
"run",
|
|
606
|
+
"--no-build"
|
|
607
|
+
],
|
|
608
|
+
env: { ASPNETCORE_URLS: "http://127.0.0.1:5000" },
|
|
609
|
+
request: {
|
|
610
|
+
url: "http://127.0.0.1:5000/health",
|
|
611
|
+
method: "GET"
|
|
612
|
+
},
|
|
613
|
+
expectedStatus: 200,
|
|
614
|
+
bodyIncludes: ["Healthy"],
|
|
615
|
+
timeoutMs: 18e4,
|
|
616
|
+
limitation: "Exercises ASP.NET and EF Core with the generated local SQLite database."
|
|
617
|
+
},
|
|
618
|
+
maintainer: "@Marve10s"
|
|
619
|
+
}
|
|
620
|
+
];
|
|
621
|
+
const CapabilityRecipeResultSchema = z.object({
|
|
622
|
+
id: z.enum([
|
|
623
|
+
"typescript",
|
|
624
|
+
"react-native",
|
|
625
|
+
"rust",
|
|
626
|
+
"python",
|
|
627
|
+
"go",
|
|
628
|
+
"java",
|
|
629
|
+
"elixir",
|
|
630
|
+
"dotnet"
|
|
631
|
+
]),
|
|
632
|
+
definitionVersion: z.number().int().positive(),
|
|
633
|
+
success: z.boolean(),
|
|
634
|
+
startedAt: z.string(),
|
|
635
|
+
completedAt: z.string(),
|
|
636
|
+
flakyRuns: z.number().int().nonnegative(),
|
|
637
|
+
repairMinutes: z.number().nonnegative(),
|
|
638
|
+
dependencyChanges: z.number().int().nonnegative(),
|
|
639
|
+
maintainerPresent: z.boolean()
|
|
640
|
+
});
|
|
641
|
+
const CapabilityEvidenceReceiptSchema = z.object({
|
|
642
|
+
schemaVersion: z.literal(CAPABILITY_RECEIPT_SCHEMA_VERSION),
|
|
643
|
+
evidenceSchemaVersion: z.literal(CAPABILITY_EVIDENCE_SCHEMA_VERSION),
|
|
644
|
+
receiptType: z.literal("better-fullstack/capability-runtime"),
|
|
645
|
+
sourceSha: z.string().regex(/^[0-9a-f]{40}$/i),
|
|
646
|
+
catalogVersion: z.string().min(1),
|
|
647
|
+
producerFingerprint: z.string().regex(/^[0-9a-f]{64}$/i),
|
|
648
|
+
createdAt: z.string(),
|
|
649
|
+
toolchains: z.record(z.string(), z.string()),
|
|
650
|
+
recipes: z.array(CapabilityRecipeResultSchema)
|
|
651
|
+
});
|
|
652
|
+
const CAPABILITY_QUARANTINE = [];
|
|
653
|
+
const CONTROL_CATEGORIES = new Set([
|
|
654
|
+
"packageManager",
|
|
655
|
+
"workspaceShape",
|
|
656
|
+
"versionChannel",
|
|
657
|
+
"git",
|
|
658
|
+
"install",
|
|
659
|
+
"aiDocs"
|
|
660
|
+
]);
|
|
661
|
+
function recordId(ecosystem, category, optionId) {
|
|
662
|
+
return `${ecosystem}:${category}:${optionId}`;
|
|
663
|
+
}
|
|
664
|
+
function receiptState(options) {
|
|
665
|
+
if (options.receipt === void 0) return {
|
|
666
|
+
freshness: "unverified",
|
|
667
|
+
receipt: null
|
|
668
|
+
};
|
|
669
|
+
const parsed = CapabilityEvidenceReceiptSchema.safeParse(options.receipt);
|
|
670
|
+
if (!parsed.success) return {
|
|
671
|
+
freshness: "producer-mismatch",
|
|
672
|
+
receipt: null
|
|
673
|
+
};
|
|
674
|
+
const receipt = parsed.data;
|
|
675
|
+
if (options.catalogVersion && receipt.catalogVersion !== options.catalogVersion || options.producerFingerprint && receipt.producerFingerprint !== options.producerFingerprint.toLowerCase()) return {
|
|
676
|
+
freshness: "producer-mismatch",
|
|
677
|
+
receipt: null
|
|
678
|
+
};
|
|
679
|
+
const createdAt = Date.parse(receipt.createdAt);
|
|
680
|
+
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
681
|
+
if (!Number.isFinite(createdAt) || createdAt > now + 5 * 6e4 || now - createdAt > CAPABILITY_RECEIPT_MAX_AGE_DAYS * 24 * 60 * 60 * 1e3) return {
|
|
682
|
+
freshness: "stale",
|
|
683
|
+
receipt: null
|
|
684
|
+
};
|
|
685
|
+
return {
|
|
686
|
+
freshness: "current",
|
|
687
|
+
receipt
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function recipeCoverage() {
|
|
691
|
+
const coverage = /* @__PURE__ */ new Map();
|
|
692
|
+
for (const recipe of GOLDEN_RUNTIME_RECIPES) {
|
|
693
|
+
const runtimeCoveredOptionIds = new Set((recipe.runtimeCoveredOptions ?? recipe.coveredOptions).map((option) => recordId(option.ecosystem, option.category, option.optionId)));
|
|
694
|
+
for (const option of recipe.coveredOptions) {
|
|
695
|
+
const id = recordId(option.ecosystem, option.category, option.optionId);
|
|
696
|
+
coverage.set(id, [...coverage.get(id) ?? [], {
|
|
697
|
+
evidenceLevel: runtimeCoveredOptionIds.has(id) ? "runtime-verified" : "build-verified",
|
|
698
|
+
recipeId: recipe.id
|
|
699
|
+
}]);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return coverage;
|
|
703
|
+
}
|
|
704
|
+
function getCapabilityInventory(options = {}) {
|
|
705
|
+
const state = receiptState(options);
|
|
706
|
+
const coverage = recipeCoverage();
|
|
707
|
+
const quarantine = new Map((options.quarantine ?? CAPABILITY_QUARANTINE).map((entry) => [recordId(entry.ecosystem, entry.category, entry.optionId), entry]));
|
|
708
|
+
const records = [];
|
|
709
|
+
for (const ecosystem of [
|
|
710
|
+
"typescript",
|
|
711
|
+
"react-native",
|
|
712
|
+
"rust",
|
|
713
|
+
"python",
|
|
714
|
+
"go",
|
|
715
|
+
"java",
|
|
716
|
+
"dotnet",
|
|
717
|
+
"elixir"
|
|
718
|
+
]) for (const category of getCategoryOrderForEcosystem(ecosystem)) for (const option of OPTION_CATEGORY_METADATA[category].options) {
|
|
719
|
+
if (option.id === "none") continue;
|
|
720
|
+
const id = recordId(ecosystem, category, option.id);
|
|
721
|
+
const quarantineEntry = quarantine.get(id);
|
|
722
|
+
const recipeCoverage$1 = coverage.get(id) ?? [];
|
|
723
|
+
const recipeIds = [...new Set(recipeCoverage$1.map((entry) => entry.recipeId))];
|
|
724
|
+
const passingCoverage = recipeCoverage$1.filter((coverageEntry) => {
|
|
725
|
+
const recipe = GOLDEN_RUNTIME_RECIPES.find((entry) => entry.id === coverageEntry.recipeId);
|
|
726
|
+
const result = state.receipt?.recipes.find((entry) => entry.id === coverageEntry.recipeId);
|
|
727
|
+
return recipe && result?.success === true && result.definitionVersion === recipe.definitionVersion;
|
|
728
|
+
});
|
|
729
|
+
const failedRecipe = recipeCoverage$1.some((coverageEntry) => {
|
|
730
|
+
return (state.receipt?.recipes.find((entry) => entry.id === coverageEntry.recipeId))?.success === false;
|
|
731
|
+
});
|
|
732
|
+
const publicOption = quarantineEntry?.visibility !== "hidden";
|
|
733
|
+
if (!publicOption && !options.includeHidden) continue;
|
|
734
|
+
const declaredEvidenceLevel = recipeCoverage$1.some((entry) => entry.evidenceLevel === "runtime-verified") ? "runtime-verified" : recipeCoverage$1.length > 0 ? "build-verified" : "listed";
|
|
735
|
+
const evidenceLevel = capCapabilityEvidenceLevel(passingCoverage.some((entry) => entry.evidenceLevel === "runtime-verified") ? "runtime-verified" : passingCoverage.length > 0 ? "build-verified" : "listed", quarantineEntry ? "listed" : "runtime-verified");
|
|
736
|
+
const maturity = quarantineEntry ? quarantineEntry.visibility === "hidden" ? "quarantined" : "experimental" : recipeIds.length > 0 || CONTROL_CATEGORIES.has(category) ? "stable" : "experimental";
|
|
737
|
+
const freshness = quarantineEntry ? "quarantined" : failedRecipe ? "failed" : state.freshness;
|
|
738
|
+
const verified = (evidenceLevel === "build-verified" || evidenceLevel === "runtime-verified") && state.receipt;
|
|
739
|
+
const strongestPassingCoverage = passingCoverage.find((entry) => entry.evidenceLevel === "runtime-verified") ?? passingCoverage[0];
|
|
740
|
+
const evidenceRecipe = GOLDEN_RUNTIME_RECIPES.find((recipe) => recipe.id === strongestPassingCoverage?.recipeId);
|
|
741
|
+
records.push({
|
|
742
|
+
id,
|
|
743
|
+
ecosystem,
|
|
744
|
+
category,
|
|
745
|
+
optionId: option.id,
|
|
746
|
+
label: option.label,
|
|
747
|
+
maintenanceOwner: quarantineEntry?.maintenanceOwner ?? "@Marve10s",
|
|
748
|
+
maturity,
|
|
749
|
+
public: publicOption,
|
|
750
|
+
declaredEvidenceLevel,
|
|
751
|
+
evidenceLevel: evidenceLevel ?? "listed",
|
|
752
|
+
freshness,
|
|
753
|
+
lastVerifiedVersion: verified ? state.receipt?.catalogVersion ?? null : null,
|
|
754
|
+
lastVerifiedAt: verified ? state.receipt?.createdAt ?? null : null,
|
|
755
|
+
limitation: quarantineEntry ? quarantineEntry.reason : recipeIds.length === 0 ? CONTROL_CATEGORIES.has(category) ? "Schema-listed project control. No runtime recipe is declared for this control." : "No current golden runtime recipe covers this option." : passingCoverage.length === 0 ? "A golden runtime recipe exists, but no current matching receipt proves it." : strongestPassingCoverage?.evidenceLevel === "build-verified" ? `Build evidence covers this option, but the recipe's live assertion does not. ${evidenceRecipe?.runtime.limitation ?? "Runtime evidence is limited to the recorded recipe."}` : evidenceRecipe?.runtime.limitation ?? "Runtime evidence is limited to the recorded recipe.",
|
|
756
|
+
recipeIds
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
return records;
|
|
760
|
+
}
|
|
761
|
+
function getCapabilityMaintenanceCosts(receipt) {
|
|
762
|
+
const parsed = CapabilityEvidenceReceiptSchema.safeParse(receipt);
|
|
763
|
+
if (!parsed.success) return [];
|
|
764
|
+
return parsed.data.recipes.map((recipe) => ({
|
|
765
|
+
recipeId: recipe.id,
|
|
766
|
+
flakyRuns: recipe.flakyRuns,
|
|
767
|
+
repairMinutes: recipe.repairMinutes,
|
|
768
|
+
dependencyChanges: recipe.dependencyChanges,
|
|
769
|
+
maintainerPresent: recipe.maintainerPresent,
|
|
770
|
+
recurringCostScore: recipe.flakyRuns * 3 + recipe.repairMinutes + recipe.dependencyChanges * 2 + (recipe.maintainerPresent ? 0 : 20)
|
|
771
|
+
}));
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/stack/starter-tracks.ts
|
|
776
|
+
const STARTER_TRACK_SCHEMA_VERSION = 1;
|
|
777
|
+
const STARTER_TRACK_IDS = [
|
|
778
|
+
"saas-app",
|
|
779
|
+
"ai-agent-app",
|
|
780
|
+
"rest-api",
|
|
781
|
+
"java-api",
|
|
782
|
+
"rust-backend",
|
|
783
|
+
"mobile-app",
|
|
784
|
+
"internal-tool"
|
|
785
|
+
];
|
|
786
|
+
const STARTER_TRACK_RUNTIME_IDS = [
|
|
787
|
+
"node",
|
|
788
|
+
"bun",
|
|
789
|
+
"python",
|
|
790
|
+
"jvm",
|
|
791
|
+
"rust",
|
|
792
|
+
"react-native"
|
|
793
|
+
];
|
|
794
|
+
const STARTER_TRACK_DEPLOYMENT_TARGET_IDS = [
|
|
795
|
+
"vercel",
|
|
796
|
+
"self-hosted",
|
|
797
|
+
"container",
|
|
798
|
+
"app-stores"
|
|
799
|
+
];
|
|
800
|
+
const STARTER_TRACK_PACKAGE_MANAGER_IDS = [
|
|
801
|
+
"bun",
|
|
802
|
+
"uv",
|
|
803
|
+
"gradle",
|
|
804
|
+
"cargo"
|
|
805
|
+
];
|
|
806
|
+
const STARTER_TRACK_DATABASE_IDS = [
|
|
807
|
+
"postgres",
|
|
808
|
+
"sqlite",
|
|
809
|
+
"none"
|
|
810
|
+
];
|
|
811
|
+
const STARTER_TRACK_AUTH_IDS = [
|
|
812
|
+
"better-auth",
|
|
813
|
+
"spring-security",
|
|
814
|
+
"none"
|
|
815
|
+
];
|
|
816
|
+
const STARTER_TRACK_WORKSPACE_SHAPE_IDS = ["monorepo", "single-app"];
|
|
817
|
+
const StarterTrackFiltersSchema = z.object({
|
|
818
|
+
evidence: z.enum(CAPABILITY_EVIDENCE_LEVEL_IDS).optional(),
|
|
819
|
+
runtime: z.enum(STARTER_TRACK_RUNTIME_IDS).optional(),
|
|
820
|
+
deploymentTarget: z.enum(STARTER_TRACK_DEPLOYMENT_TARGET_IDS).optional(),
|
|
821
|
+
packageManager: z.enum(STARTER_TRACK_PACKAGE_MANAGER_IDS).optional(),
|
|
822
|
+
database: z.enum(STARTER_TRACK_DATABASE_IDS).optional(),
|
|
823
|
+
auth: z.enum(STARTER_TRACK_AUTH_IDS).optional(),
|
|
824
|
+
workspaceShape: z.enum(STARTER_TRACK_WORKSPACE_SHAPE_IDS).optional()
|
|
825
|
+
});
|
|
826
|
+
const STARTER_TRACK_FILTER_URL_KEYS = {
|
|
827
|
+
evidence: "te",
|
|
828
|
+
runtime: "tr",
|
|
829
|
+
deploymentTarget: "td",
|
|
830
|
+
packageManager: "tpm",
|
|
831
|
+
database: "tdb",
|
|
832
|
+
auth: "ta",
|
|
833
|
+
workspaceShape: "tws"
|
|
834
|
+
};
|
|
835
|
+
const NON_TYPESCRIPT_SELECTION = {
|
|
836
|
+
stackMode: "solo",
|
|
837
|
+
stackPartSpecs: [],
|
|
838
|
+
webFrontend: ["none"],
|
|
839
|
+
nativeFrontend: ["none"],
|
|
840
|
+
astroIntegration: "none",
|
|
841
|
+
runtime: "none",
|
|
842
|
+
backend: "none",
|
|
843
|
+
orm: "none",
|
|
844
|
+
dbSetup: "none",
|
|
845
|
+
auth: "none",
|
|
846
|
+
payments: "none",
|
|
847
|
+
email: "none",
|
|
848
|
+
fileUpload: "none",
|
|
849
|
+
logging: "none",
|
|
850
|
+
observability: "none",
|
|
851
|
+
featureFlags: "none",
|
|
852
|
+
integrations: "none",
|
|
853
|
+
ecommerce: "none",
|
|
854
|
+
analytics: "none",
|
|
855
|
+
backendLibraries: "none",
|
|
856
|
+
stateManagement: "none",
|
|
857
|
+
forms: "none",
|
|
858
|
+
validation: "none",
|
|
859
|
+
testing: "none",
|
|
860
|
+
realtime: "none",
|
|
861
|
+
jobQueue: "none",
|
|
862
|
+
caching: "none",
|
|
863
|
+
rateLimit: "none",
|
|
864
|
+
botProtection: "none",
|
|
865
|
+
i18n: "none",
|
|
866
|
+
animation: "none",
|
|
867
|
+
cssFramework: "none",
|
|
868
|
+
uiLibrary: "none",
|
|
869
|
+
cms: "none",
|
|
870
|
+
search: "none",
|
|
871
|
+
vectorDb: "none",
|
|
872
|
+
fileStorage: "none",
|
|
873
|
+
codeQuality: [],
|
|
874
|
+
documentation: [],
|
|
875
|
+
appPlatforms: [],
|
|
876
|
+
workspaceShape: "single-app",
|
|
877
|
+
examples: [],
|
|
878
|
+
aiSdk: "none",
|
|
879
|
+
api: "none",
|
|
880
|
+
webDeploy: "none",
|
|
881
|
+
serverDeploy: "none"
|
|
882
|
+
};
|
|
883
|
+
function createSelection(overrides, stackPartSpecs) {
|
|
884
|
+
return normalizeStackSelection({
|
|
885
|
+
...cloneDefaultStackSelection(),
|
|
886
|
+
...overrides,
|
|
887
|
+
projectName: "my-app",
|
|
888
|
+
stackMode: "multi",
|
|
889
|
+
stackPartSpecs: [...stackPartSpecs],
|
|
890
|
+
git: "true",
|
|
891
|
+
install: "true"
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
const STARTER_TRACK_DEFINITIONS = [
|
|
895
|
+
{
|
|
896
|
+
id: "saas-app",
|
|
897
|
+
name: "SaaS App",
|
|
898
|
+
intent: "Sell subscriptions",
|
|
899
|
+
description: "Next.js with auth, relational data, payments, email, and a deployment-ready app shape.",
|
|
900
|
+
presetId: "nextjs-saas",
|
|
901
|
+
ecosystem: "typescript",
|
|
902
|
+
icon: "stripe",
|
|
903
|
+
guideHref: "/guides/packs/create-saas-app/",
|
|
904
|
+
docsHref: "/docs/choosing-a-stack/#deployment",
|
|
905
|
+
highlights: [
|
|
906
|
+
"Next.js",
|
|
907
|
+
"Better Auth",
|
|
908
|
+
"Stripe",
|
|
909
|
+
"Drizzle"
|
|
910
|
+
],
|
|
911
|
+
audience: "Founders validating paid products",
|
|
912
|
+
outcome: "Billing, auth, email, and data wired into one app",
|
|
913
|
+
ctaLabel: "Start SaaS",
|
|
914
|
+
selection: createSelection({
|
|
915
|
+
webFrontend: ["next"],
|
|
916
|
+
nativeFrontend: ["none"],
|
|
917
|
+
runtime: "none",
|
|
918
|
+
backend: "self-next",
|
|
919
|
+
database: "postgres",
|
|
920
|
+
orm: "drizzle",
|
|
921
|
+
auth: "better-auth",
|
|
922
|
+
payments: "stripe",
|
|
923
|
+
email: "react-email",
|
|
924
|
+
codeQuality: ["biome"],
|
|
925
|
+
appPlatforms: ["turborepo"],
|
|
926
|
+
packageManager: "bun",
|
|
927
|
+
workspaceShape: "monorepo",
|
|
928
|
+
api: "trpc"
|
|
929
|
+
}, [
|
|
930
|
+
"frontend:typescript:next",
|
|
931
|
+
"backend:typescript:self",
|
|
932
|
+
"database:universal:postgres",
|
|
933
|
+
"codeQuality:universal:biome",
|
|
934
|
+
"workspaceRunner:universal:turborepo",
|
|
935
|
+
"backend.orm:typescript:drizzle",
|
|
936
|
+
"backend.api:typescript:trpc",
|
|
937
|
+
"backend.auth:typescript:better-auth",
|
|
938
|
+
"frontend.css:typescript:tailwind",
|
|
939
|
+
"frontend.ui:typescript:shadcn-ui",
|
|
940
|
+
"frontend.forms:typescript:react-hook-form",
|
|
941
|
+
"backend.email:typescript:react-email",
|
|
942
|
+
"backend.payments:typescript:stripe",
|
|
943
|
+
"backend.validation:typescript:zod",
|
|
944
|
+
"backend.testing:typescript:vitest"
|
|
945
|
+
]),
|
|
946
|
+
facets: {
|
|
947
|
+
runtimes: ["node"],
|
|
948
|
+
deploymentTargets: [
|
|
949
|
+
"vercel",
|
|
950
|
+
"self-hosted",
|
|
951
|
+
"container"
|
|
952
|
+
],
|
|
953
|
+
packageManagers: ["bun"],
|
|
954
|
+
databases: ["postgres"],
|
|
955
|
+
auth: ["better-auth"],
|
|
956
|
+
workspaceShapes: ["monorepo"]
|
|
957
|
+
},
|
|
958
|
+
keywords: [
|
|
959
|
+
"saas",
|
|
960
|
+
"subscription",
|
|
961
|
+
"subscriptions",
|
|
962
|
+
"billing",
|
|
963
|
+
"payments",
|
|
964
|
+
"stripe",
|
|
965
|
+
"checkout",
|
|
966
|
+
"founder"
|
|
967
|
+
],
|
|
968
|
+
phrases: [
|
|
969
|
+
"sell subscriptions",
|
|
970
|
+
"paid product",
|
|
971
|
+
"software as a service"
|
|
972
|
+
]
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
id: "ai-agent-app",
|
|
976
|
+
name: "AI Agent App",
|
|
977
|
+
intent: "Build with agents",
|
|
978
|
+
description: "A Next.js workspace prepared for AI CLI flows, MCP, skills, and generated agent docs.",
|
|
979
|
+
presetId: "ai-cli-agent-workbench",
|
|
980
|
+
ecosystem: "typescript",
|
|
981
|
+
icon: "ai-cli",
|
|
982
|
+
guideHref: "/guides/packs/create-ai-agent-app/",
|
|
983
|
+
docsHref: "/docs/ai/overview/",
|
|
984
|
+
highlights: [
|
|
985
|
+
"Next.js",
|
|
986
|
+
"AI CLI",
|
|
987
|
+
"MCP",
|
|
988
|
+
"Skills"
|
|
989
|
+
],
|
|
990
|
+
audience: "Teams building agent-assisted products",
|
|
991
|
+
outcome: "AI docs, MCP, skills, and CLI workflow ready from day one",
|
|
992
|
+
ctaLabel: "Start AI",
|
|
993
|
+
selection: createSelection({
|
|
994
|
+
webFrontend: ["next"],
|
|
995
|
+
nativeFrontend: ["none"],
|
|
996
|
+
runtime: "none",
|
|
997
|
+
backend: "self-next",
|
|
998
|
+
database: "sqlite",
|
|
999
|
+
orm: "drizzle",
|
|
1000
|
+
auth: "better-auth",
|
|
1001
|
+
codeQuality: ["biome", "ruler"],
|
|
1002
|
+
appPlatforms: [
|
|
1003
|
+
"turborepo",
|
|
1004
|
+
"mcp",
|
|
1005
|
+
"skills"
|
|
1006
|
+
],
|
|
1007
|
+
packageManager: "bun",
|
|
1008
|
+
workspaceShape: "monorepo",
|
|
1009
|
+
aiSdk: "ai-cli",
|
|
1010
|
+
aiDocs: [
|
|
1011
|
+
"claude-md",
|
|
1012
|
+
"agents-md",
|
|
1013
|
+
"cursorrules"
|
|
1014
|
+
],
|
|
1015
|
+
api: "trpc"
|
|
1016
|
+
}, [
|
|
1017
|
+
"frontend:typescript:next",
|
|
1018
|
+
"backend:typescript:self",
|
|
1019
|
+
"database:universal:sqlite",
|
|
1020
|
+
"codeQuality:universal:biome",
|
|
1021
|
+
"aiTooling:universal:ruler",
|
|
1022
|
+
"workspaceRunner:universal:turborepo",
|
|
1023
|
+
"aiTooling:universal:mcp",
|
|
1024
|
+
"aiTooling:universal:skills",
|
|
1025
|
+
"backend.orm:typescript:drizzle",
|
|
1026
|
+
"backend.api:typescript:trpc",
|
|
1027
|
+
"backend.auth:typescript:better-auth",
|
|
1028
|
+
"frontend.css:typescript:tailwind",
|
|
1029
|
+
"frontend.ui:typescript:shadcn-ui",
|
|
1030
|
+
"frontend.forms:typescript:react-hook-form",
|
|
1031
|
+
"backend.ai:typescript:ai-cli",
|
|
1032
|
+
"backend.validation:typescript:zod",
|
|
1033
|
+
"backend.testing:typescript:vitest"
|
|
1034
|
+
]),
|
|
1035
|
+
facets: {
|
|
1036
|
+
runtimes: ["node"],
|
|
1037
|
+
deploymentTargets: ["vercel", "self-hosted"],
|
|
1038
|
+
packageManagers: ["bun"],
|
|
1039
|
+
databases: ["sqlite"],
|
|
1040
|
+
auth: ["better-auth"],
|
|
1041
|
+
workspaceShapes: ["monorepo"]
|
|
1042
|
+
},
|
|
1043
|
+
keywords: [
|
|
1044
|
+
"ai",
|
|
1045
|
+
"agent",
|
|
1046
|
+
"agents",
|
|
1047
|
+
"llm",
|
|
1048
|
+
"chatbot",
|
|
1049
|
+
"assistant",
|
|
1050
|
+
"mcp",
|
|
1051
|
+
"skills",
|
|
1052
|
+
"copilot",
|
|
1053
|
+
"rag"
|
|
1054
|
+
],
|
|
1055
|
+
phrases: [
|
|
1056
|
+
"ai agent",
|
|
1057
|
+
"agent assisted",
|
|
1058
|
+
"model context protocol"
|
|
1059
|
+
]
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
id: "rest-api",
|
|
1063
|
+
name: "REST API",
|
|
1064
|
+
intent: "Expose a service",
|
|
1065
|
+
description: "FastAPI with SQLAlchemy, Pydantic, Ruff, and a small API-first Python project layout.",
|
|
1066
|
+
presetId: "python-fastapi",
|
|
1067
|
+
ecosystem: "python",
|
|
1068
|
+
icon: "fastapi",
|
|
1069
|
+
guideHref: "/guides/packs/create-rest-api/",
|
|
1070
|
+
docsHref: "/docs/choosing-a-stack/#deployment",
|
|
1071
|
+
highlights: [
|
|
1072
|
+
"FastAPI",
|
|
1073
|
+
"PostgreSQL",
|
|
1074
|
+
"SQLAlchemy",
|
|
1075
|
+
"Pydantic"
|
|
1076
|
+
],
|
|
1077
|
+
audience: "Backend teams exposing typed services",
|
|
1078
|
+
outcome: "FastAPI service with validation, persistence, and Ruff quality checks",
|
|
1079
|
+
ctaLabel: "Start API",
|
|
1080
|
+
selection: createSelection({
|
|
1081
|
+
...NON_TYPESCRIPT_SELECTION,
|
|
1082
|
+
ecosystem: "python",
|
|
1083
|
+
database: "postgres",
|
|
1084
|
+
pythonWebFramework: "fastapi",
|
|
1085
|
+
pythonOrm: "sqlalchemy",
|
|
1086
|
+
pythonValidation: "pydantic",
|
|
1087
|
+
pythonAi: [],
|
|
1088
|
+
pythonAuth: "none",
|
|
1089
|
+
pythonApi: "none",
|
|
1090
|
+
pythonTaskQueue: "none",
|
|
1091
|
+
pythonGraphql: "none",
|
|
1092
|
+
pythonQuality: "ruff",
|
|
1093
|
+
pythonPackageManager: "uv",
|
|
1094
|
+
aiDocs: ["claude-md"]
|
|
1095
|
+
}, [
|
|
1096
|
+
"backend:python:fastapi",
|
|
1097
|
+
"database:universal:postgres",
|
|
1098
|
+
"backend.orm:python:sqlalchemy",
|
|
1099
|
+
"backend.validation:python:pydantic",
|
|
1100
|
+
"backend.codeQuality:python:ruff",
|
|
1101
|
+
"backend.packageManager:python:uv"
|
|
1102
|
+
]),
|
|
1103
|
+
facets: {
|
|
1104
|
+
runtimes: ["python"],
|
|
1105
|
+
deploymentTargets: ["self-hosted", "container"],
|
|
1106
|
+
packageManagers: ["uv"],
|
|
1107
|
+
databases: ["postgres"],
|
|
1108
|
+
auth: ["none"],
|
|
1109
|
+
workspaceShapes: ["single-app"]
|
|
1110
|
+
},
|
|
1111
|
+
keywords: [
|
|
1112
|
+
"api",
|
|
1113
|
+
"rest",
|
|
1114
|
+
"fastapi",
|
|
1115
|
+
"python",
|
|
1116
|
+
"pydantic",
|
|
1117
|
+
"sqlalchemy",
|
|
1118
|
+
"service"
|
|
1119
|
+
],
|
|
1120
|
+
phrases: [
|
|
1121
|
+
"rest api",
|
|
1122
|
+
"python api",
|
|
1123
|
+
"typed service",
|
|
1124
|
+
"backend service"
|
|
1125
|
+
]
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
id: "java-api",
|
|
1129
|
+
name: "Java API",
|
|
1130
|
+
intent: "Ship Spring services",
|
|
1131
|
+
description: "Spring Boot with security, JPA, migrations, and test coverage for backend teams.",
|
|
1132
|
+
presetId: "java-secure",
|
|
1133
|
+
ecosystem: "java",
|
|
1134
|
+
icon: "java",
|
|
1135
|
+
guideHref: "/guides/packs/create-java-api/",
|
|
1136
|
+
docsHref: "/docs/ecosystems/#java-and-kotlin",
|
|
1137
|
+
highlights: [
|
|
1138
|
+
"Spring Boot",
|
|
1139
|
+
"Security",
|
|
1140
|
+
"JPA",
|
|
1141
|
+
"Testcontainers"
|
|
1142
|
+
],
|
|
1143
|
+
audience: "Java teams shipping secure APIs",
|
|
1144
|
+
outcome: "Spring Security, JPA, migrations, and test coverage scaffolded",
|
|
1145
|
+
ctaLabel: "Start Java",
|
|
1146
|
+
selection: createSelection({
|
|
1147
|
+
...NON_TYPESCRIPT_SELECTION,
|
|
1148
|
+
ecosystem: "java",
|
|
1149
|
+
database: "postgres",
|
|
1150
|
+
javaWebFramework: "spring-boot",
|
|
1151
|
+
javaBuildTool: "gradle",
|
|
1152
|
+
javaOrm: "spring-data-jpa",
|
|
1153
|
+
javaAuth: "spring-security",
|
|
1154
|
+
javaLibraries: ["spring-actuator", "flyway"],
|
|
1155
|
+
javaTestingLibraries: ["junit5", "testcontainers"],
|
|
1156
|
+
aiDocs: ["claude-md"]
|
|
1157
|
+
}, [
|
|
1158
|
+
"backend:java:spring-boot",
|
|
1159
|
+
"database:universal:postgres",
|
|
1160
|
+
"backend.orm:java:spring-data-jpa",
|
|
1161
|
+
"backend.auth:java:spring-security",
|
|
1162
|
+
"backend.buildTool:java:gradle",
|
|
1163
|
+
"backend.libraries:java:spring-actuator",
|
|
1164
|
+
"backend.libraries:java:flyway",
|
|
1165
|
+
"backend.testing:java:junit5",
|
|
1166
|
+
"backend.testing:java:testcontainers"
|
|
1167
|
+
]),
|
|
1168
|
+
facets: {
|
|
1169
|
+
runtimes: ["jvm"],
|
|
1170
|
+
deploymentTargets: ["self-hosted", "container"],
|
|
1171
|
+
packageManagers: ["gradle"],
|
|
1172
|
+
databases: ["postgres"],
|
|
1173
|
+
auth: ["spring-security"],
|
|
1174
|
+
workspaceShapes: ["single-app"]
|
|
1175
|
+
},
|
|
1176
|
+
keywords: [
|
|
1177
|
+
"java",
|
|
1178
|
+
"spring",
|
|
1179
|
+
"springboot",
|
|
1180
|
+
"jpa",
|
|
1181
|
+
"jvm",
|
|
1182
|
+
"enterprise",
|
|
1183
|
+
"secure"
|
|
1184
|
+
],
|
|
1185
|
+
phrases: [
|
|
1186
|
+
"spring boot",
|
|
1187
|
+
"java api",
|
|
1188
|
+
"spring service",
|
|
1189
|
+
"secure api"
|
|
1190
|
+
]
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
id: "rust-backend",
|
|
1194
|
+
name: "Rust Backend",
|
|
1195
|
+
intent: "Prefer systems-grade APIs",
|
|
1196
|
+
description: "Axum and SeaORM for a compact Rust backend with typed persistence and observability hooks.",
|
|
1197
|
+
presetId: "rust-api",
|
|
1198
|
+
ecosystem: "rust",
|
|
1199
|
+
icon: "rust",
|
|
1200
|
+
guideHref: "/guides/packs/create-rust-backend/",
|
|
1201
|
+
docsHref: "/docs/ecosystems/#rust",
|
|
1202
|
+
highlights: [
|
|
1203
|
+
"Axum",
|
|
1204
|
+
"SeaORM",
|
|
1205
|
+
"PostgreSQL",
|
|
1206
|
+
"Tracing"
|
|
1207
|
+
],
|
|
1208
|
+
audience: "Systems-minded backend developers",
|
|
1209
|
+
outcome: "Axum service with typed persistence and clean compiler checks",
|
|
1210
|
+
ctaLabel: "Start Rust",
|
|
1211
|
+
selection: createSelection({
|
|
1212
|
+
...NON_TYPESCRIPT_SELECTION,
|
|
1213
|
+
ecosystem: "rust",
|
|
1214
|
+
database: "postgres",
|
|
1215
|
+
rustWebFramework: "axum",
|
|
1216
|
+
rustFrontend: "none",
|
|
1217
|
+
rustOrm: "sea-orm",
|
|
1218
|
+
rustApi: "none",
|
|
1219
|
+
rustCli: "none",
|
|
1220
|
+
rustLibraries: [],
|
|
1221
|
+
aiDocs: ["claude-md"]
|
|
1222
|
+
}, [
|
|
1223
|
+
"backend:rust:axum",
|
|
1224
|
+
"database:universal:postgres",
|
|
1225
|
+
"backend.orm:rust:sea-orm",
|
|
1226
|
+
"backend.logging:rust:tracing",
|
|
1227
|
+
"backend.errorHandling:rust:anyhow-thiserror"
|
|
1228
|
+
]),
|
|
1229
|
+
facets: {
|
|
1230
|
+
runtimes: ["rust"],
|
|
1231
|
+
deploymentTargets: ["self-hosted", "container"],
|
|
1232
|
+
packageManagers: ["cargo"],
|
|
1233
|
+
databases: ["postgres"],
|
|
1234
|
+
auth: ["none"],
|
|
1235
|
+
workspaceShapes: ["single-app"]
|
|
1236
|
+
},
|
|
1237
|
+
keywords: [
|
|
1238
|
+
"rust",
|
|
1239
|
+
"axum",
|
|
1240
|
+
"seaorm",
|
|
1241
|
+
"systems",
|
|
1242
|
+
"native",
|
|
1243
|
+
"performance",
|
|
1244
|
+
"safe"
|
|
1245
|
+
],
|
|
1246
|
+
phrases: [
|
|
1247
|
+
"rust api",
|
|
1248
|
+
"rust backend",
|
|
1249
|
+
"systems grade",
|
|
1250
|
+
"memory safe service"
|
|
1251
|
+
]
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
id: "mobile-app",
|
|
1255
|
+
name: "Mobile App",
|
|
1256
|
+
intent: "Start native",
|
|
1257
|
+
description: "Expo with Uniwind for a native-first app shell before backend services are needed.",
|
|
1258
|
+
presetId: "uniwind",
|
|
1259
|
+
ecosystem: "react-native",
|
|
1260
|
+
icon: "native-uniwind",
|
|
1261
|
+
guideHref: "/guides/packs/create-mobile-app/",
|
|
1262
|
+
docsHref: "/docs/ecosystems/#typescript",
|
|
1263
|
+
highlights: [
|
|
1264
|
+
"Expo",
|
|
1265
|
+
"Uniwind",
|
|
1266
|
+
"React Native",
|
|
1267
|
+
"Mobile"
|
|
1268
|
+
],
|
|
1269
|
+
audience: "Product teams starting native first",
|
|
1270
|
+
outcome: "Expo app shell with Uniwind styling and mobile defaults",
|
|
1271
|
+
ctaLabel: "Start mobile",
|
|
1272
|
+
selection: createSelection({
|
|
1273
|
+
...NON_TYPESCRIPT_SELECTION,
|
|
1274
|
+
ecosystem: "react-native",
|
|
1275
|
+
database: "none",
|
|
1276
|
+
nativeFrontend: ["native-uniwind"],
|
|
1277
|
+
mobileNavigation: "expo-router",
|
|
1278
|
+
mobileUI: "uniwind",
|
|
1279
|
+
mobileDeepLinking: "expo-linking",
|
|
1280
|
+
packageManager: "bun",
|
|
1281
|
+
aiDocs: ["claude-md", "agents-md"]
|
|
1282
|
+
}, [
|
|
1283
|
+
"mobile:react-native:native-uniwind",
|
|
1284
|
+
"mobile.navigation:react-native:expo-router",
|
|
1285
|
+
"mobile.ui:react-native:uniwind",
|
|
1286
|
+
"mobile.deepLinking:react-native:expo-linking"
|
|
1287
|
+
]),
|
|
1288
|
+
facets: {
|
|
1289
|
+
runtimes: ["react-native"],
|
|
1290
|
+
deploymentTargets: ["app-stores"],
|
|
1291
|
+
packageManagers: ["bun"],
|
|
1292
|
+
databases: ["none"],
|
|
1293
|
+
auth: ["none"],
|
|
1294
|
+
workspaceShapes: ["single-app"]
|
|
1295
|
+
},
|
|
1296
|
+
keywords: [
|
|
1297
|
+
"mobile",
|
|
1298
|
+
"ios",
|
|
1299
|
+
"android",
|
|
1300
|
+
"expo",
|
|
1301
|
+
"native",
|
|
1302
|
+
"uniwind",
|
|
1303
|
+
"phone"
|
|
1304
|
+
],
|
|
1305
|
+
phrases: [
|
|
1306
|
+
"react native",
|
|
1307
|
+
"mobile app",
|
|
1308
|
+
"app store",
|
|
1309
|
+
"play store"
|
|
1310
|
+
]
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
id: "internal-tool",
|
|
1314
|
+
name: "Internal Tool",
|
|
1315
|
+
intent: "Move fast with CRUD",
|
|
1316
|
+
description: "TanStack Router, Hono, Drizzle, auth, and tRPC for product dashboards and admin tools.",
|
|
1317
|
+
presetId: "tanstack-hono",
|
|
1318
|
+
ecosystem: "typescript",
|
|
1319
|
+
icon: "hono",
|
|
1320
|
+
guideHref: "/guides/packs/create-internal-tool/",
|
|
1321
|
+
docsHref: "/docs/choosing-a-stack/#deployment",
|
|
1322
|
+
highlights: [
|
|
1323
|
+
"TanStack Router",
|
|
1324
|
+
"Hono",
|
|
1325
|
+
"Drizzle",
|
|
1326
|
+
"tRPC"
|
|
1327
|
+
],
|
|
1328
|
+
audience: "Teams building dashboards and admin tools",
|
|
1329
|
+
outcome: "Frontend, API, auth, and data layer ready for CRUD workflows",
|
|
1330
|
+
ctaLabel: "Start tool",
|
|
1331
|
+
selection: createSelection({
|
|
1332
|
+
webFrontend: ["tanstack-router"],
|
|
1333
|
+
nativeFrontend: ["none"],
|
|
1334
|
+
runtime: "bun",
|
|
1335
|
+
backend: "hono",
|
|
1336
|
+
database: "postgres",
|
|
1337
|
+
orm: "drizzle",
|
|
1338
|
+
auth: "better-auth",
|
|
1339
|
+
appPlatforms: ["turborepo"],
|
|
1340
|
+
packageManager: "bun",
|
|
1341
|
+
workspaceShape: "monorepo",
|
|
1342
|
+
api: "trpc"
|
|
1343
|
+
}, [
|
|
1344
|
+
"frontend:typescript:tanstack-router",
|
|
1345
|
+
"backend:typescript:hono",
|
|
1346
|
+
"database:universal:postgres",
|
|
1347
|
+
"workspaceRunner:universal:turborepo",
|
|
1348
|
+
"backend.orm:typescript:drizzle",
|
|
1349
|
+
"backend.api:typescript:trpc",
|
|
1350
|
+
"backend.auth:typescript:better-auth",
|
|
1351
|
+
"frontend.css:typescript:tailwind",
|
|
1352
|
+
"frontend.ui:typescript:shadcn-ui",
|
|
1353
|
+
"frontend.forms:typescript:react-hook-form",
|
|
1354
|
+
"backend.runtime:typescript:bun",
|
|
1355
|
+
"backend.validation:typescript:zod",
|
|
1356
|
+
"backend.testing:typescript:vitest"
|
|
1357
|
+
]),
|
|
1358
|
+
facets: {
|
|
1359
|
+
runtimes: ["bun"],
|
|
1360
|
+
deploymentTargets: ["self-hosted", "container"],
|
|
1361
|
+
packageManagers: ["bun"],
|
|
1362
|
+
databases: ["postgres"],
|
|
1363
|
+
auth: ["better-auth"],
|
|
1364
|
+
workspaceShapes: ["monorepo"]
|
|
1365
|
+
},
|
|
1366
|
+
keywords: [
|
|
1367
|
+
"internal",
|
|
1368
|
+
"admin",
|
|
1369
|
+
"dashboard",
|
|
1370
|
+
"crud",
|
|
1371
|
+
"backoffice",
|
|
1372
|
+
"portal",
|
|
1373
|
+
"operations"
|
|
1374
|
+
],
|
|
1375
|
+
phrases: [
|
|
1376
|
+
"internal tool",
|
|
1377
|
+
"admin panel",
|
|
1378
|
+
"operations dashboard",
|
|
1379
|
+
"crud app"
|
|
1380
|
+
]
|
|
1381
|
+
}
|
|
1382
|
+
];
|
|
1383
|
+
function optionCategory(value) {
|
|
1384
|
+
return value && value in OPTION_CATEGORY_METADATA ? value : void 0;
|
|
1385
|
+
}
|
|
1386
|
+
function partEvidenceCategory(part) {
|
|
1387
|
+
if (part.role === "frontend") return "webFrontend";
|
|
1388
|
+
if (part.role === "mobile") return "nativeFrontend";
|
|
1389
|
+
return optionCategory(STACK_TOOL_DEFINITIONS.find((candidate) => candidate.toolId === part.toolId && candidate.roles.includes(part.role) && candidate.ecosystems.includes(part.ecosystem))?.legacyCategory);
|
|
1390
|
+
}
|
|
1391
|
+
function partOptionId(part, category, config) {
|
|
1392
|
+
if (!category) return part.toolId;
|
|
1393
|
+
if (category === "webFrontend") return part.toolId;
|
|
1394
|
+
if (category === "backend" && part.toolId === "self") {
|
|
1395
|
+
const frontend = config.frontend.find((value$1) => value$1 !== "none");
|
|
1396
|
+
if (frontend) return `self-${frontend}`;
|
|
1397
|
+
}
|
|
1398
|
+
const value = config[category];
|
|
1399
|
+
if (typeof value === "string") return value;
|
|
1400
|
+
if (Array.isArray(value) && value.includes(part.toolId)) return part.toolId;
|
|
1401
|
+
return part.toolId;
|
|
1402
|
+
}
|
|
1403
|
+
function evidenceEcosystem(part, trackEcosystem) {
|
|
1404
|
+
return part.ecosystem === "universal" ? trackEcosystem : part.ecosystem;
|
|
1405
|
+
}
|
|
1406
|
+
function evidenceForPart(part, config, inventory) {
|
|
1407
|
+
const category = partEvidenceCategory(part);
|
|
1408
|
+
const optionId = partOptionId(part, category, config);
|
|
1409
|
+
const ecosystem = evidenceEcosystem(part, config.ecosystem);
|
|
1410
|
+
const candidates = inventory.filter((record) => record.optionId === optionId || record.optionId === part.toolId);
|
|
1411
|
+
return candidates.find((record) => record.ecosystem === ecosystem && (!category || record.category === category)) ?? candidates.find((record) => record.ecosystem === ecosystem) ?? candidates.find((record) => !category || record.category === category) ?? candidates[0];
|
|
1412
|
+
}
|
|
1413
|
+
const FRESHNESS_ORDER = [
|
|
1414
|
+
"current",
|
|
1415
|
+
"unverified",
|
|
1416
|
+
"stale",
|
|
1417
|
+
"producer-mismatch",
|
|
1418
|
+
"failed",
|
|
1419
|
+
"quarantined"
|
|
1420
|
+
];
|
|
1421
|
+
function minimumEvidenceLevel(levels) {
|
|
1422
|
+
return [...levels].sort((left, right) => CAPABILITY_EVIDENCE_LEVEL_IDS.indexOf(left) - CAPABILITY_EVIDENCE_LEVEL_IDS.indexOf(right))[0] ?? "listed";
|
|
1423
|
+
}
|
|
1424
|
+
function getProjectConfigEvidence(config, options = {}) {
|
|
1425
|
+
const inventory = options.inventory ?? getCapabilityInventory({
|
|
1426
|
+
receipt: options.receipt,
|
|
1427
|
+
catalogVersion: options.catalogVersion,
|
|
1428
|
+
producerFingerprint: options.producerFingerprint
|
|
1429
|
+
});
|
|
1430
|
+
const parts = (config.stackParts ?? legacyProjectConfigToStackParts(config)).filter((part) => part.toolId !== "none" && part.source !== "provided");
|
|
1431
|
+
const records = parts.map((part) => {
|
|
1432
|
+
const evidence = evidenceForPart(part, config, inventory);
|
|
1433
|
+
return {
|
|
1434
|
+
partSpec: formatStackPartSpec(part, parts),
|
|
1435
|
+
role: part.role,
|
|
1436
|
+
toolId: part.toolId,
|
|
1437
|
+
category: evidence?.category ?? null,
|
|
1438
|
+
optionId: evidence?.optionId ?? part.toolId,
|
|
1439
|
+
level: evidence?.evidenceLevel ?? "listed",
|
|
1440
|
+
declaredLevel: evidence?.declaredEvidenceLevel ?? "listed",
|
|
1441
|
+
maturity: evidence?.maturity ?? "experimental",
|
|
1442
|
+
freshness: evidence?.freshness ?? "unverified",
|
|
1443
|
+
maintenanceOwner: evidence?.maintenanceOwner ?? "@Marve10s",
|
|
1444
|
+
limitation: evidence?.limitation ?? "No capability inventory record matches this Stack Part.",
|
|
1445
|
+
recipeIds: evidence ? [...evidence.recipeIds] : []
|
|
1446
|
+
};
|
|
1447
|
+
});
|
|
1448
|
+
return {
|
|
1449
|
+
level: minimumEvidenceLevel(records.map((record) => record.level)),
|
|
1450
|
+
declaredLevel: minimumEvidenceLevel(records.map((record) => record.declaredLevel)),
|
|
1451
|
+
freshness: [...records].sort((left, right) => FRESHNESS_ORDER.indexOf(right.freshness) - FRESHNESS_ORDER.indexOf(left.freshness))[0]?.freshness ?? "unverified",
|
|
1452
|
+
partCount: parts.length,
|
|
1453
|
+
records,
|
|
1454
|
+
limitations: [...new Set(records.map((record) => record.limitation))]
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function getStackSelectionEvidence(selection, options = {}) {
|
|
1458
|
+
return getProjectConfigEvidence(stackSelectionToProjectConfig(selection, {
|
|
1459
|
+
projectDir: "/starter-track",
|
|
1460
|
+
relativePath: "starter-track",
|
|
1461
|
+
install: false
|
|
1462
|
+
}), options);
|
|
1463
|
+
}
|
|
1464
|
+
function materializeStarterTrack(definition, inventory) {
|
|
1465
|
+
const selection = normalizeStackSelection({
|
|
1466
|
+
...definition.selection,
|
|
1467
|
+
stackPartSpecs: [...definition.selection.stackPartSpecs]
|
|
1468
|
+
});
|
|
1469
|
+
const parts = legacyProjectConfigToStackParts(stackSelectionToProjectConfig(selection, {
|
|
1470
|
+
projectDir: "/starter-track",
|
|
1471
|
+
relativePath: "starter-track",
|
|
1472
|
+
install: false
|
|
1473
|
+
}));
|
|
1474
|
+
const validation = validateStackParts(parts);
|
|
1475
|
+
const stackPartSpecs = parts.map((part) => formatStackPartSpec(part, parts));
|
|
1476
|
+
return {
|
|
1477
|
+
id: definition.id,
|
|
1478
|
+
name: definition.name,
|
|
1479
|
+
intent: definition.intent,
|
|
1480
|
+
description: definition.description,
|
|
1481
|
+
presetId: definition.presetId,
|
|
1482
|
+
ecosystem: definition.ecosystem,
|
|
1483
|
+
icon: definition.icon,
|
|
1484
|
+
guideHref: definition.guideHref,
|
|
1485
|
+
docsHref: definition.docsHref,
|
|
1486
|
+
highlights: [...definition.highlights],
|
|
1487
|
+
audience: definition.audience,
|
|
1488
|
+
outcome: definition.outcome,
|
|
1489
|
+
ctaLabel: definition.ctaLabel,
|
|
1490
|
+
selection,
|
|
1491
|
+
stackPartSpecs,
|
|
1492
|
+
compatibility: {
|
|
1493
|
+
valid: validation.issues.length === 0,
|
|
1494
|
+
issues: validation.issues.map((issue) => ({
|
|
1495
|
+
code: issue.code,
|
|
1496
|
+
message: issue.message,
|
|
1497
|
+
partId: issue.partId ?? null,
|
|
1498
|
+
role: issue.role ?? null,
|
|
1499
|
+
toolId: issue.toolId ?? null,
|
|
1500
|
+
alternatives: issue.alternatives ?? []
|
|
1501
|
+
}))
|
|
1502
|
+
},
|
|
1503
|
+
evidence: getStackSelectionEvidence(selection, { inventory }),
|
|
1504
|
+
facets: {
|
|
1505
|
+
runtimes: [...definition.facets.runtimes],
|
|
1506
|
+
deploymentTargets: [...definition.facets.deploymentTargets],
|
|
1507
|
+
packageManagers: [...definition.facets.packageManagers],
|
|
1508
|
+
databases: [...definition.facets.databases],
|
|
1509
|
+
auth: [...definition.facets.auth],
|
|
1510
|
+
workspaceShapes: [...definition.facets.workspaceShapes]
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
function matchesFilters(track, filters) {
|
|
1515
|
+
if (filters.evidence && !capabilityEvidenceAtLeast(track.evidence.level, filters.evidence)) return false;
|
|
1516
|
+
if (filters.runtime && !track.facets.runtimes.includes(filters.runtime)) return false;
|
|
1517
|
+
if (filters.deploymentTarget && !track.facets.deploymentTargets.includes(filters.deploymentTarget)) return false;
|
|
1518
|
+
if (filters.packageManager && !track.facets.packageManagers.includes(filters.packageManager)) return false;
|
|
1519
|
+
if (filters.database && !track.facets.databases.includes(filters.database)) return false;
|
|
1520
|
+
if (filters.auth && !track.facets.auth.includes(filters.auth)) return false;
|
|
1521
|
+
if (filters.workspaceShape && !track.facets.workspaceShapes.includes(filters.workspaceShape)) return false;
|
|
1522
|
+
return true;
|
|
1523
|
+
}
|
|
1524
|
+
function getStarterTrackCatalog(options = {}) {
|
|
1525
|
+
const filters = StarterTrackFiltersSchema.parse(options.filters ?? {});
|
|
1526
|
+
const inventory = options.inventory ?? getCapabilityInventory({
|
|
1527
|
+
receipt: options.receipt,
|
|
1528
|
+
catalogVersion: options.catalogVersion,
|
|
1529
|
+
producerFingerprint: options.producerFingerprint
|
|
1530
|
+
});
|
|
1531
|
+
const tracks = STARTER_TRACK_DEFINITIONS.map((definition) => materializeStarterTrack(definition, inventory)).filter((track) => track.compatibility.valid && matchesFilters(track, filters));
|
|
1532
|
+
return {
|
|
1533
|
+
schemaVersion: STARTER_TRACK_SCHEMA_VERSION,
|
|
1534
|
+
filters,
|
|
1535
|
+
total: tracks.length,
|
|
1536
|
+
tracks
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
function stringSearchValue(value) {
|
|
1540
|
+
if (typeof value === "string") return value;
|
|
1541
|
+
if (Array.isArray(value)) return value.find((entry) => typeof entry === "string");
|
|
1542
|
+
}
|
|
1543
|
+
function parseStarterTrackFilters(search) {
|
|
1544
|
+
const candidate = {};
|
|
1545
|
+
for (const [key, urlKey] of Object.entries(STARTER_TRACK_FILTER_URL_KEYS)) {
|
|
1546
|
+
const value = stringSearchValue(search[urlKey]);
|
|
1547
|
+
if (value) candidate[key] = value;
|
|
1548
|
+
}
|
|
1549
|
+
const parsed = StarterTrackFiltersSchema.safeParse(candidate);
|
|
1550
|
+
return parsed.success ? parsed.data : {};
|
|
1551
|
+
}
|
|
1552
|
+
function createStarterTrackFilterSearchParams(filters) {
|
|
1553
|
+
const parsed = StarterTrackFiltersSchema.parse(filters);
|
|
1554
|
+
const params = new URLSearchParams();
|
|
1555
|
+
for (const [key, urlKey] of Object.entries(STARTER_TRACK_FILTER_URL_KEYS)) {
|
|
1556
|
+
const value = parsed[key];
|
|
1557
|
+
if (value) params.set(urlKey, value);
|
|
1558
|
+
}
|
|
1559
|
+
return params;
|
|
1560
|
+
}
|
|
1561
|
+
function briefTokens(brief) {
|
|
1562
|
+
return (brief.toLowerCase().match(/(?<![a-z0-9])(?:react-native|\.net|c\+\+|c#)(?![a-z0-9])|[a-z0-9]+/g) ?? []).filter((token) => token.length >= 2);
|
|
1563
|
+
}
|
|
1564
|
+
function recommendStarterTrack(brief, options = {}) {
|
|
1565
|
+
const catalog = getStarterTrackCatalog({
|
|
1566
|
+
inventory: options.inventory,
|
|
1567
|
+
receipt: options.receipt,
|
|
1568
|
+
catalogVersion: options.catalogVersion,
|
|
1569
|
+
producerFingerprint: options.producerFingerprint
|
|
1570
|
+
});
|
|
1571
|
+
const allowedTracks = options.trackIds ? catalog.tracks.filter((track) => options.trackIds?.includes(track.id)) : catalog.tracks;
|
|
1572
|
+
const pool = options.ecosystem ? allowedTracks.filter((track) => track.ecosystem === options.ecosystem) : allowedTracks;
|
|
1573
|
+
if (pool.length === 0) {
|
|
1574
|
+
const constraint = options.ecosystem ? ` for ecosystem '${options.ecosystem}'` : options.trackIds ? " in the requested track set" : "";
|
|
1575
|
+
throw new Error(`No schema-valid starter track is available${constraint}.`);
|
|
1576
|
+
}
|
|
1577
|
+
const normalized = brief.trim().toLowerCase();
|
|
1578
|
+
const tokens = new Set(briefTokens(brief));
|
|
1579
|
+
let best = pool[0];
|
|
1580
|
+
let bestScore = -1;
|
|
1581
|
+
let bestTerms = [];
|
|
1582
|
+
for (const track of pool) {
|
|
1583
|
+
const definition = STARTER_TRACK_DEFINITIONS.find((entry) => entry.id === track.id);
|
|
1584
|
+
if (!definition) continue;
|
|
1585
|
+
let score = 0;
|
|
1586
|
+
const matched = /* @__PURE__ */ new Set();
|
|
1587
|
+
for (const phrase of definition.phrases) if (normalized.includes(phrase)) {
|
|
1588
|
+
score += 8;
|
|
1589
|
+
matched.add(phrase);
|
|
1590
|
+
}
|
|
1591
|
+
for (const keyword of definition.keywords) if (tokens.has(keyword)) {
|
|
1592
|
+
score += 3;
|
|
1593
|
+
matched.add(keyword);
|
|
1594
|
+
}
|
|
1595
|
+
if (tokens.has(track.ecosystem)) {
|
|
1596
|
+
score += 4;
|
|
1597
|
+
matched.add(track.ecosystem);
|
|
1598
|
+
}
|
|
1599
|
+
if (score > bestScore) {
|
|
1600
|
+
best = track;
|
|
1601
|
+
bestScore = score;
|
|
1602
|
+
bestTerms = [...matched];
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (!best) throw new Error("No schema-valid starter track is available.");
|
|
1606
|
+
const fallback = bestScore <= 0;
|
|
1607
|
+
return {
|
|
1608
|
+
schemaVersion: STARTER_TRACK_SCHEMA_VERSION,
|
|
1609
|
+
recommendationMode: "deterministic",
|
|
1610
|
+
modelUsed: false,
|
|
1611
|
+
track: best,
|
|
1612
|
+
matchedTerms: bestTerms.sort(),
|
|
1613
|
+
score: Math.max(bestScore, 0),
|
|
1614
|
+
rationale: fallback ? `No strong signal matched. Start with ${best.name}, then review every editable Stack Part.` : `${best.name} matched ${bestTerms.sort().join(", ")}.`,
|
|
1615
|
+
constraints: [
|
|
1616
|
+
`The proposed graph has ${best.stackPartSpecs.length} schema-valid Stack Parts.`,
|
|
1617
|
+
`Current fail-closed evidence is ${best.evidence.level}; declared evidence is ${best.evidence.declaredLevel}.`,
|
|
1618
|
+
"Review the normal project plan before creation."
|
|
1619
|
+
]
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
//#endregion
|
|
1624
|
+
//#region src/telemetry/telemetry.ts
|
|
1625
|
+
const TELEMETRY_ACTION_IDS = [
|
|
1626
|
+
"create",
|
|
1627
|
+
"sponsors",
|
|
1628
|
+
"docs",
|
|
1629
|
+
"builder",
|
|
1630
|
+
"add",
|
|
1631
|
+
"status",
|
|
1632
|
+
"remove",
|
|
1633
|
+
"replace",
|
|
1634
|
+
"history",
|
|
1635
|
+
"telemetry",
|
|
1636
|
+
"update-deps",
|
|
1637
|
+
"gen",
|
|
1638
|
+
"registry",
|
|
1639
|
+
"recovery",
|
|
1640
|
+
"recommend",
|
|
1641
|
+
"update",
|
|
1642
|
+
"adopt",
|
|
1643
|
+
"check",
|
|
1644
|
+
"doctor",
|
|
1645
|
+
"mcp",
|
|
1646
|
+
"bfs_add_feature",
|
|
1647
|
+
"bfs_apply_doctor_fix",
|
|
1648
|
+
"bfs_apply_gen",
|
|
1649
|
+
"bfs_apply_part_removal",
|
|
1650
|
+
"bfs_apply_primary_role_replacement",
|
|
1651
|
+
"bfs_apply_project_update",
|
|
1652
|
+
"bfs_apply_registry_add",
|
|
1653
|
+
"bfs_apply_stack_update",
|
|
1654
|
+
"bfs_check_compatibility",
|
|
1655
|
+
"bfs_check_project",
|
|
1656
|
+
"bfs_check_recipes",
|
|
1657
|
+
"bfs_confirm_project_adoption",
|
|
1658
|
+
"bfs_create_project",
|
|
1659
|
+
"bfs_get_capability_evidence",
|
|
1660
|
+
"bfs_get_guidance",
|
|
1661
|
+
"bfs_get_project_context",
|
|
1662
|
+
"bfs_get_project_recovery_point",
|
|
1663
|
+
"bfs_get_project_status",
|
|
1664
|
+
"bfs_get_recipe_history",
|
|
1665
|
+
"bfs_get_schema",
|
|
1666
|
+
"bfs_list_presets",
|
|
1667
|
+
"bfs_list_project_recovery_points",
|
|
1668
|
+
"bfs_list_starter_tracks",
|
|
1669
|
+
"bfs_plan_addition",
|
|
1670
|
+
"bfs_plan_doctor_fix",
|
|
1671
|
+
"bfs_plan_gen",
|
|
1672
|
+
"bfs_plan_part_removal",
|
|
1673
|
+
"bfs_plan_primary_role_replacement",
|
|
1674
|
+
"bfs_plan_project",
|
|
1675
|
+
"bfs_plan_project_adoption",
|
|
1676
|
+
"bfs_plan_project_update",
|
|
1677
|
+
"bfs_plan_registry_add",
|
|
1678
|
+
"bfs_plan_stack_update",
|
|
1679
|
+
"bfs_prune_project_recovery_points",
|
|
1680
|
+
"bfs_recommend_stack",
|
|
1681
|
+
"bfs_recover_project_transaction",
|
|
1682
|
+
"bfs_verify_project_recovery_point",
|
|
1683
|
+
"campaign-viewed",
|
|
1684
|
+
"campaign-preset-opened",
|
|
1685
|
+
"builder-viewed",
|
|
1686
|
+
"builder-view-changed",
|
|
1687
|
+
"builder-command-copied",
|
|
1688
|
+
"builder-run-started",
|
|
1689
|
+
"builder-run-ready",
|
|
1690
|
+
"builder-run-failed",
|
|
1691
|
+
"builder-run-stopped",
|
|
1692
|
+
"builder-file-edited",
|
|
1693
|
+
"builder-zip-started",
|
|
1694
|
+
"builder-zip-downloaded",
|
|
1695
|
+
"builder-zip-failed",
|
|
1696
|
+
"builder-share-prompted",
|
|
1697
|
+
"builder-stack-shared",
|
|
1698
|
+
"builder-github-clicked",
|
|
1699
|
+
"builder-starter-track-applied",
|
|
1700
|
+
"builder-incompatibility-recovered",
|
|
1701
|
+
"builder-plan-abandoned"
|
|
1702
|
+
];
|
|
1703
|
+
const TELEMETRY_MODE_IDS = [
|
|
1704
|
+
"dry-run",
|
|
1705
|
+
"defaults",
|
|
1706
|
+
"create",
|
|
1707
|
+
"apply",
|
|
1708
|
+
"json",
|
|
1709
|
+
"human",
|
|
1710
|
+
"clear",
|
|
1711
|
+
"list",
|
|
1712
|
+
"status",
|
|
1713
|
+
"enable",
|
|
1714
|
+
"disable",
|
|
1715
|
+
"check",
|
|
1716
|
+
"patch",
|
|
1717
|
+
"all",
|
|
1718
|
+
"update",
|
|
1719
|
+
"plan",
|
|
1720
|
+
"show",
|
|
1721
|
+
"verify",
|
|
1722
|
+
"prune",
|
|
1723
|
+
"prune-apply",
|
|
1724
|
+
"read",
|
|
1725
|
+
"write",
|
|
1726
|
+
"recover",
|
|
1727
|
+
"record-baseline",
|
|
1728
|
+
"confirm",
|
|
1729
|
+
"fix-apply",
|
|
1730
|
+
"fix-plan",
|
|
1731
|
+
"full",
|
|
1732
|
+
"config-only",
|
|
1733
|
+
"solo",
|
|
1734
|
+
"multi"
|
|
1735
|
+
];
|
|
1736
|
+
const TELEMETRY_ERROR_NAMES = [
|
|
1737
|
+
"Error",
|
|
1738
|
+
"TypeError",
|
|
1739
|
+
"RangeError",
|
|
1740
|
+
"ReferenceError",
|
|
1741
|
+
"SyntaxError",
|
|
1742
|
+
"URIError",
|
|
1743
|
+
"EvalError",
|
|
1744
|
+
"AggregateError",
|
|
1745
|
+
"AbortError",
|
|
1746
|
+
"TimeoutError",
|
|
1747
|
+
"CLIError",
|
|
1748
|
+
"UserCancelledError",
|
|
1749
|
+
"UnknownError",
|
|
1750
|
+
"UpdateError",
|
|
1751
|
+
"AdoptionError",
|
|
1752
|
+
"ConfigDriftRepairError",
|
|
1753
|
+
"ProjectStatusError"
|
|
1754
|
+
];
|
|
1755
|
+
const TELEMETRY_SETUP_FAILURE_IDS = [
|
|
1756
|
+
"install-dependencies",
|
|
1757
|
+
"database-setup",
|
|
1758
|
+
"cargo-build",
|
|
1759
|
+
"python-uv-sync",
|
|
1760
|
+
"python-pip-install",
|
|
1761
|
+
"python-poetry-install",
|
|
1762
|
+
"go-mod-tidy",
|
|
1763
|
+
"maven-tests",
|
|
1764
|
+
"gradle-tests",
|
|
1765
|
+
"elixir-deps-compile"
|
|
1766
|
+
];
|
|
1767
|
+
const TELEMETRY_FAILURE_STAGES = [
|
|
1768
|
+
...TELEMETRY_SETUP_FAILURE_IDS,
|
|
1769
|
+
"generation",
|
|
1770
|
+
"browser_support",
|
|
1771
|
+
"runtime_boot",
|
|
1772
|
+
"project_mount",
|
|
1773
|
+
"source_sync",
|
|
1774
|
+
"dependency_install",
|
|
1775
|
+
"server_start",
|
|
1776
|
+
"server_timeout",
|
|
1777
|
+
"server_exit",
|
|
1778
|
+
"archive_generation",
|
|
1779
|
+
"browser_download"
|
|
1780
|
+
];
|
|
1781
|
+
const TELEMETRY_FAILURE_REASONS = [
|
|
1782
|
+
"network",
|
|
1783
|
+
"registry-not-found",
|
|
1784
|
+
"registry-auth",
|
|
1785
|
+
"peer-conflict",
|
|
1786
|
+
"arborist-crash",
|
|
1787
|
+
"lockfile-mismatch",
|
|
1788
|
+
"disk-space",
|
|
1789
|
+
"permission",
|
|
1790
|
+
"path-too-long",
|
|
1791
|
+
"missing-tool",
|
|
1792
|
+
"engine-mismatch",
|
|
1793
|
+
"build-script-failed",
|
|
1794
|
+
"out-of-memory",
|
|
1795
|
+
"timeout",
|
|
1796
|
+
"unknown",
|
|
1797
|
+
"generation_failed",
|
|
1798
|
+
"browser_runtime_unsupported",
|
|
1799
|
+
"runtime_boot_failed",
|
|
1800
|
+
"project_mount_failed",
|
|
1801
|
+
"source_sync_failed",
|
|
1802
|
+
"dependency_install_failed",
|
|
1803
|
+
"dependency_install_exit",
|
|
1804
|
+
"server_start_failed",
|
|
1805
|
+
"server_ready_timeout",
|
|
1806
|
+
"server_process_exit",
|
|
1807
|
+
"archive_generation_failed",
|
|
1808
|
+
"browser_download_failed"
|
|
1809
|
+
];
|
|
1810
|
+
const TELEMETRY_CI_PROVIDERS = [
|
|
1811
|
+
"github-actions",
|
|
1812
|
+
"gitlab-ci",
|
|
1813
|
+
"circleci",
|
|
1814
|
+
"vercel",
|
|
1815
|
+
"other"
|
|
1816
|
+
];
|
|
1817
|
+
const TELEMETRY_EXECUTION_RUNTIMES = [
|
|
1818
|
+
"bun",
|
|
1819
|
+
"node",
|
|
1820
|
+
"browser"
|
|
1821
|
+
];
|
|
1822
|
+
const TELEMETRY_PLATFORMS = [
|
|
1823
|
+
"aix",
|
|
1824
|
+
"android",
|
|
1825
|
+
"darwin",
|
|
1826
|
+
"freebsd",
|
|
1827
|
+
"haiku",
|
|
1828
|
+
"linux",
|
|
1829
|
+
"openbsd",
|
|
1830
|
+
"sunos",
|
|
1831
|
+
"win32",
|
|
1832
|
+
"cygwin",
|
|
1833
|
+
"netbsd",
|
|
1834
|
+
"browser"
|
|
1835
|
+
];
|
|
1836
|
+
const TELEMETRY_PROJECT_CONFIG_KEYS = [
|
|
1837
|
+
"ecosystem",
|
|
1838
|
+
"database",
|
|
1839
|
+
"orm",
|
|
1840
|
+
"backend",
|
|
1841
|
+
"runtime",
|
|
1842
|
+
"frontend",
|
|
1843
|
+
"addons",
|
|
1844
|
+
"examples",
|
|
1845
|
+
"auth",
|
|
1846
|
+
"payments",
|
|
1847
|
+
"git",
|
|
1848
|
+
"packageManager",
|
|
1849
|
+
"workspaceShape",
|
|
1850
|
+
"versionChannel",
|
|
1851
|
+
"install",
|
|
1852
|
+
"dbSetup",
|
|
1853
|
+
"api",
|
|
1854
|
+
"webDeploy",
|
|
1855
|
+
"serverDeploy",
|
|
1856
|
+
"astroIntegration",
|
|
1857
|
+
"ai",
|
|
1858
|
+
"effect",
|
|
1859
|
+
"stateManagement",
|
|
1860
|
+
"forms",
|
|
1861
|
+
"testing",
|
|
1862
|
+
"email",
|
|
1863
|
+
"cssFramework",
|
|
1864
|
+
"uiLibrary",
|
|
1865
|
+
"shadcnBase",
|
|
1866
|
+
"shadcnStyle",
|
|
1867
|
+
"shadcnIconLibrary",
|
|
1868
|
+
"shadcnColorTheme",
|
|
1869
|
+
"shadcnBaseColor",
|
|
1870
|
+
"shadcnFont",
|
|
1871
|
+
"shadcnRadius",
|
|
1872
|
+
"validation",
|
|
1873
|
+
"realtime",
|
|
1874
|
+
"jobQueue",
|
|
1875
|
+
"animation",
|
|
1876
|
+
"fileUpload",
|
|
1877
|
+
"logging",
|
|
1878
|
+
"observability",
|
|
1879
|
+
"featureFlags",
|
|
1880
|
+
"integrations",
|
|
1881
|
+
"ecommerce",
|
|
1882
|
+
"analytics",
|
|
1883
|
+
"cms",
|
|
1884
|
+
"caching",
|
|
1885
|
+
"rateLimit",
|
|
1886
|
+
"botProtection",
|
|
1887
|
+
"i18n",
|
|
1888
|
+
"search",
|
|
1889
|
+
"vectorDb",
|
|
1890
|
+
"fileStorage",
|
|
1891
|
+
"mobileNavigation",
|
|
1892
|
+
"mobileUI",
|
|
1893
|
+
"mobileStorage",
|
|
1894
|
+
"mobileTesting",
|
|
1895
|
+
"mobilePush",
|
|
1896
|
+
"mobileOTA",
|
|
1897
|
+
"mobileDeepLinking",
|
|
1898
|
+
"mobileLibraries",
|
|
1899
|
+
"rustWebFramework",
|
|
1900
|
+
"rustFrontend",
|
|
1901
|
+
"dotnetFrontend",
|
|
1902
|
+
"kotlinMobile",
|
|
1903
|
+
"kotlinMobileLibraries",
|
|
1904
|
+
"swiftMobile",
|
|
1905
|
+
"dartMobile",
|
|
1906
|
+
"rustOrm",
|
|
1907
|
+
"rustApi",
|
|
1908
|
+
"rustCli",
|
|
1909
|
+
"rustLibraries",
|
|
1910
|
+
"rustLogging",
|
|
1911
|
+
"rustErrorHandling",
|
|
1912
|
+
"rustCaching",
|
|
1913
|
+
"rustAuth",
|
|
1914
|
+
"rustRealtime",
|
|
1915
|
+
"rustMessageQueue",
|
|
1916
|
+
"rustObservability",
|
|
1917
|
+
"rustTemplating",
|
|
1918
|
+
"pythonWebFramework",
|
|
1919
|
+
"pythonOrm",
|
|
1920
|
+
"pythonValidation",
|
|
1921
|
+
"pythonAi",
|
|
1922
|
+
"pythonAuth",
|
|
1923
|
+
"pythonApi",
|
|
1924
|
+
"pythonTaskQueue",
|
|
1925
|
+
"pythonGraphql",
|
|
1926
|
+
"pythonQuality",
|
|
1927
|
+
"pythonTesting",
|
|
1928
|
+
"pythonCaching",
|
|
1929
|
+
"pythonRealtime",
|
|
1930
|
+
"pythonObservability",
|
|
1931
|
+
"pythonCli",
|
|
1932
|
+
"pythonCloudSdk",
|
|
1933
|
+
"pythonHttpClient",
|
|
1934
|
+
"pythonData",
|
|
1935
|
+
"pythonMedia",
|
|
1936
|
+
"pythonServer",
|
|
1937
|
+
"pythonPackageManager",
|
|
1938
|
+
"pythonMessageQueue",
|
|
1939
|
+
"goWebFramework",
|
|
1940
|
+
"goOrm",
|
|
1941
|
+
"goApi",
|
|
1942
|
+
"goCli",
|
|
1943
|
+
"goLogging",
|
|
1944
|
+
"goAuth",
|
|
1945
|
+
"goTesting",
|
|
1946
|
+
"goRealtime",
|
|
1947
|
+
"goMessageQueue",
|
|
1948
|
+
"goCaching",
|
|
1949
|
+
"goConfig",
|
|
1950
|
+
"goObservability",
|
|
1951
|
+
"goValidation",
|
|
1952
|
+
"goQuality",
|
|
1953
|
+
"goMigrations",
|
|
1954
|
+
"goTemplating",
|
|
1955
|
+
"goProtoTooling",
|
|
1956
|
+
"goDI",
|
|
1957
|
+
"javaLanguage",
|
|
1958
|
+
"javaWebFramework",
|
|
1959
|
+
"javaBuildTool",
|
|
1960
|
+
"javaOrm",
|
|
1961
|
+
"javaAuth",
|
|
1962
|
+
"javaApi",
|
|
1963
|
+
"javaLogging",
|
|
1964
|
+
"javaLibraries",
|
|
1965
|
+
"javaTestingLibraries",
|
|
1966
|
+
"dotnetWebFramework",
|
|
1967
|
+
"dotnetOrm",
|
|
1968
|
+
"dotnetAuth",
|
|
1969
|
+
"dotnetApi",
|
|
1970
|
+
"dotnetTesting",
|
|
1971
|
+
"dotnetJobQueue",
|
|
1972
|
+
"dotnetRealtime",
|
|
1973
|
+
"dotnetObservability",
|
|
1974
|
+
"dotnetValidation",
|
|
1975
|
+
"dotnetCaching",
|
|
1976
|
+
"dotnetDeploy",
|
|
1977
|
+
"dotnetLibraries",
|
|
1978
|
+
"elixirWebFramework",
|
|
1979
|
+
"elixirOrm",
|
|
1980
|
+
"elixirAuth",
|
|
1981
|
+
"elixirApi",
|
|
1982
|
+
"elixirRealtime",
|
|
1983
|
+
"elixirJobs",
|
|
1984
|
+
"elixirValidation",
|
|
1985
|
+
"elixirHttp",
|
|
1986
|
+
"elixirJson",
|
|
1987
|
+
"elixirEmail",
|
|
1988
|
+
"elixirCaching",
|
|
1989
|
+
"elixirObservability",
|
|
1990
|
+
"elixirTesting",
|
|
1991
|
+
"elixirQuality",
|
|
1992
|
+
"elixirI18n",
|
|
1993
|
+
"elixirHttpServer",
|
|
1994
|
+
"elixirApplicationFramework",
|
|
1995
|
+
"elixirDocumentation",
|
|
1996
|
+
"elixirClustering",
|
|
1997
|
+
"elixirDeploy",
|
|
1998
|
+
"elixirLibraries",
|
|
1999
|
+
"aiDocs"
|
|
2000
|
+
];
|
|
2001
|
+
const TELEMETRY_STACK_DIMENSION_KEYS = [
|
|
2002
|
+
...TELEMETRY_PROJECT_CONFIG_KEYS,
|
|
2003
|
+
"webFrontend",
|
|
2004
|
+
"nativeFrontend",
|
|
2005
|
+
"backendLibraries",
|
|
2006
|
+
"codeQuality",
|
|
2007
|
+
"documentation",
|
|
2008
|
+
"appPlatforms",
|
|
2009
|
+
"aiSdk",
|
|
2010
|
+
"stackMode",
|
|
2011
|
+
"stackPartSelections",
|
|
2012
|
+
"stackPartRoles",
|
|
2013
|
+
"stackPartEcosystems",
|
|
2014
|
+
"multiEcosystem",
|
|
2015
|
+
"decision_stage",
|
|
2016
|
+
"selection_outcome",
|
|
2017
|
+
"selected_evidence_level",
|
|
2018
|
+
"selection_problem",
|
|
2019
|
+
"starter_track",
|
|
2020
|
+
"campaign",
|
|
2021
|
+
"moment",
|
|
2022
|
+
"placement",
|
|
2023
|
+
"preset",
|
|
2024
|
+
"rerun",
|
|
2025
|
+
"target",
|
|
2026
|
+
"view",
|
|
2027
|
+
"targetEcosystem",
|
|
2028
|
+
"generatorKind",
|
|
2029
|
+
"registryAction",
|
|
2030
|
+
"bun_version"
|
|
2031
|
+
];
|
|
2032
|
+
const TELEMETRY_DECISION_STAGES = [
|
|
2033
|
+
"discover",
|
|
2034
|
+
"evaluate",
|
|
2035
|
+
"plan",
|
|
2036
|
+
"create"
|
|
2037
|
+
];
|
|
2038
|
+
const TELEMETRY_SELECTION_OUTCOMES = [
|
|
2039
|
+
"track-applied",
|
|
2040
|
+
"incompatibility-recovered",
|
|
2041
|
+
"plan-abandoned",
|
|
2042
|
+
"create-completed",
|
|
2043
|
+
"create-failed",
|
|
2044
|
+
"handoff-completed"
|
|
2045
|
+
];
|
|
2046
|
+
const TELEMETRY_SELECTION_PROBLEMS = [
|
|
2047
|
+
"none",
|
|
2048
|
+
"discoverability",
|
|
2049
|
+
"missing-capability",
|
|
2050
|
+
"compatibility",
|
|
2051
|
+
"reliability"
|
|
2052
|
+
];
|
|
2053
|
+
const MAX_ARRAY_ITEMS = 64;
|
|
2054
|
+
const MAX_VALUE_LENGTH = 100;
|
|
2055
|
+
const VERSION_PATTERN = /^\d{1,3}\.\d{1,3}\.\d{1,5}$/;
|
|
2056
|
+
const NODE_VERSION_PATTERN = /^v?\d{1,3}\.\d{1,3}\.\d{1,5}$/;
|
|
2057
|
+
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2058
|
+
const actionIdSet = new Set(TELEMETRY_ACTION_IDS);
|
|
2059
|
+
const modeIdSet = new Set(TELEMETRY_MODE_IDS);
|
|
2060
|
+
const errorNameSet = new Set(TELEMETRY_ERROR_NAMES);
|
|
2061
|
+
const failureStageSet = new Set(TELEMETRY_FAILURE_STAGES);
|
|
2062
|
+
const failureReasonSet = new Set(TELEMETRY_FAILURE_REASONS);
|
|
2063
|
+
const setupFailureIdSet = new Set(TELEMETRY_SETUP_FAILURE_IDS);
|
|
2064
|
+
const ciProviderSet = new Set(TELEMETRY_CI_PROVIDERS);
|
|
2065
|
+
const executionRuntimeSet = new Set(TELEMETRY_EXECUTION_RUNTIMES);
|
|
2066
|
+
const platformSet = new Set(TELEMETRY_PLATFORMS);
|
|
2067
|
+
const telemetryProjectConfigKeySet = new Set(TELEMETRY_PROJECT_CONFIG_KEYS);
|
|
2068
|
+
const telemetryStackDimensionKeySet = new Set(TELEMETRY_STACK_DIMENSION_KEYS);
|
|
2069
|
+
const stackPartSelectionSet = new Set(STACK_TOOL_DEFINITIONS.flatMap((definition) => definition.roles.flatMap((role) => definition.ecosystems.map((ecosystem) => `${role}:${ecosystem}:${definition.toolId}`))));
|
|
2070
|
+
const frontendToolIds = new Set(STACK_TOOL_DEFINITIONS.filter((definition) => definition.roles.includes("frontend") || definition.roles.includes("mobile")).map((definition) => definition.toolId));
|
|
2071
|
+
const backendToolIds = new Set(STACK_TOOL_DEFINITIONS.filter((definition) => definition.roles.includes("backend")).map((definition) => definition.toolId));
|
|
2072
|
+
const databaseToolIds = new Set(STACK_TOOL_DEFINITIONS.filter((definition) => definition.roles.includes("database")).map((definition) => definition.toolId));
|
|
2073
|
+
const stackPartEcosystemIds = new Set([
|
|
2074
|
+
...EcosystemSchema.options,
|
|
2075
|
+
"kotlin",
|
|
2076
|
+
"swift",
|
|
2077
|
+
"dart",
|
|
2078
|
+
"universal"
|
|
2079
|
+
]);
|
|
2080
|
+
const SELECTION_ONLY_CATEGORIES = {
|
|
2081
|
+
webFrontend: "webFrontend",
|
|
2082
|
+
nativeFrontend: "nativeFrontend",
|
|
2083
|
+
backendLibraries: "backendLibraries",
|
|
2084
|
+
codeQuality: "codeQuality",
|
|
2085
|
+
documentation: "documentation",
|
|
2086
|
+
appPlatforms: "appPlatforms",
|
|
2087
|
+
aiSdk: "ai"
|
|
2088
|
+
};
|
|
2089
|
+
const SELECTION_ONLY_ARRAY_KEYS = new Set([
|
|
2090
|
+
"webFrontend",
|
|
2091
|
+
"nativeFrontend",
|
|
2092
|
+
"codeQuality",
|
|
2093
|
+
"documentation",
|
|
2094
|
+
"appPlatforms"
|
|
2095
|
+
]);
|
|
2096
|
+
const FIXED_DIMENSION_VALUES = {
|
|
2097
|
+
stackMode: new Set(["solo", "multi"]),
|
|
2098
|
+
decision_stage: new Set(TELEMETRY_DECISION_STAGES),
|
|
2099
|
+
selection_outcome: new Set(TELEMETRY_SELECTION_OUTCOMES),
|
|
2100
|
+
selected_evidence_level: new Set(CAPABILITY_EVIDENCE_LEVEL_IDS),
|
|
2101
|
+
selection_problem: new Set(TELEMETRY_SELECTION_PROBLEMS),
|
|
2102
|
+
starter_track: new Set(STARTER_TRACK_IDS),
|
|
2103
|
+
campaign: new Set(["run-before-you-clone"]),
|
|
2104
|
+
moment: new Set(["run", "download"]),
|
|
2105
|
+
placement: new Set([
|
|
2106
|
+
"hero",
|
|
2107
|
+
"preset_grid",
|
|
2108
|
+
"footer"
|
|
2109
|
+
]),
|
|
2110
|
+
preset: new Set([
|
|
2111
|
+
"t3",
|
|
2112
|
+
"nextjs-saas",
|
|
2113
|
+
"nextjs-minimal",
|
|
2114
|
+
"mern",
|
|
2115
|
+
"pern",
|
|
2116
|
+
"react-hono",
|
|
2117
|
+
"tanstack-start",
|
|
2118
|
+
"tanstack-hono",
|
|
2119
|
+
"sveltekit-fullstack",
|
|
2120
|
+
"sveltekit-hono",
|
|
2121
|
+
"nuxt-fullstack",
|
|
2122
|
+
"nuxt-hono",
|
|
2123
|
+
"astro-react",
|
|
2124
|
+
"astro-fullstack",
|
|
2125
|
+
"tanstack-fullstack",
|
|
2126
|
+
"solidstart-fullstack",
|
|
2127
|
+
"solid-hono",
|
|
2128
|
+
"angular-express",
|
|
2129
|
+
"qwik-city",
|
|
2130
|
+
"uniwind",
|
|
2131
|
+
"expo-bare",
|
|
2132
|
+
"ai-cli-agent-workbench",
|
|
2133
|
+
"ai-cli-react-hono",
|
|
2134
|
+
"ai-cli-frontend-lab",
|
|
2135
|
+
"rust-fullstack",
|
|
2136
|
+
"rust-api",
|
|
2137
|
+
"rust-grpc",
|
|
2138
|
+
"rust-cli",
|
|
2139
|
+
"python-fastapi",
|
|
2140
|
+
"python-django",
|
|
2141
|
+
"python-ai-agent",
|
|
2142
|
+
"python-ai-anthropic",
|
|
2143
|
+
"go-gin",
|
|
2144
|
+
"go-echo",
|
|
2145
|
+
"go-grpc",
|
|
2146
|
+
"go-cli",
|
|
2147
|
+
"java-spring",
|
|
2148
|
+
"java-jpa",
|
|
2149
|
+
"java-secure",
|
|
2150
|
+
"dotnet-minimal-api",
|
|
2151
|
+
"dotnet-graphql",
|
|
2152
|
+
"dotnet-worker",
|
|
2153
|
+
"elixir-phoenix-api",
|
|
2154
|
+
"elixir-liveview-full",
|
|
2155
|
+
"elixir-plain-worker"
|
|
2156
|
+
]),
|
|
2157
|
+
target: new Set([
|
|
2158
|
+
"clipboard",
|
|
2159
|
+
"native",
|
|
2160
|
+
"x"
|
|
2161
|
+
]),
|
|
2162
|
+
view: new Set([
|
|
2163
|
+
"command",
|
|
2164
|
+
"preview",
|
|
2165
|
+
"run",
|
|
2166
|
+
"presets",
|
|
2167
|
+
"saved"
|
|
2168
|
+
]),
|
|
2169
|
+
targetEcosystem: new Set(EcosystemSchema.options),
|
|
2170
|
+
generatorKind: new Set(["resource", "route"]),
|
|
2171
|
+
registryAction: new Set(["add", "list"])
|
|
2172
|
+
};
|
|
2173
|
+
function isTelemetryStackValue(value) {
|
|
2174
|
+
return typeof value === "string" || typeof value === "boolean" || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
2175
|
+
}
|
|
2176
|
+
function sanitizeCanonicalList(value, allowed, output, maxItems = MAX_ARRAY_ITEMS) {
|
|
2177
|
+
if (typeof value === "string" && value.length > MAX_VALUE_LENGTH) return void 0;
|
|
2178
|
+
const entries = typeof value === "string" ? value.split(",").map((entry) => entry.trim()) : Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
|
|
2179
|
+
if (!entries || entries.length === 0 || entries.length > maxItems) return void 0;
|
|
2180
|
+
if (entries.some((entry) => !allowed.has(entry))) return void 0;
|
|
2181
|
+
const canonical = [...new Set(entries)];
|
|
2182
|
+
return output === "array" ? canonical : canonical.join(",");
|
|
2183
|
+
}
|
|
2184
|
+
function sanitizeProjectConfigValue(key, value) {
|
|
2185
|
+
if (value === void 0) return void 0;
|
|
2186
|
+
if (key === "frontend") return sanitizeCanonicalList(value, new Set(frontendToolIds).add("none"), "array");
|
|
2187
|
+
if (key === "backend") return sanitizeCanonicalList(value, new Set(backendToolIds).add("none"), "comma-list");
|
|
2188
|
+
if (key === "database") return sanitizeCanonicalList(value, new Set(databaseToolIds).add("none"), "comma-list");
|
|
2189
|
+
if (key === "ecosystem") {
|
|
2190
|
+
const allowed = new Set(stackPartEcosystemIds);
|
|
2191
|
+
allowed.delete("universal");
|
|
2192
|
+
return sanitizeCanonicalList(value, allowed, "comma-list");
|
|
2193
|
+
}
|
|
2194
|
+
if (Array.isArray(value) && value.length > MAX_ARRAY_ITEMS) return void 0;
|
|
2195
|
+
const result = ProjectConfigSchema.shape[key].safeParse(value);
|
|
2196
|
+
return result.success && isTelemetryStackValue(result.data) ? result.data : void 0;
|
|
2197
|
+
}
|
|
2198
|
+
function isTelemetryStackDimensionKey(key) {
|
|
2199
|
+
return telemetryStackDimensionKeySet.has(key);
|
|
2200
|
+
}
|
|
2201
|
+
function isRegisteredTelemetryStackPartSelection(value) {
|
|
2202
|
+
return stackPartSelectionSet.has(value);
|
|
2203
|
+
}
|
|
2204
|
+
function sanitizeKnownValue(value, allowed) {
|
|
2205
|
+
return typeof value === "string" && allowed.has(value) ? value : void 0;
|
|
2206
|
+
}
|
|
2207
|
+
function sanitizeTelemetryAction(value) {
|
|
2208
|
+
return sanitizeKnownValue(value, actionIdSet);
|
|
2209
|
+
}
|
|
2210
|
+
function sanitizeTelemetryMode(value) {
|
|
2211
|
+
return sanitizeKnownValue(value, modeIdSet);
|
|
2212
|
+
}
|
|
2213
|
+
function sanitizeTelemetryMachineId(value) {
|
|
2214
|
+
return typeof value === "string" && UUID_V4_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
2215
|
+
}
|
|
2216
|
+
function sanitizeTelemetryErrorName(value) {
|
|
2217
|
+
return sanitizeKnownValue(value, errorNameSet);
|
|
2218
|
+
}
|
|
2219
|
+
function sanitizeTelemetryFailureStage(value) {
|
|
2220
|
+
return sanitizeKnownValue(value, failureStageSet);
|
|
2221
|
+
}
|
|
2222
|
+
function sanitizeTelemetryFailureReason(value) {
|
|
2223
|
+
return sanitizeKnownValue(value, failureReasonSet);
|
|
2224
|
+
}
|
|
2225
|
+
function sanitizeTelemetrySetupFailure(value) {
|
|
2226
|
+
return sanitizeKnownValue(value, setupFailureIdSet);
|
|
2227
|
+
}
|
|
2228
|
+
function sanitizeTelemetrySetupFailures(value) {
|
|
2229
|
+
if (!Array.isArray(value) || value.length > 32) return void 0;
|
|
2230
|
+
const sanitized = value.map(sanitizeTelemetrySetupFailure);
|
|
2231
|
+
if (!sanitized.every((item) => item !== void 0)) return void 0;
|
|
2232
|
+
return [...new Set(sanitized)];
|
|
2233
|
+
}
|
|
2234
|
+
function sanitizeTelemetryCiProvider(value) {
|
|
2235
|
+
return sanitizeKnownValue(value, ciProviderSet);
|
|
2236
|
+
}
|
|
2237
|
+
function sanitizeTelemetryExecutionRuntime(value) {
|
|
2238
|
+
return sanitizeKnownValue(value, executionRuntimeSet);
|
|
2239
|
+
}
|
|
2240
|
+
function sanitizeTelemetryCliVersion(value) {
|
|
2241
|
+
return typeof value === "string" && VERSION_PATTERN.test(value) ? value : void 0;
|
|
2242
|
+
}
|
|
2243
|
+
function sanitizeTelemetryNodeVersion(value) {
|
|
2244
|
+
return typeof value === "string" && NODE_VERSION_PATTERN.test(value) ? value : void 0;
|
|
2245
|
+
}
|
|
2246
|
+
function sanitizeTelemetryPlatform(value) {
|
|
2247
|
+
return sanitizeKnownValue(value, platformSet);
|
|
2248
|
+
}
|
|
2249
|
+
function sanitizeTelemetryStackDimension(key, value) {
|
|
2250
|
+
if (!isTelemetryStackDimensionKey(key)) return void 0;
|
|
2251
|
+
if (telemetryProjectConfigKeySet.has(key)) return sanitizeProjectConfigValue(key, value);
|
|
2252
|
+
const selectionCategory = SELECTION_ONLY_CATEGORIES[key];
|
|
2253
|
+
if (selectionCategory) {
|
|
2254
|
+
const allowed$1 = new Set(getCategoryOptionIds(selectionCategory));
|
|
2255
|
+
const isArray = SELECTION_ONLY_ARRAY_KEYS.has(key);
|
|
2256
|
+
return sanitizeCanonicalList(value, allowed$1, isArray ? "array" : "comma-list", isArray ? MAX_ARRAY_ITEMS : 1);
|
|
2257
|
+
}
|
|
2258
|
+
if (key === "stackPartSelections") return sanitizeCanonicalList(value, stackPartSelectionSet, "array");
|
|
2259
|
+
if (key === "stackPartRoles") return sanitizeCanonicalList(value, new Set(StackPartRoleSchema.options), "array");
|
|
2260
|
+
if (key === "stackPartEcosystems") return sanitizeCanonicalList(value, stackPartEcosystemIds, "array");
|
|
2261
|
+
if (key === "multiEcosystem") return typeof value === "boolean" ? value : void 0;
|
|
2262
|
+
if (key === "rerun") return typeof value === "boolean" ? value : void 0;
|
|
2263
|
+
if (key === "bun_version") return typeof value === "string" && value.length <= MAX_VALUE_LENGTH && VERSION_PATTERN.test(value) ? value : void 0;
|
|
2264
|
+
const allowed = FIXED_DIMENSION_VALUES[key];
|
|
2265
|
+
return typeof value === "string" && allowed?.has(value) ? value : void 0;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
//#endregion
|
|
2269
|
+
export { CAPABILITY_RECEIPT_SCHEMA_VERSION as $, sanitizeTelemetryStackDimension as A, STARTER_TRACK_WORKSPACE_SHAPE_IDS as B, sanitizeTelemetryFailureStage as C, sanitizeTelemetryPlatform as D, sanitizeTelemetryNodeVersion as E, STARTER_TRACK_FILTER_URL_KEYS as F, getStarterTrackCatalog as G, createStarterTrackFilterSearchParams as H, STARTER_TRACK_IDS as I, CAPABILITY_FRESHNESS_IDS as J, parseStarterTrackFilters as K, STARTER_TRACK_PACKAGE_MANAGER_IDS as L, STARTER_TRACK_DATABASE_IDS as M, STARTER_TRACK_DEFINITIONS as N, sanitizeTelemetrySetupFailure as O, STARTER_TRACK_DEPLOYMENT_TARGET_IDS as P, CAPABILITY_RECEIPT_MAX_AGE_DAYS as Q, STARTER_TRACK_RUNTIME_IDS as R, sanitizeTelemetryFailureReason as S, sanitizeTelemetryMode as T, getProjectConfigEvidence as U, StarterTrackFiltersSchema as V, getStackSelectionEvidence as W, CAPABILITY_MATURITY_IDS as X, CAPABILITY_INVENTORY_SCHEMA_VERSION as Y, CAPABILITY_QUARANTINE as Z, sanitizeTelemetryAction as _, TELEMETRY_EXECUTION_RUNTIMES as a, CAPABILITY_EVIDENCE_LEVELS as at, sanitizeTelemetryErrorName as b, TELEMETRY_MODE_IDS as c, CapabilityEvidenceLevelSchema as ct, TELEMETRY_SELECTION_OUTCOMES as d, capabilityEvidenceLevel as dt, CapabilityEvidenceReceiptSchema as et, TELEMETRY_SELECTION_PROBLEMS as f, isTelemetryStackDimensionKey as g, isRegisteredTelemetryStackPartSelection as h, TELEMETRY_ERROR_NAMES as i, getCapabilityMaintenanceCosts as it, STARTER_TRACK_AUTH_IDS as j, sanitizeTelemetrySetupFailures as k, TELEMETRY_PLATFORMS as l, capCapabilityEvidenceLevel as lt, TELEMETRY_STACK_DIMENSION_KEYS as m, TELEMETRY_CI_PROVIDERS as n, GOLDEN_RUNTIME_RECIPES as nt, TELEMETRY_FAILURE_REASONS as o, CAPABILITY_EVIDENCE_LEVEL_IDS as ot, TELEMETRY_SETUP_FAILURE_IDS as p, recommendStarterTrack as q, TELEMETRY_DECISION_STAGES as r, getCapabilityInventory as rt, TELEMETRY_FAILURE_STAGES as s, CAPABILITY_EVIDENCE_SCHEMA_VERSION as st, TELEMETRY_ACTION_IDS as t, CapabilityRecipeResultSchema as tt, TELEMETRY_PROJECT_CONFIG_KEYS as u, capabilityEvidenceAtLeast as ut, sanitizeTelemetryCiProvider as v, sanitizeTelemetryMachineId as w, sanitizeTelemetryExecutionRuntime as x, sanitizeTelemetryCliVersion as y, STARTER_TRACK_SCHEMA_VERSION as z };
|
|
2270
|
+
//# sourceMappingURL=telemetry-C9qQ80l9.mjs.map
|