@better-fullstack/types 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{defaults-CFAJhN--.d.mts → defaults-BSM_JIBn.d.mts} +2 -2
- package/dist/defaults-BSM_JIBn.d.mts.map +1 -0
- package/dist/{defaults-D23vxGYl.mjs → defaults-DNmNiuxG.mjs} +1 -1
- package/dist/{defaults-D23vxGYl.mjs.map → defaults-DNmNiuxG.mjs.map} +1 -1
- package/dist/defaults.d.mts +3 -3
- package/dist/defaults.mjs +1 -1
- package/dist/index.d.mts +6 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -4
- package/dist/index.mjs.map +1 -1
- package/dist/json-schema.d.mts +570 -42
- package/dist/json-schema.d.mts.map +1 -1
- package/dist/json-schema.mjs +1 -1
- package/dist/{schemas-Bs_n2xLj.d.mts → schemas-B5EgRfVL.d.mts} +556 -46
- package/dist/schemas-B5EgRfVL.d.mts.map +1 -0
- package/dist/{schemas-BHCZL3uR.mjs → schemas-DnEGgQzA.mjs} +74 -7
- package/dist/schemas-DnEGgQzA.mjs.map +1 -0
- package/dist/schemas.d.mts +2 -2
- package/dist/schemas.mjs +2 -2
- package/dist/stack-graph-C2tzuzRe.mjs +585 -0
- package/dist/stack-graph-C2tzuzRe.mjs.map +1 -0
- package/dist/stack-graph-_551XvrL.d.mts +68 -0
- package/dist/stack-graph-_551XvrL.d.mts.map +1 -0
- package/dist/stack-graph.d.mts +4 -0
- package/dist/stack-graph.mjs +4 -0
- package/dist/{stack-translation-_uOX-Io0.mjs → stack-translation-BcsaMRuV.mjs} +260 -22
- package/dist/stack-translation-BcsaMRuV.mjs.map +1 -0
- package/dist/{stack-translation-xj8JvbAI.d.mts → stack-translation-CqslePvp.d.mts} +14 -7
- package/dist/stack-translation-CqslePvp.d.mts.map +1 -0
- package/dist/stack-translation.d.mts +5 -5
- package/dist/stack-translation.mjs +4 -3
- package/dist/{types-DwFVhx5c.d.mts → types-DorAmN4i.d.mts} +7 -3
- package/dist/{types-DwFVhx5c.d.mts.map → types-DorAmN4i.d.mts.map} +1 -1
- package/dist/types.d.mts +3 -3
- package/package.json +5 -1
- package/dist/defaults-CFAJhN--.d.mts.map +0 -1
- package/dist/schemas-BHCZL3uR.mjs.map +0 -1
- package/dist/schemas-Bs_n2xLj.d.mts.map +0 -1
- package/dist/stack-translation-_uOX-Io0.mjs.map +0 -1
- package/dist/stack-translation-xj8JvbAI.d.mts.map +0 -1
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import { $ as ELIXIR_VALIDATION_VALUES, $n as RUST_API_VALUES, An as PYTHON_API_VALUES, B as ELIXIR_API_VALUES, Et as FRONTEND_VALUES, Fn as PYTHON_TASK_QUEUE_VALUES, H as ELIXIR_CACHING_VALUES, In as PYTHON_VALIDATION_VALUES, It as GO_ORM_VALUES, J as ELIXIR_OBSERVABILITY_VALUES, K as ELIXIR_JOBS_VALUES, Ln as PYTHON_WEB_FRAMEWORK_VALUES, Lt as GO_WEB_FRAMEWORK_VALUES, Mn as PYTHON_GRAPHQL_VALUES, Mt as GO_API_VALUES, N as DATABASE_VALUES, Nn as PYTHON_ORM_VALUES, Nt as GO_AUTH_VALUES, Q as ELIXIR_TESTING_VALUES, Qt as JAVA_WEB_FRAMEWORK_VALUES, Tn as ORM_VALUES, U as ELIXIR_DEPLOY_VALUES, V as ELIXIR_AUTH_VALUES, Vr as StackPartRoleSchema, W as ELIXIR_EMAIL_VALUES, Xt as JAVA_ORM_VALUES, Y as ELIXIR_ORM_VALUES, Z as ELIXIR_REALTIME_VALUES, c as API_VALUES, cr as RUST_WEB_FRAMEWORK_VALUES, er as RUST_AUTH_VALUES, et as ELIXIR_WEB_FRAMEWORK_VALUES, ir as RUST_FRONTEND_VALUES, jn as PYTHON_AUTH_VALUES, qt as JAVA_AUTH_VALUES, sr as RUST_ORM_VALUES, tr as RUST_CACHING_VALUES, u as AUTH_VALUES, v as BACKEND_VALUES } from "./schemas-DnEGgQzA.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/stack-graph.ts
|
|
4
|
+
const PRIMARY_ROLES = new Set([
|
|
5
|
+
"frontend",
|
|
6
|
+
"backend",
|
|
7
|
+
"mobile",
|
|
8
|
+
"database"
|
|
9
|
+
]);
|
|
10
|
+
const NATIVE_FRONTENDS = new Set([
|
|
11
|
+
"native-bare",
|
|
12
|
+
"native-uniwind",
|
|
13
|
+
"native-unistyles"
|
|
14
|
+
]);
|
|
15
|
+
const WEB_FRONTENDS = FRONTEND_VALUES.filter((value) => value !== "none" && !NATIVE_FRONTENDS.has(value));
|
|
16
|
+
const DJANGO_API_TOOLS = new Set(["django-rest-framework", "django-ninja"]);
|
|
17
|
+
const JAVA_SPRING_CAPABILITY_TOOLS = new Set(["spring-data-jpa", "spring-security"]);
|
|
18
|
+
const TYPESCRIPT_TRPC_INCOMPATIBLE_FRONTENDS = new Set([
|
|
19
|
+
"nuxt",
|
|
20
|
+
"svelte",
|
|
21
|
+
"solid",
|
|
22
|
+
"solid-start"
|
|
23
|
+
]);
|
|
24
|
+
const BETTER_AUTH_UNSUPPORTED_ORM_TOOLS = new Set([
|
|
25
|
+
"typeorm",
|
|
26
|
+
"mikroorm",
|
|
27
|
+
"sequelize"
|
|
28
|
+
]);
|
|
29
|
+
const ELIXIR_ECTO_REQUIRED_TOOLS = new Set(["absinthe", "phx-gen-auth"]);
|
|
30
|
+
const ELIXIR_ECTO_SQL_REQUIRED_TOOLS = new Set(["oban"]);
|
|
31
|
+
const ELIXIR_UNSUPPORTED_GRAPH_TOOLS = new Set([
|
|
32
|
+
"ecto",
|
|
33
|
+
"ueberauth",
|
|
34
|
+
"guardian",
|
|
35
|
+
"nimble-options",
|
|
36
|
+
"nebulex",
|
|
37
|
+
"opentelemetry",
|
|
38
|
+
"prom_ex",
|
|
39
|
+
"mox",
|
|
40
|
+
"bypass",
|
|
41
|
+
"wallaby",
|
|
42
|
+
"fly",
|
|
43
|
+
"gigalixir"
|
|
44
|
+
]);
|
|
45
|
+
const ROLE_TARGET_PATHS = {
|
|
46
|
+
frontend: "apps/web",
|
|
47
|
+
backend: "apps/server",
|
|
48
|
+
mobile: "apps/native",
|
|
49
|
+
database: "packages/database"
|
|
50
|
+
};
|
|
51
|
+
function defineTools(values, role, ecosystem, legacyCategory) {
|
|
52
|
+
return values.filter((toolId) => toolId !== "none").map((toolId) => ({
|
|
53
|
+
toolId,
|
|
54
|
+
roles: [role],
|
|
55
|
+
ecosystems: [ecosystem],
|
|
56
|
+
legacyCategory
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
const STACK_TOOL_DEFINITIONS = [
|
|
60
|
+
...defineTools(WEB_FRONTENDS, "frontend", "typescript", "frontend"),
|
|
61
|
+
...defineTools([...NATIVE_FRONTENDS], "mobile", "react-native", "frontend"),
|
|
62
|
+
...defineTools(BACKEND_VALUES.filter((value) => value !== "convex"), "backend", "typescript", "backend"),
|
|
63
|
+
...defineTools(DATABASE_VALUES, "database", "universal", "database"),
|
|
64
|
+
...defineTools(DATABASE_VALUES, "database", "typescript", "database"),
|
|
65
|
+
...defineTools(ORM_VALUES, "orm", "typescript", "orm"),
|
|
66
|
+
...defineTools(API_VALUES, "api", "typescript", "api"),
|
|
67
|
+
...defineTools(AUTH_VALUES, "auth", "typescript", "auth"),
|
|
68
|
+
...defineTools(AUTH_VALUES, "auth", "react-native", "auth"),
|
|
69
|
+
...defineTools(RUST_WEB_FRAMEWORK_VALUES, "backend", "rust", "rustWebFramework"),
|
|
70
|
+
...defineTools(RUST_FRONTEND_VALUES, "frontend", "rust", "rustFrontend"),
|
|
71
|
+
...defineTools(RUST_ORM_VALUES, "orm", "rust", "rustOrm"),
|
|
72
|
+
...defineTools(RUST_API_VALUES, "api", "rust", "rustApi"),
|
|
73
|
+
...defineTools(RUST_AUTH_VALUES, "auth", "rust", "rustAuth"),
|
|
74
|
+
...defineTools(RUST_CACHING_VALUES, "caching", "rust", "rustCaching"),
|
|
75
|
+
...defineTools(PYTHON_WEB_FRAMEWORK_VALUES, "backend", "python", "pythonWebFramework"),
|
|
76
|
+
...defineTools(PYTHON_ORM_VALUES, "orm", "python", "pythonOrm"),
|
|
77
|
+
...defineTools(PYTHON_VALIDATION_VALUES, "validation", "python", "pythonValidation"),
|
|
78
|
+
...defineTools(PYTHON_API_VALUES, "api", "python", "pythonApi"),
|
|
79
|
+
...defineTools(PYTHON_AUTH_VALUES, "auth", "python", "pythonAuth"),
|
|
80
|
+
...defineTools(PYTHON_TASK_QUEUE_VALUES, "jobQueue", "python", "pythonTaskQueue"),
|
|
81
|
+
...defineTools(PYTHON_GRAPHQL_VALUES, "api", "python", "pythonGraphql"),
|
|
82
|
+
...defineTools(GO_WEB_FRAMEWORK_VALUES, "backend", "go", "goWebFramework"),
|
|
83
|
+
...defineTools(GO_ORM_VALUES, "orm", "go", "goOrm"),
|
|
84
|
+
...defineTools(GO_API_VALUES, "api", "go", "goApi"),
|
|
85
|
+
...defineTools(GO_AUTH_VALUES, "auth", "go", "goAuth"),
|
|
86
|
+
...defineTools(JAVA_WEB_FRAMEWORK_VALUES, "backend", "java", "javaWebFramework"),
|
|
87
|
+
...defineTools(JAVA_ORM_VALUES, "orm", "java", "javaOrm"),
|
|
88
|
+
...defineTools(JAVA_AUTH_VALUES, "auth", "java", "javaAuth"),
|
|
89
|
+
...defineTools(ELIXIR_WEB_FRAMEWORK_VALUES, "backend", "elixir", "elixirWebFramework"),
|
|
90
|
+
...defineTools(ELIXIR_ORM_VALUES, "orm", "elixir", "elixirOrm"),
|
|
91
|
+
...defineTools(ELIXIR_AUTH_VALUES, "auth", "elixir", "elixirAuth"),
|
|
92
|
+
...defineTools(ELIXIR_API_VALUES, "api", "elixir", "elixirApi"),
|
|
93
|
+
...defineTools(ELIXIR_REALTIME_VALUES, "api", "elixir", "elixirRealtime"),
|
|
94
|
+
...defineTools(ELIXIR_JOBS_VALUES, "jobQueue", "elixir", "elixirJobs"),
|
|
95
|
+
...defineTools(ELIXIR_VALIDATION_VALUES, "validation", "elixir", "elixirValidation"),
|
|
96
|
+
...defineTools(ELIXIR_EMAIL_VALUES, "email", "elixir", "elixirEmail"),
|
|
97
|
+
...defineTools(ELIXIR_CACHING_VALUES, "caching", "elixir", "elixirCaching"),
|
|
98
|
+
...defineTools(ELIXIR_OBSERVABILITY_VALUES, "observability", "elixir", "elixirObservability"),
|
|
99
|
+
...defineTools(ELIXIR_TESTING_VALUES, "testing", "elixir", "elixirTesting"),
|
|
100
|
+
...defineTools(ELIXIR_DEPLOY_VALUES, "deploy", "elixir", "elixirDeploy"),
|
|
101
|
+
{
|
|
102
|
+
toolId: "convex",
|
|
103
|
+
roles: ["backend"],
|
|
104
|
+
ecosystems: ["typescript"],
|
|
105
|
+
legacyCategory: "backend",
|
|
106
|
+
provides: [{
|
|
107
|
+
role: "database",
|
|
108
|
+
toolId: "convex",
|
|
109
|
+
ecosystem: "typescript",
|
|
110
|
+
overrideable: false
|
|
111
|
+
}, {
|
|
112
|
+
role: "api",
|
|
113
|
+
toolId: "convex",
|
|
114
|
+
ecosystem: "typescript",
|
|
115
|
+
overrideable: false
|
|
116
|
+
}]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
toolId: "convex",
|
|
120
|
+
roles: ["database", "api"],
|
|
121
|
+
ecosystems: ["typescript"],
|
|
122
|
+
selectable: false
|
|
123
|
+
}
|
|
124
|
+
];
|
|
125
|
+
function sanitizePartId(input) {
|
|
126
|
+
return input.trim().toLowerCase().replace(/[^a-z0-9:._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
127
|
+
}
|
|
128
|
+
function getRoleTargetPath(role) {
|
|
129
|
+
return PRIMARY_ROLES.has(role) ? ROLE_TARGET_PATHS[role] : void 0;
|
|
130
|
+
}
|
|
131
|
+
function createStackPart(input) {
|
|
132
|
+
const roleKey = input.ownerPartId ? `${input.ownerPartId}.${input.role}` : input.role;
|
|
133
|
+
return {
|
|
134
|
+
id: input.id ?? sanitizePartId(`${roleKey}:${input.ecosystem}:${input.toolId}`),
|
|
135
|
+
role: input.role,
|
|
136
|
+
toolId: input.toolId,
|
|
137
|
+
ecosystem: input.ecosystem,
|
|
138
|
+
ownerPartId: input.ownerPartId,
|
|
139
|
+
source: input.source ?? "selected",
|
|
140
|
+
providedByPartId: input.providedByPartId,
|
|
141
|
+
targetPath: input.targetPath ?? getRoleTargetPath(input.role),
|
|
142
|
+
settings: input.settings
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function isNoneTool(toolId) {
|
|
146
|
+
return !toolId || toolId === "none";
|
|
147
|
+
}
|
|
148
|
+
function allowsCrossEcosystemOwner(part, context) {
|
|
149
|
+
if (part.ecosystem === "universal") return true;
|
|
150
|
+
if (part.role === "auth" && context.ownerRole === "mobile" && part.ecosystem === "react-native") return true;
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
function createStackGraphIssue(input) {
|
|
154
|
+
return {
|
|
155
|
+
code: input.code,
|
|
156
|
+
message: input.message,
|
|
157
|
+
partId: input.partId,
|
|
158
|
+
role: input.role,
|
|
159
|
+
toolId: input.toolId
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function getStackPartCompatibilityIssue(part, context) {
|
|
163
|
+
if (context.ownerEcosystem && part.ecosystem !== context.ownerEcosystem && !allowsCrossEcosystemOwner(part, context)) return createStackGraphIssue({
|
|
164
|
+
code: "INCOMPATIBLE_OWNER_ECOSYSTEM",
|
|
165
|
+
partId: part.id,
|
|
166
|
+
role: part.role,
|
|
167
|
+
toolId: part.toolId,
|
|
168
|
+
message: `'${part.toolId}' uses the ${part.ecosystem} adapter but its owner uses ${context.ownerEcosystem}.`
|
|
169
|
+
});
|
|
170
|
+
if (part.ecosystem === "python" && part.role === "api" && DJANGO_API_TOOLS.has(part.toolId)) {
|
|
171
|
+
if (context.ownerToolId !== "django") return createStackGraphIssue({
|
|
172
|
+
code: "INCOMPATIBLE_OWNER_TOOL",
|
|
173
|
+
partId: part.id,
|
|
174
|
+
role: part.role,
|
|
175
|
+
toolId: part.toolId,
|
|
176
|
+
message: `'${part.toolId}' can only be selected for a Django backend.`
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (part.ecosystem === "java" && (part.role === "orm" || part.role === "auth") && JAVA_SPRING_CAPABILITY_TOOLS.has(part.toolId)) {
|
|
180
|
+
if (context.ownerToolId !== "spring-boot") return createStackGraphIssue({
|
|
181
|
+
code: "INCOMPATIBLE_OWNER_TOOL",
|
|
182
|
+
partId: part.id,
|
|
183
|
+
role: part.role,
|
|
184
|
+
toolId: part.toolId,
|
|
185
|
+
message: `'${part.toolId}' can only be selected for a Spring Boot backend.`
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (part.ecosystem === "typescript" && part.role === "api" && part.toolId === "trpc") {
|
|
189
|
+
const frontendTool = context.primaryToolIdsByRole?.frontend;
|
|
190
|
+
if (frontendTool && TYPESCRIPT_TRPC_INCOMPATIBLE_FRONTENDS.has(frontendTool)) return createStackGraphIssue({
|
|
191
|
+
code: "INCOMPATIBLE_GRAPH_SELECTION",
|
|
192
|
+
partId: part.id,
|
|
193
|
+
role: part.role,
|
|
194
|
+
toolId: part.toolId,
|
|
195
|
+
message: `'trpc' cannot be selected with the '${frontendTool}' frontend.`
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (part.ecosystem === "typescript" && part.role === "auth" && part.toolId === "better-auth") {
|
|
199
|
+
if (context.primaryToolIdsByRole?.database === "redis") return createStackGraphIssue({
|
|
200
|
+
code: "INCOMPATIBLE_GRAPH_SELECTION",
|
|
201
|
+
partId: part.id,
|
|
202
|
+
role: part.role,
|
|
203
|
+
toolId: part.toolId,
|
|
204
|
+
message: "'better-auth' cannot use Redis as the primary database."
|
|
205
|
+
});
|
|
206
|
+
const ormTool = context.siblingToolIdsByRole?.orm;
|
|
207
|
+
if (ormTool && BETTER_AUTH_UNSUPPORTED_ORM_TOOLS.has(ormTool)) return createStackGraphIssue({
|
|
208
|
+
code: "INCOMPATIBLE_GRAPH_SELECTION",
|
|
209
|
+
partId: part.id,
|
|
210
|
+
role: part.role,
|
|
211
|
+
toolId: part.toolId,
|
|
212
|
+
message: `'better-auth' is not compatible with the '${ormTool}' ORM selection.`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (part.ecosystem === "elixir" && ELIXIR_ECTO_REQUIRED_TOOLS.has(part.toolId) && (part.role === "api" || part.role === "auth")) {
|
|
216
|
+
const ormTool = context.siblingToolIdsByRole?.orm;
|
|
217
|
+
if (isNoneTool(ormTool)) return createStackGraphIssue({
|
|
218
|
+
code: "INCOMPATIBLE_GRAPH_SELECTION",
|
|
219
|
+
partId: part.id,
|
|
220
|
+
role: part.role,
|
|
221
|
+
toolId: part.toolId,
|
|
222
|
+
message: `'${part.toolId}' requires an Ecto database layer.`
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (part.ecosystem === "elixir" && ELIXIR_ECTO_SQL_REQUIRED_TOOLS.has(part.toolId)) {
|
|
226
|
+
if (context.siblingToolIdsByRole?.orm !== "ecto-sql") return createStackGraphIssue({
|
|
227
|
+
code: "INCOMPATIBLE_GRAPH_SELECTION",
|
|
228
|
+
partId: part.id,
|
|
229
|
+
role: part.role,
|
|
230
|
+
toolId: part.toolId,
|
|
231
|
+
message: `'${part.toolId}' requires the Ecto SQL database layer.`
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (part.ecosystem === "elixir" && part.toolId === "live-view-streams") {
|
|
235
|
+
if (context.ownerToolId !== "phoenix-live-view") return createStackGraphIssue({
|
|
236
|
+
code: "INCOMPATIBLE_OWNER_TOOL",
|
|
237
|
+
partId: part.id,
|
|
238
|
+
role: part.role,
|
|
239
|
+
toolId: part.toolId,
|
|
240
|
+
message: "'live-view-streams' can only be selected for a Phoenix LiveView backend."
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (part.ecosystem === "elixir" && ELIXIR_UNSUPPORTED_GRAPH_TOOLS.has(part.toolId)) return createStackGraphIssue({
|
|
244
|
+
code: "UNSUPPORTED_GRAPH_TOOL",
|
|
245
|
+
partId: part.id,
|
|
246
|
+
role: part.role,
|
|
247
|
+
toolId: part.toolId,
|
|
248
|
+
message: `'${part.toolId}' is not generated by the current Elixir scaffold.`
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function getStackToolDefinitions(context) {
|
|
252
|
+
if (!context) return [...STACK_TOOL_DEFINITIONS];
|
|
253
|
+
return STACK_TOOL_DEFINITIONS.filter((definition) => {
|
|
254
|
+
if (!definition.roles.includes(context.role)) return false;
|
|
255
|
+
if (context.ecosystem && !definition.ecosystems.includes(context.ecosystem)) return false;
|
|
256
|
+
if (definition.selectable === false) return false;
|
|
257
|
+
const ecosystem = context.ecosystem ?? definition.ecosystems[0];
|
|
258
|
+
if (getStackPartCompatibilityIssue({
|
|
259
|
+
id: `${context.ownerRole ?? "root"}:${context.role}:${definition.toolId}`,
|
|
260
|
+
role: context.role,
|
|
261
|
+
toolId: definition.toolId,
|
|
262
|
+
ecosystem
|
|
263
|
+
}, context)) return false;
|
|
264
|
+
return true;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function getStackPartOptions(context) {
|
|
268
|
+
return [...new Set(getStackToolDefinitions(context).map((definition) => definition.toolId))];
|
|
269
|
+
}
|
|
270
|
+
function findDefinition(part) {
|
|
271
|
+
return STACK_TOOL_DEFINITIONS.find((definition) => definition.toolId === part.toolId && definition.roles.includes(part.role) && definition.ecosystems.includes(part.ecosystem));
|
|
272
|
+
}
|
|
273
|
+
function parseRolePath(rolePath) {
|
|
274
|
+
const segments = rolePath.split(".");
|
|
275
|
+
const rawRole = segments.length === 1 ? segments[0] : segments[segments.length - 1];
|
|
276
|
+
const rawOwnerRole = segments.length > 1 ? segments[0] : void 0;
|
|
277
|
+
const role = StackPartRoleSchema.parse(rawRole);
|
|
278
|
+
const ownerRole = rawOwnerRole ? StackPartRoleSchema.parse(rawOwnerRole) : void 0;
|
|
279
|
+
if (ownerRole && !PRIMARY_ROLES.has(ownerRole)) throw new Error(`Stack part owner role '${ownerRole}' must be a primary role.`);
|
|
280
|
+
return {
|
|
281
|
+
role,
|
|
282
|
+
ownerRole
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function parseStackPartSpecs(specs, source = "selected") {
|
|
286
|
+
const unresolved = specs.map((spec) => {
|
|
287
|
+
const [rolePath, ecosystem, toolId, customId] = spec.split(":");
|
|
288
|
+
if (!rolePath || !ecosystem || !toolId || spec.split(":").length > 4) throw new Error(`Invalid --part '${spec}'. Use role:ecosystem:tool or owner.role:ecosystem:tool.`);
|
|
289
|
+
const { role, ownerRole } = parseRolePath(rolePath);
|
|
290
|
+
return {
|
|
291
|
+
role,
|
|
292
|
+
ownerRole,
|
|
293
|
+
ecosystem,
|
|
294
|
+
toolId,
|
|
295
|
+
customId
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
const primaryParts = unresolved.filter((part) => !part.ownerRole).map((part) => createStackPart({
|
|
299
|
+
role: part.role,
|
|
300
|
+
ecosystem: part.ecosystem,
|
|
301
|
+
toolId: part.toolId,
|
|
302
|
+
source,
|
|
303
|
+
id: part.customId
|
|
304
|
+
}));
|
|
305
|
+
const primaryByRole = new Map(primaryParts.map((part) => [part.role, part]));
|
|
306
|
+
const scopedParts = unresolved.filter((part) => part.ownerRole !== void 0).map((part) => createStackPart({
|
|
307
|
+
role: part.role,
|
|
308
|
+
ecosystem: part.ecosystem,
|
|
309
|
+
toolId: part.toolId,
|
|
310
|
+
ownerPartId: primaryByRole.get(part.ownerRole)?.id,
|
|
311
|
+
source,
|
|
312
|
+
id: part.customId
|
|
313
|
+
}));
|
|
314
|
+
return materializeProvidedStackParts([...primaryParts, ...scopedParts]);
|
|
315
|
+
}
|
|
316
|
+
function formatStackPartSpec(part, parts = []) {
|
|
317
|
+
const owner = part.ownerPartId ? parts.find((candidate) => candidate.id === part.ownerPartId) : void 0;
|
|
318
|
+
return `${owner ? `${owner.role}.${part.role}` : part.role}:${part.ecosystem}:${part.toolId}`;
|
|
319
|
+
}
|
|
320
|
+
function materializeProvidedStackParts(parts) {
|
|
321
|
+
const next = [...parts];
|
|
322
|
+
for (const part of parts) {
|
|
323
|
+
const definition = findDefinition(part);
|
|
324
|
+
for (const provided of definition?.provides ?? []) next.push(createStackPart({
|
|
325
|
+
role: provided.role,
|
|
326
|
+
toolId: provided.toolId,
|
|
327
|
+
ecosystem: provided.ecosystem ?? part.ecosystem,
|
|
328
|
+
ownerPartId: part.id,
|
|
329
|
+
source: "provided",
|
|
330
|
+
providedByPartId: part.id
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
return next;
|
|
334
|
+
}
|
|
335
|
+
function getPrimaryPart(parts, role) {
|
|
336
|
+
return parts.find((part) => part.role === role && !part.ownerPartId);
|
|
337
|
+
}
|
|
338
|
+
function addLegacyPart(parts, role, ecosystem, toolId, source, ownerPartId) {
|
|
339
|
+
if (!toolId || toolId === "none") return void 0;
|
|
340
|
+
const part = createStackPart({
|
|
341
|
+
role,
|
|
342
|
+
ecosystem,
|
|
343
|
+
toolId,
|
|
344
|
+
source,
|
|
345
|
+
ownerPartId
|
|
346
|
+
});
|
|
347
|
+
parts.push(part);
|
|
348
|
+
return part;
|
|
349
|
+
}
|
|
350
|
+
function legacyProjectConfigToStackParts(config, source = "legacy") {
|
|
351
|
+
const parts = [];
|
|
352
|
+
const frontends = config.frontend ?? [];
|
|
353
|
+
const webFrontends = frontends.filter((frontend) => frontend !== "none" && !NATIVE_FRONTENDS.has(frontend));
|
|
354
|
+
const nativeFrontends = frontends.filter((frontend) => NATIVE_FRONTENDS.has(frontend));
|
|
355
|
+
if (webFrontends.length > 1) throw new Error("Multiple primary web frontends are not supported in the stack graph.");
|
|
356
|
+
if (nativeFrontends.length > 1) throw new Error("Multiple primary mobile frontends are not supported in the stack graph.");
|
|
357
|
+
const webFrontend = webFrontends[0];
|
|
358
|
+
const nativeFrontend = nativeFrontends[0];
|
|
359
|
+
const frontendPart = addLegacyPart(parts, "frontend", "typescript", webFrontend, source);
|
|
360
|
+
addLegacyPart(parts, "mobile", "react-native", nativeFrontend, source);
|
|
361
|
+
let backendPart;
|
|
362
|
+
if ((config.ecosystem === void 0 || config.ecosystem === "typescript") && config.backend && config.backend !== "none") backendPart = addLegacyPart(parts, "backend", "typescript", config.backend, source);
|
|
363
|
+
if (config.ecosystem === "rust" && config.rustWebFramework && config.rustWebFramework !== "none") backendPart = addLegacyPart(parts, "backend", "rust", config.rustWebFramework, source);
|
|
364
|
+
if (config.ecosystem === "rust" && config.rustFrontend && config.rustFrontend !== "none") addLegacyPart(parts, "frontend", "rust", config.rustFrontend, source);
|
|
365
|
+
if (config.ecosystem === "python" && config.pythonWebFramework && config.pythonWebFramework !== "none") backendPart = addLegacyPart(parts, "backend", "python", config.pythonWebFramework, source);
|
|
366
|
+
if (config.ecosystem === "go" && config.goWebFramework && config.goWebFramework !== "none") backendPart = addLegacyPart(parts, "backend", "go", config.goWebFramework, source);
|
|
367
|
+
if (config.ecosystem === "java" && config.javaWebFramework && config.javaWebFramework !== "none") backendPart = addLegacyPart(parts, "backend", "java", config.javaWebFramework, source);
|
|
368
|
+
if (config.ecosystem === "elixir" && config.elixirWebFramework && config.elixirWebFramework !== "none") backendPart = addLegacyPart(parts, "backend", "elixir", config.elixirWebFramework, source);
|
|
369
|
+
const databasePart = addLegacyPart(parts, "database", "universal", config.database, source);
|
|
370
|
+
const capabilityOwner = backendPart?.id ?? frontendPart?.id ?? databasePart?.id;
|
|
371
|
+
if (config.ecosystem === void 0 || config.ecosystem === "typescript" || config.ecosystem === "react-native") {
|
|
372
|
+
addLegacyPart(parts, "orm", "typescript", config.orm, source, capabilityOwner);
|
|
373
|
+
addLegacyPart(parts, "api", "typescript", config.api, source, capabilityOwner);
|
|
374
|
+
addLegacyPart(parts, "auth", config.ecosystem ?? "typescript", config.auth, source, capabilityOwner);
|
|
375
|
+
}
|
|
376
|
+
if (config.ecosystem === "rust") {
|
|
377
|
+
addLegacyPart(parts, "orm", "rust", config.rustOrm, source, backendPart?.id);
|
|
378
|
+
addLegacyPart(parts, "api", "rust", config.rustApi, source, backendPart?.id);
|
|
379
|
+
addLegacyPart(parts, "auth", "rust", config.rustAuth, source, backendPart?.id);
|
|
380
|
+
}
|
|
381
|
+
if (config.ecosystem === "python") {
|
|
382
|
+
addLegacyPart(parts, "orm", "python", config.pythonOrm, source, backendPart?.id);
|
|
383
|
+
addLegacyPart(parts, "api", "python", config.pythonApi, source, backendPart?.id);
|
|
384
|
+
addLegacyPart(parts, "auth", "python", config.pythonAuth, source, backendPart?.id);
|
|
385
|
+
}
|
|
386
|
+
if (config.ecosystem === "go") {
|
|
387
|
+
addLegacyPart(parts, "orm", "go", config.goOrm, source, backendPart?.id);
|
|
388
|
+
addLegacyPart(parts, "api", "go", config.goApi, source, backendPart?.id);
|
|
389
|
+
addLegacyPart(parts, "auth", "go", config.goAuth, source, backendPart?.id);
|
|
390
|
+
}
|
|
391
|
+
if (config.ecosystem === "java") {
|
|
392
|
+
addLegacyPart(parts, "orm", "java", config.javaOrm, source, backendPart?.id);
|
|
393
|
+
addLegacyPart(parts, "auth", "java", config.javaAuth, source, backendPart?.id);
|
|
394
|
+
}
|
|
395
|
+
if (config.ecosystem === "elixir") {
|
|
396
|
+
addLegacyPart(parts, "orm", "elixir", config.elixirOrm, source, backendPart?.id);
|
|
397
|
+
addLegacyPart(parts, "api", "elixir", config.elixirApi, source, backendPart?.id);
|
|
398
|
+
addLegacyPart(parts, "auth", "elixir", config.elixirAuth, source, backendPart?.id);
|
|
399
|
+
}
|
|
400
|
+
return materializeProvidedStackParts(parts);
|
|
401
|
+
}
|
|
402
|
+
function ecosystemForLegacy(parts) {
|
|
403
|
+
const frontend = getPrimaryPart(parts, "frontend");
|
|
404
|
+
if (frontend?.ecosystem && frontend.ecosystem !== "universal") return frontend.ecosystem;
|
|
405
|
+
if (getPrimaryPart(parts, "mobile")) return "react-native";
|
|
406
|
+
const backend = getPrimaryPart(parts, "backend");
|
|
407
|
+
if (backend?.ecosystem && backend.ecosystem !== "universal") return backend.ecosystem;
|
|
408
|
+
return "typescript";
|
|
409
|
+
}
|
|
410
|
+
function stackPartsToLegacyProjectConfigPartial(parts) {
|
|
411
|
+
const config = {
|
|
412
|
+
stackParts: [...parts],
|
|
413
|
+
ecosystem: ecosystemForLegacy(parts),
|
|
414
|
+
frontend: [],
|
|
415
|
+
backend: "none",
|
|
416
|
+
database: "none",
|
|
417
|
+
orm: "none",
|
|
418
|
+
api: "none",
|
|
419
|
+
auth: "none"
|
|
420
|
+
};
|
|
421
|
+
for (const part of parts) {
|
|
422
|
+
if (part.source === "provided") continue;
|
|
423
|
+
if (!part.ownerPartId) {
|
|
424
|
+
if (part.role === "frontend" && part.ecosystem === "typescript") config.frontend = [...config.frontend ?? [], part.toolId];
|
|
425
|
+
else if (part.role === "frontend" && part.ecosystem === "rust") config.rustFrontend = part.toolId;
|
|
426
|
+
else if (part.role === "mobile") config.frontend = [...config.frontend ?? [], part.toolId];
|
|
427
|
+
else if (part.role === "backend") {
|
|
428
|
+
if (part.ecosystem === "typescript") config.backend = part.toolId;
|
|
429
|
+
if (part.ecosystem === "rust") config.rustWebFramework = part.toolId;
|
|
430
|
+
if (part.ecosystem === "python") config.pythonWebFramework = part.toolId;
|
|
431
|
+
if (part.ecosystem === "go") config.goWebFramework = part.toolId;
|
|
432
|
+
if (part.ecosystem === "java") config.javaWebFramework = part.toolId;
|
|
433
|
+
if (part.ecosystem === "elixir") config.elixirWebFramework = part.toolId;
|
|
434
|
+
} else if (part.role === "database") config.database = part.toolId;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const definition = findDefinition(part);
|
|
438
|
+
if (definition?.legacyCategory) {
|
|
439
|
+
config[definition.legacyCategory] = part.toolId;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (part.role === "orm") {
|
|
443
|
+
if (part.ecosystem === "typescript") config.orm = part.toolId;
|
|
444
|
+
if (part.ecosystem === "rust") config.rustOrm = part.toolId;
|
|
445
|
+
if (part.ecosystem === "python") config.pythonOrm = part.toolId;
|
|
446
|
+
if (part.ecosystem === "go") config.goOrm = part.toolId;
|
|
447
|
+
if (part.ecosystem === "java") config.javaOrm = part.toolId;
|
|
448
|
+
if (part.ecosystem === "elixir") config.elixirOrm = part.toolId;
|
|
449
|
+
}
|
|
450
|
+
if (part.role === "api") {
|
|
451
|
+
if (part.ecosystem === "typescript") config.api = part.toolId;
|
|
452
|
+
if (part.ecosystem === "rust") config.rustApi = part.toolId;
|
|
453
|
+
if (part.ecosystem === "python") config.pythonApi = part.toolId;
|
|
454
|
+
if (part.ecosystem === "go") config.goApi = part.toolId;
|
|
455
|
+
if (part.ecosystem === "elixir") config.elixirApi = part.toolId;
|
|
456
|
+
}
|
|
457
|
+
if (part.role === "auth") {
|
|
458
|
+
if (part.ecosystem === "typescript" || part.ecosystem === "react-native") config.auth = part.toolId;
|
|
459
|
+
if (part.ecosystem === "rust") config.rustAuth = part.toolId;
|
|
460
|
+
if (part.ecosystem === "python") config.pythonAuth = part.toolId;
|
|
461
|
+
if (part.ecosystem === "go") config.goAuth = part.toolId;
|
|
462
|
+
if (part.ecosystem === "java") config.javaAuth = part.toolId;
|
|
463
|
+
if (part.ecosystem === "elixir") config.elixirAuth = part.toolId;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return config;
|
|
467
|
+
}
|
|
468
|
+
function compareLegacyConfigToStackParts(config, stackParts) {
|
|
469
|
+
const derived = stackPartsToLegacyProjectConfigPartial(stackParts);
|
|
470
|
+
const diagnostics = [];
|
|
471
|
+
for (const key of [
|
|
472
|
+
"ecosystem",
|
|
473
|
+
"frontend",
|
|
474
|
+
"backend",
|
|
475
|
+
"database",
|
|
476
|
+
"orm",
|
|
477
|
+
"api",
|
|
478
|
+
"auth",
|
|
479
|
+
"rustWebFramework",
|
|
480
|
+
"pythonWebFramework",
|
|
481
|
+
"goWebFramework",
|
|
482
|
+
"javaWebFramework",
|
|
483
|
+
"elixirWebFramework"
|
|
484
|
+
]) {
|
|
485
|
+
const current = config[key];
|
|
486
|
+
const next = derived[key];
|
|
487
|
+
if (current === void 0 || next === void 0) continue;
|
|
488
|
+
if (JSON.stringify(current) !== JSON.stringify(next)) diagnostics.push({
|
|
489
|
+
code: "LEGACY_CONFIG_MISMATCH",
|
|
490
|
+
path: key,
|
|
491
|
+
message: `Legacy field '${key}' differs from stackParts and will be derived from the graph.`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return diagnostics;
|
|
495
|
+
}
|
|
496
|
+
function validateStackParts(parts) {
|
|
497
|
+
const issues = [];
|
|
498
|
+
const partsById = new Map(parts.map((part) => [part.id, part]));
|
|
499
|
+
const primaryToolIdsByRole = {};
|
|
500
|
+
for (const role of PRIMARY_ROLES) {
|
|
501
|
+
const primaryPart = getPrimaryPart(parts, role);
|
|
502
|
+
if (primaryPart) primaryToolIdsByRole[role] = primaryPart.toolId;
|
|
503
|
+
}
|
|
504
|
+
for (const role of PRIMARY_ROLES) if (parts.filter((part) => part.role === role && !part.ownerPartId).length > 1) issues.push({
|
|
505
|
+
code: "DUPLICATE_PRIMARY_ROLE",
|
|
506
|
+
role,
|
|
507
|
+
message: `Only one primary '${role}' stack part is supported in v1.`
|
|
508
|
+
});
|
|
509
|
+
for (const part of parts) {
|
|
510
|
+
const definition = findDefinition(part);
|
|
511
|
+
if (!definition) issues.push({
|
|
512
|
+
code: "UNSUPPORTED_ROLE_BINDING",
|
|
513
|
+
partId: part.id,
|
|
514
|
+
role: part.role,
|
|
515
|
+
toolId: part.toolId,
|
|
516
|
+
message: `'${part.toolId}' is not a valid ${part.ecosystem} tool for role '${part.role}'.`
|
|
517
|
+
});
|
|
518
|
+
if (!PRIMARY_ROLES.has(part.role) && !part.ownerPartId) issues.push({
|
|
519
|
+
code: "MISSING_OWNER_PART",
|
|
520
|
+
partId: part.id,
|
|
521
|
+
role: part.role,
|
|
522
|
+
toolId: part.toolId,
|
|
523
|
+
message: `Capability stack part '${part.id}' must reference an owning primary part.`
|
|
524
|
+
});
|
|
525
|
+
if (part.ownerPartId && !partsById.has(part.ownerPartId)) issues.push({
|
|
526
|
+
code: "MISSING_OWNER_PART",
|
|
527
|
+
partId: part.id,
|
|
528
|
+
role: part.role,
|
|
529
|
+
toolId: part.toolId,
|
|
530
|
+
message: `Stack part '${part.id}' references missing owner '${part.ownerPartId}'.`
|
|
531
|
+
});
|
|
532
|
+
const owner = part.ownerPartId ? partsById.get(part.ownerPartId) : void 0;
|
|
533
|
+
if (definition && (PRIMARY_ROLES.has(part.role) || owner)) {
|
|
534
|
+
const siblingToolIdsByRole = {};
|
|
535
|
+
for (const sibling of parts) {
|
|
536
|
+
if (sibling.ownerPartId !== part.ownerPartId || sibling.source === "provided") continue;
|
|
537
|
+
siblingToolIdsByRole[sibling.role] = sibling.toolId;
|
|
538
|
+
}
|
|
539
|
+
const compatibilityIssue = getStackPartCompatibilityIssue(part, {
|
|
540
|
+
role: part.role,
|
|
541
|
+
ecosystem: part.ecosystem,
|
|
542
|
+
ownerRole: owner && PRIMARY_ROLES.has(owner.role) ? owner.role : void 0,
|
|
543
|
+
ownerToolId: owner?.toolId,
|
|
544
|
+
ownerEcosystem: owner?.ecosystem,
|
|
545
|
+
siblingToolIdsByRole,
|
|
546
|
+
primaryToolIdsByRole
|
|
547
|
+
});
|
|
548
|
+
if (compatibilityIssue) issues.push(compatibilityIssue);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
552
|
+
for (const part of parts) {
|
|
553
|
+
const key = `${part.ownerPartId ?? "root"}:${part.role}`;
|
|
554
|
+
byScope.set(key, [...byScope.get(key) ?? [], part]);
|
|
555
|
+
}
|
|
556
|
+
for (const scopedParts of byScope.values()) {
|
|
557
|
+
const selectedParts = scopedParts.filter((part) => part.source !== "provided");
|
|
558
|
+
if (selectedParts.length > 1) {
|
|
559
|
+
const [first] = selectedParts;
|
|
560
|
+
issues.push({
|
|
561
|
+
code: "DUPLICATE_ROLE_SCOPE",
|
|
562
|
+
partId: first?.id,
|
|
563
|
+
role: first?.role,
|
|
564
|
+
toolId: first?.toolId,
|
|
565
|
+
message: `Only one selected '${first?.role}' stack part is supported per owner scope.`
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
const selected = scopedParts.find((part) => part.source !== "provided");
|
|
569
|
+
const provided = scopedParts.find((part) => part.source === "provided");
|
|
570
|
+
if (!selected || !provided || selected.toolId === provided.toolId) continue;
|
|
571
|
+
const provider = provided.providedByPartId ? partsById.get(provided.providedByPartId) : void 0;
|
|
572
|
+
if (!((provider ? findDefinition(provider) : void 0)?.provides?.find((capability) => capability.role === provided.role && capability.toolId === provided.toolId))?.overrideable) issues.push({
|
|
573
|
+
code: "PROVIDED_CAPABILITY_CONFLICT",
|
|
574
|
+
partId: selected.id,
|
|
575
|
+
role: selected.role,
|
|
576
|
+
toolId: selected.toolId,
|
|
577
|
+
message: `'${selected.toolId}' conflicts with '${provided.toolId}' provided by '${provider?.toolId ?? "another part"}'.`
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
return { issues };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
//#endregion
|
|
584
|
+
export { getRoleTargetPath as a, legacyProjectConfigToStackParts as c, stackPartsToLegacyProjectConfigPartial as d, validateStackParts as f, formatStackPartSpec as i, materializeProvidedStackParts as l, compareLegacyConfigToStackParts as n, getStackPartOptions as o, createStackPart as r, getStackToolDefinitions as s, STACK_TOOL_DEFINITIONS as t, parseStackPartSpecs as u };
|
|
585
|
+
//# sourceMappingURL=stack-graph-C2tzuzRe.mjs.map
|