@stardeck-customer-apps/compose 0.8.0 → 0.10.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/SKILL.md +25 -0
- package/dist/cli.js +475 -353
- package/dist/index.d.mts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +627 -513
- package/dist/index.mjs +623 -513
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2027,16 +2027,20 @@ var require_semver2 = __commonJS({
|
|
|
2027
2027
|
// src/index.ts
|
|
2028
2028
|
var index_exports = {};
|
|
2029
2029
|
__export(index_exports, {
|
|
2030
|
+
BUILD_TSCONFIG_PATH: () => BUILD_TSCONFIG_PATH,
|
|
2030
2031
|
COMPOSE_INPUTS_PATH: () => COMPOSE_INPUTS_PATH,
|
|
2031
2032
|
assertSupportedTypescript: () => assertSupportedTypescript,
|
|
2032
2033
|
compose: () => compose,
|
|
2033
2034
|
escapeGitignore: () => escapeGitignore,
|
|
2035
|
+
findModuleBoundaryViolations: () => findModuleBoundaryViolations,
|
|
2036
|
+
findOptionalPeerViolations: () => findOptionalPeerViolations,
|
|
2037
|
+
formatBoundaryViolations: () => formatBoundaryViolations,
|
|
2034
2038
|
readComposeInputs: () => readComposeInputs
|
|
2035
2039
|
});
|
|
2036
2040
|
module.exports = __toCommonJS(index_exports);
|
|
2037
2041
|
var import_node_child_process2 = require("child_process");
|
|
2038
|
-
var
|
|
2039
|
-
var
|
|
2042
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
2043
|
+
var import_node_path3 = __toESM(require("path"));
|
|
2040
2044
|
|
|
2041
2045
|
// ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
|
|
2042
2046
|
var import_node_child_process = require("child_process");
|
|
@@ -2095,11 +2099,54 @@ function shellQuote(value) {
|
|
|
2095
2099
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
2096
2100
|
}
|
|
2097
2101
|
|
|
2098
|
-
// ../../packages/lib/src/shared/
|
|
2099
|
-
var
|
|
2102
|
+
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
2103
|
+
var import_zod = require("zod");
|
|
2104
|
+
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
2105
|
+
var composeInputsSchema = import_zod.z.strictObject({
|
|
2106
|
+
connectedStores: import_zod.z.array(
|
|
2107
|
+
import_zod.z.strictObject({
|
|
2108
|
+
storeId: import_zod.z.string().min(1),
|
|
2109
|
+
slug: import_zod.z.string().min(1),
|
|
2110
|
+
// A row with an empty binding key must not fail a deploy: the rail
|
|
2111
|
+
// treats falsy as "no binding key", so normalise rather than reject.
|
|
2112
|
+
bindingKey: import_zod.z.string().nullish().transform((value) => value || null),
|
|
2113
|
+
accessLevel: import_zod.z.enum(["read", "write", "admin"]),
|
|
2114
|
+
// Defaulted so a file written before the platform emitted store types
|
|
2115
|
+
// (database rows only) still parses. The platform starts writing
|
|
2116
|
+
// storage rows, with `type` set, once forks run a compose that reads it.
|
|
2117
|
+
type: import_zod.z.enum(["database", "storage"]).default("database")
|
|
2118
|
+
})
|
|
2119
|
+
).default([]),
|
|
2120
|
+
installs: import_zod.z.record(
|
|
2121
|
+
import_zod.z.string(),
|
|
2122
|
+
import_zod.z.strictObject({
|
|
2123
|
+
installedVersion: import_zod.z.string().min(1),
|
|
2124
|
+
datastoreBindings: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).nullish()
|
|
2125
|
+
})
|
|
2126
|
+
).default({})
|
|
2127
|
+
});
|
|
2128
|
+
var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
2129
|
+
var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
2130
|
+
function escapeStubIgnorePattern(pattern) {
|
|
2131
|
+
const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
|
|
2132
|
+
return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
|
|
2133
|
+
}
|
|
2134
|
+
function buildStubIgnore(artifactPaths) {
|
|
2135
|
+
const prefix = "apps/web/src/app/";
|
|
2136
|
+
const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
|
|
2137
|
+
return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// ../../packages/lib/src/shared/module-enablement.ts
|
|
2141
|
+
function isModuleEnabledByList(raw, name, kindOf) {
|
|
2142
|
+
if (!raw) return true;
|
|
2143
|
+
const kind = kindOf(name);
|
|
2144
|
+
if (kind != null && kind !== "feature") return true;
|
|
2145
|
+
return raw.split(",").includes(name);
|
|
2146
|
+
}
|
|
2100
2147
|
|
|
2101
2148
|
// ../../packages/module-contract/src/module-manifest.ts
|
|
2102
|
-
var
|
|
2149
|
+
var import_zod2 = require("zod");
|
|
2103
2150
|
|
|
2104
2151
|
// ../../packages/module-contract/src/slug.ts
|
|
2105
2152
|
function validateSlug(slug) {
|
|
@@ -2134,30 +2181,30 @@ var ROUTE_GROUP_REGEX = /^\([a-z0-9-]+\)$/;
|
|
|
2134
2181
|
var ENDPOINT_STATIC_SEGMENT_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2135
2182
|
var ENDPOINT_PARAM_NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
|
|
2136
2183
|
var ENDPOINT_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
2137
|
-
var moduleNameSchema =
|
|
2184
|
+
var moduleNameSchema = import_zod2.z.string().max(40).regex(KEBAB_SLUG_REGEX, "must be a kebab-case slug (lowercase letters, digits, hyphens)");
|
|
2138
2185
|
function isValidModuleName(value) {
|
|
2139
2186
|
return moduleNameSchema.safeParse(value).success;
|
|
2140
2187
|
}
|
|
2141
|
-
var projectSlugSchema =
|
|
2142
|
-
var moduleKindSchema =
|
|
2143
|
-
var endpointMethodSchema =
|
|
2144
|
-
function endpointPathValidationError(
|
|
2145
|
-
if (!
|
|
2188
|
+
var projectSlugSchema = import_zod2.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
2189
|
+
var moduleKindSchema = import_zod2.z.enum(["feature", "library", "contract"]);
|
|
2190
|
+
var endpointMethodSchema = import_zod2.z.enum(ENDPOINT_METHODS);
|
|
2191
|
+
function endpointPathValidationError(path4) {
|
|
2192
|
+
if (!path4.startsWith("/api/")) {
|
|
2146
2193
|
return 'must begin with "/api/"';
|
|
2147
2194
|
}
|
|
2148
|
-
if (
|
|
2195
|
+
if (path4.endsWith("/")) {
|
|
2149
2196
|
return "must not end with a trailing slash";
|
|
2150
2197
|
}
|
|
2151
|
-
if (
|
|
2198
|
+
if (path4.includes("?") || path4.includes("#")) {
|
|
2152
2199
|
return "must not include query or hash";
|
|
2153
2200
|
}
|
|
2154
|
-
if (
|
|
2201
|
+
if (path4.includes("\\")) {
|
|
2155
2202
|
return "must not include backslashes";
|
|
2156
2203
|
}
|
|
2157
|
-
if (
|
|
2204
|
+
if (path4.includes("%")) {
|
|
2158
2205
|
return "must not include percent-encoded segments";
|
|
2159
2206
|
}
|
|
2160
|
-
const parts =
|
|
2207
|
+
const parts = path4.split("/");
|
|
2161
2208
|
if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
|
|
2162
2209
|
return 'must begin with "/api/"';
|
|
2163
2210
|
}
|
|
@@ -2207,12 +2254,12 @@ function endpointPathValidationError(path3) {
|
|
|
2207
2254
|
}
|
|
2208
2255
|
return null;
|
|
2209
2256
|
}
|
|
2210
|
-
function normalizeEndpointPathForCollision(
|
|
2211
|
-
const error = endpointPathValidationError(
|
|
2257
|
+
function normalizeEndpointPathForCollision(path4) {
|
|
2258
|
+
const error = endpointPathValidationError(path4);
|
|
2212
2259
|
if (error != null) {
|
|
2213
2260
|
throw new Error(`invalid endpoint path: ${error}`);
|
|
2214
2261
|
}
|
|
2215
|
-
return
|
|
2262
|
+
return path4.split("/").map((segment, index) => {
|
|
2216
2263
|
if (index < 2) {
|
|
2217
2264
|
return segment;
|
|
2218
2265
|
}
|
|
@@ -2225,21 +2272,21 @@ function normalizeEndpointPathForCollision(path3) {
|
|
|
2225
2272
|
return segment;
|
|
2226
2273
|
}).join("/");
|
|
2227
2274
|
}
|
|
2228
|
-
var endpointPathSchema =
|
|
2229
|
-
const error = endpointPathValidationError(
|
|
2275
|
+
var endpointPathSchema = import_zod2.z.string().superRefine((path4, ctx) => {
|
|
2276
|
+
const error = endpointPathValidationError(path4);
|
|
2230
2277
|
if (error != null) {
|
|
2231
|
-
ctx.addIssue({ code:
|
|
2278
|
+
ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: error });
|
|
2232
2279
|
}
|
|
2233
2280
|
});
|
|
2234
|
-
var endpointClaimSchema =
|
|
2281
|
+
var endpointClaimSchema = import_zod2.z.object({
|
|
2235
2282
|
path: endpointPathSchema,
|
|
2236
|
-
methods:
|
|
2283
|
+
methods: import_zod2.z.array(endpointMethodSchema).min(1, "methods must be non-empty").superRefine((methods, ctx) => {
|
|
2237
2284
|
const seen = /* @__PURE__ */ new Set();
|
|
2238
2285
|
for (let i = 0; i < methods.length; i += 1) {
|
|
2239
2286
|
const method = methods[i];
|
|
2240
2287
|
if (seen.has(method)) {
|
|
2241
2288
|
ctx.addIssue({
|
|
2242
|
-
code:
|
|
2289
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2243
2290
|
message: `duplicate method "${method}"`,
|
|
2244
2291
|
path: [i]
|
|
2245
2292
|
});
|
|
@@ -2276,14 +2323,14 @@ function mountSegmentValidationError(segment) {
|
|
|
2276
2323
|
}
|
|
2277
2324
|
return null;
|
|
2278
2325
|
}
|
|
2279
|
-
var mountSegmentSchema =
|
|
2326
|
+
var mountSegmentSchema = import_zod2.z.string().superRefine((segment, ctx) => {
|
|
2280
2327
|
const error = mountSegmentValidationError(segment);
|
|
2281
2328
|
if (error != null) {
|
|
2282
|
-
ctx.addIssue({ code:
|
|
2329
|
+
ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: error });
|
|
2283
2330
|
}
|
|
2284
2331
|
});
|
|
2285
|
-
var mountSchema =
|
|
2286
|
-
group:
|
|
2332
|
+
var mountSchema = import_zod2.z.object({
|
|
2333
|
+
group: import_zod2.z.string().regex(ROUTE_GROUP_REGEX, 'must be a Next.js route group like "(admin)"'),
|
|
2287
2334
|
segment: mountSegmentSchema
|
|
2288
2335
|
}).strict();
|
|
2289
2336
|
function splitMountSegment(segment) {
|
|
@@ -2296,21 +2343,21 @@ function splitMountSegment(segment) {
|
|
|
2296
2343
|
}
|
|
2297
2344
|
return segment.split("/");
|
|
2298
2345
|
}
|
|
2299
|
-
var tableNameSchema =
|
|
2346
|
+
var tableNameSchema = import_zod2.z.string().regex(SNAKE_TABLE_REGEX, "must be snake_case");
|
|
2300
2347
|
var NPM_PACKAGE_NAME_REGEX = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
2301
|
-
var npmPackageNameSchema =
|
|
2302
|
-
var descriptionSchema =
|
|
2303
|
-
var crossAppSchema =
|
|
2304
|
-
exposes:
|
|
2305
|
-
consumes:
|
|
2348
|
+
var npmPackageNameSchema = import_zod2.z.string().max(214).regex(NPM_PACKAGE_NAME_REGEX, "must be a valid npm package name");
|
|
2349
|
+
var descriptionSchema = import_zod2.z.string().max(200, "must be at most 200 characters").refine((value) => value.trim().length > 0, "must be non-empty after trimming");
|
|
2350
|
+
var crossAppSchema = import_zod2.z.object({
|
|
2351
|
+
exposes: import_zod2.z.array(import_zod2.z.unknown()).max(0, "must be empty until cross-app contracts are defined"),
|
|
2352
|
+
consumes: import_zod2.z.array(import_zod2.z.unknown()).max(0, "must be empty until cross-app contracts are defined")
|
|
2306
2353
|
}).strict();
|
|
2307
|
-
var enhancementWorkflowsSchema =
|
|
2308
|
-
var enhancementEndpointsSchema =
|
|
2309
|
-
var datastoreRequirementSchema =
|
|
2354
|
+
var enhancementWorkflowsSchema = import_zod2.z.record(moduleNameSchema, import_zod2.z.array(import_zod2.z.string().min(1))).default({});
|
|
2355
|
+
var enhancementEndpointsSchema = import_zod2.z.record(moduleNameSchema, import_zod2.z.array(endpointClaimSchema)).default({});
|
|
2356
|
+
var datastoreRequirementSchema = import_zod2.z.object({
|
|
2310
2357
|
/** Logical name, module-local, snake_case. Never a physical store key. */
|
|
2311
2358
|
id: tableNameSchema,
|
|
2312
2359
|
/** "app" = this project's app-local store; "org" = an org-level shared store. */
|
|
2313
|
-
scope:
|
|
2360
|
+
scope: import_zod2.z.enum(["app", "org"]).default("app"),
|
|
2314
2361
|
/**
|
|
2315
2362
|
* Which kind of connected store satisfies this requirement. A "database"
|
|
2316
2363
|
* requirement only ever binds to a database store and a "storage" one
|
|
@@ -2318,34 +2365,34 @@ var datastoreRequirementSchema = import_zod.z.object({
|
|
|
2318
2365
|
* rail never crosses the two. Defaults to "database", the only kind the
|
|
2319
2366
|
* rail resolved before this field existed.
|
|
2320
2367
|
*/
|
|
2321
|
-
type:
|
|
2368
|
+
type: import_zod2.z.enum(["database", "storage"]).default("database"),
|
|
2322
2369
|
/** true → install fails when no store resolves; false → binding omitted, module degrades. */
|
|
2323
|
-
required:
|
|
2370
|
+
required: import_zod2.z.boolean().default(true),
|
|
2324
2371
|
/** Checked against the connected store's accessLevel at install. */
|
|
2325
|
-
access:
|
|
2372
|
+
access: import_zod2.z.enum(["read", "write", "admin"]).default("write")
|
|
2326
2373
|
}).strict();
|
|
2327
|
-
var permissionClaimSchema =
|
|
2374
|
+
var permissionClaimSchema = import_zod2.z.object({
|
|
2328
2375
|
/**
|
|
2329
2376
|
* Lowercase resource:action with one colon and no underscores. This grammar
|
|
2330
2377
|
* is deliberately narrower than the platform permission tool's charset.
|
|
2331
2378
|
*/
|
|
2332
|
-
key:
|
|
2379
|
+
key: import_zod2.z.string().regex(/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*$/, "must be a lowercase resource:action key").max(100, "must be at most 100 characters (platform permission tool limit)"),
|
|
2333
2380
|
/** Human label for the deployment auth config row. */
|
|
2334
2381
|
description: descriptionSchema
|
|
2335
2382
|
}).strict();
|
|
2336
|
-
var seedBundleSchema =
|
|
2383
|
+
var seedBundleSchema = import_zod2.z.object({
|
|
2337
2384
|
table: tableNameSchema,
|
|
2338
2385
|
/** Columns forming the ON CONFLICT key — must appear in every row. */
|
|
2339
|
-
conflictTarget:
|
|
2386
|
+
conflictTarget: import_zod2.z.array(import_zod2.z.string().min(1)).min(1),
|
|
2340
2387
|
/** Scalar JSON values only (no nested objects/arrays). */
|
|
2341
|
-
rows:
|
|
2388
|
+
rows: import_zod2.z.array(import_zod2.z.record(import_zod2.z.string(), import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number(), import_zod2.z.boolean(), import_zod2.z.null()]))).min(1)
|
|
2342
2389
|
}).strict().superRefine((bundle, ctx) => {
|
|
2343
2390
|
const rowKeys = bundle.rows.map((row) => Object.keys(row).sort().join("\0"));
|
|
2344
2391
|
const canonical = rowKeys[0];
|
|
2345
2392
|
for (let i = 1; i < rowKeys.length; i += 1) {
|
|
2346
2393
|
if (rowKeys[i] !== canonical) {
|
|
2347
2394
|
ctx.addIssue({
|
|
2348
|
-
code:
|
|
2395
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2349
2396
|
message: "all rows in a seed bundle must have identical key sets",
|
|
2350
2397
|
path: ["rows", i]
|
|
2351
2398
|
});
|
|
@@ -2357,7 +2404,7 @@ var seedBundleSchema = import_zod.z.object({
|
|
|
2357
2404
|
for (const col of bundle.conflictTarget) {
|
|
2358
2405
|
if (!keys.has(col)) {
|
|
2359
2406
|
ctx.addIssue({
|
|
2360
|
-
code:
|
|
2407
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2361
2408
|
message: `conflictTarget column "${col}" must be present in every row`,
|
|
2362
2409
|
path: ["rows", i]
|
|
2363
2410
|
});
|
|
@@ -2365,35 +2412,35 @@ var seedBundleSchema = import_zod.z.object({
|
|
|
2365
2412
|
}
|
|
2366
2413
|
}
|
|
2367
2414
|
});
|
|
2368
|
-
var moduleManifestSchema =
|
|
2415
|
+
var moduleManifestSchema = import_zod2.z.object({
|
|
2369
2416
|
name: moduleNameSchema,
|
|
2370
2417
|
kind: moduleKindSchema,
|
|
2371
2418
|
// Release versions are platform-owned in module_versions. Accept this
|
|
2372
2419
|
// legacy field while existing Module source ages out, but strip it from the
|
|
2373
2420
|
// parsed contract so no runtime can treat module.json as version authority.
|
|
2374
|
-
version:
|
|
2421
|
+
version: import_zod2.z.unknown().optional(),
|
|
2375
2422
|
home: projectSlugSchema,
|
|
2376
2423
|
/** Machine-checkable purpose line — required, ≤200 code units, non-blank when trimmed. */
|
|
2377
2424
|
description: descriptionSchema,
|
|
2378
|
-
uses:
|
|
2425
|
+
uses: import_zod2.z.array(moduleNameSchema).default([]),
|
|
2379
2426
|
/**
|
|
2380
2427
|
* Soft peers: this module ships `enhancements/<peer>/` directories that are
|
|
2381
2428
|
* composition-pruned — the installer includes them only when the peer is
|
|
2382
2429
|
* present, so the peer is NOT an install-time requirement. Disjoint from
|
|
2383
2430
|
* `uses`; entries may name modules absent from the composition.
|
|
2384
2431
|
*/
|
|
2385
|
-
enhancements:
|
|
2386
|
-
tables:
|
|
2387
|
-
mounts:
|
|
2388
|
-
integration:
|
|
2389
|
-
workflows:
|
|
2432
|
+
enhancements: import_zod2.z.array(moduleNameSchema).default([]),
|
|
2433
|
+
tables: import_zod2.z.array(tableNameSchema).default([]),
|
|
2434
|
+
mounts: import_zod2.z.array(mountSchema).default([]),
|
|
2435
|
+
integration: import_zod2.z.array(import_zod2.z.string()).default([]),
|
|
2436
|
+
workflows: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
|
|
2390
2437
|
/**
|
|
2391
2438
|
* Slice-scoped workflow names keyed by peer. Keys must be ⊆ `enhancements`;
|
|
2392
2439
|
* names must be unique across root `workflows` and every slice list.
|
|
2393
2440
|
*/
|
|
2394
2441
|
enhancementWorkflows: enhancementWorkflowsSchema,
|
|
2395
2442
|
/** Root API endpoint path claims. */
|
|
2396
|
-
endpoints:
|
|
2443
|
+
endpoints: import_zod2.z.array(endpointClaimSchema).default([]),
|
|
2397
2444
|
/**
|
|
2398
2445
|
* Slice-scoped endpoint claims keyed by peer. Keys must be ⊆ `enhancements`;
|
|
2399
2446
|
* normalized paths must be unique across root and every slice.
|
|
@@ -2409,38 +2456,38 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2409
2456
|
* to a physical connected store and generates module-datastores.gen.ts;
|
|
2410
2457
|
* unlike seeds, this field drives generated output.
|
|
2411
2458
|
*/
|
|
2412
|
-
datastores:
|
|
2459
|
+
datastores: import_zod2.z.array(datastoreRequirementSchema).optional(),
|
|
2413
2460
|
/**
|
|
2414
2461
|
* Keys this module's guards enforce. Install registers them in deployment
|
|
2415
2462
|
* auth config; registration only — declaring is never granting.
|
|
2416
2463
|
*/
|
|
2417
|
-
permissions:
|
|
2464
|
+
permissions: import_zod2.z.array(permissionClaimSchema).optional(),
|
|
2418
2465
|
/**
|
|
2419
2466
|
* Optional starter rows for tables this module owns. Replay is
|
|
2420
2467
|
* `INSERT ... ON CONFLICT (conflictTarget) DO NOTHING` — idempotent by
|
|
2421
2468
|
* construction; the rail (not this schema) performs the replay.
|
|
2422
2469
|
*/
|
|
2423
|
-
seeds:
|
|
2470
|
+
seeds: import_zod2.z.array(seedBundleSchema).optional(),
|
|
2424
2471
|
/** npm packages the source imports beyond the base app. Names only; the rail installs each at the home app's range. */
|
|
2425
|
-
packages:
|
|
2472
|
+
packages: import_zod2.z.array(npmPackageNameSchema).optional(),
|
|
2426
2473
|
/**
|
|
2427
2474
|
* UI-layer contract. `separable: true` asserts the Module's components/ and
|
|
2428
2475
|
* routes/ hold presentation only — the Blueprint's
|
|
2429
2476
|
* `module-boundaries/ui-layer` lint rule enforces it — so a fork may
|
|
2430
2477
|
* soft-eject them. See docs/module-ui-soft-eject-plan.md.
|
|
2431
2478
|
*/
|
|
2432
|
-
ui:
|
|
2479
|
+
ui: import_zod2.z.object({ separable: import_zod2.z.boolean() }).strict().optional()
|
|
2433
2480
|
}).strict().superRefine((manifest, ctx) => {
|
|
2434
2481
|
if (manifest.uses.includes(manifest.name)) {
|
|
2435
2482
|
ctx.addIssue({
|
|
2436
|
-
code:
|
|
2483
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2437
2484
|
message: "a module cannot list itself in uses",
|
|
2438
2485
|
path: ["uses"]
|
|
2439
2486
|
});
|
|
2440
2487
|
}
|
|
2441
2488
|
if (manifest.enhancements.includes(manifest.name)) {
|
|
2442
2489
|
ctx.addIssue({
|
|
2443
|
-
code:
|
|
2490
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2444
2491
|
message: "a module cannot list itself in enhancements",
|
|
2445
2492
|
path: ["enhancements"]
|
|
2446
2493
|
});
|
|
@@ -2448,7 +2495,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2448
2495
|
const hardAndSoft = manifest.enhancements.filter((peer) => manifest.uses.includes(peer));
|
|
2449
2496
|
if (hardAndSoft.length > 0) {
|
|
2450
2497
|
ctx.addIssue({
|
|
2451
|
-
code:
|
|
2498
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2452
2499
|
message: `uses and enhancements must be disjoint (a peer is either a hard dep or a soft one): ${hardAndSoft.join(", ")}`,
|
|
2453
2500
|
path: ["enhancements"]
|
|
2454
2501
|
});
|
|
@@ -2457,7 +2504,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2457
2504
|
for (const peer of Object.keys(manifest.enhancementWorkflows)) {
|
|
2458
2505
|
if (!enhancementPeers.has(peer)) {
|
|
2459
2506
|
ctx.addIssue({
|
|
2460
|
-
code:
|
|
2507
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2461
2508
|
message: `enhancementWorkflows key "${peer}" must be listed in enhancements`,
|
|
2462
2509
|
path: ["enhancementWorkflows", peer]
|
|
2463
2510
|
});
|
|
@@ -2466,23 +2513,23 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2466
2513
|
for (const peer of Object.keys(manifest.enhancementEndpoints)) {
|
|
2467
2514
|
if (!enhancementPeers.has(peer)) {
|
|
2468
2515
|
ctx.addIssue({
|
|
2469
|
-
code:
|
|
2516
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2470
2517
|
message: `enhancementEndpoints key "${peer}" must be listed in enhancements`,
|
|
2471
2518
|
path: ["enhancementEndpoints", peer]
|
|
2472
2519
|
});
|
|
2473
2520
|
}
|
|
2474
2521
|
}
|
|
2475
2522
|
const seenWorkflows = /* @__PURE__ */ new Map();
|
|
2476
|
-
const noteWorkflow = (name,
|
|
2523
|
+
const noteWorkflow = (name, path4) => {
|
|
2477
2524
|
const existing = seenWorkflows.get(name);
|
|
2478
2525
|
if (existing != null) {
|
|
2479
2526
|
ctx.addIssue({
|
|
2480
|
-
code:
|
|
2527
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2481
2528
|
message: `duplicate workflow name "${name}" (also at ${existing})`,
|
|
2482
|
-
path:
|
|
2529
|
+
path: path4
|
|
2483
2530
|
});
|
|
2484
2531
|
} else {
|
|
2485
|
-
seenWorkflows.set(name,
|
|
2532
|
+
seenWorkflows.set(name, path4.join("."));
|
|
2486
2533
|
}
|
|
2487
2534
|
};
|
|
2488
2535
|
for (let i = 0; i < manifest.workflows.length; i += 1) {
|
|
@@ -2494,7 +2541,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2494
2541
|
}
|
|
2495
2542
|
}
|
|
2496
2543
|
const seenEndpointKeys = /* @__PURE__ */ new Map();
|
|
2497
|
-
const noteEndpoint = (claim,
|
|
2544
|
+
const noteEndpoint = (claim, path4) => {
|
|
2498
2545
|
let key;
|
|
2499
2546
|
try {
|
|
2500
2547
|
key = normalizeEndpointPathForCollision(claim.path);
|
|
@@ -2504,12 +2551,12 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2504
2551
|
const existing = seenEndpointKeys.get(key);
|
|
2505
2552
|
if (existing != null) {
|
|
2506
2553
|
ctx.addIssue({
|
|
2507
|
-
code:
|
|
2554
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2508
2555
|
message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
|
|
2509
|
-
path:
|
|
2556
|
+
path: path4
|
|
2510
2557
|
});
|
|
2511
2558
|
} else {
|
|
2512
|
-
seenEndpointKeys.set(key,
|
|
2559
|
+
seenEndpointKeys.set(key, path4.join("."));
|
|
2513
2560
|
}
|
|
2514
2561
|
};
|
|
2515
2562
|
for (let i = 0; i < manifest.endpoints.length; i += 1) {
|
|
@@ -2526,7 +2573,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2526
2573
|
const id = manifest.datastores[i].id;
|
|
2527
2574
|
if (seenDatastoreIds.has(id)) {
|
|
2528
2575
|
ctx.addIssue({
|
|
2529
|
-
code:
|
|
2576
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2530
2577
|
message: `duplicate datastore id "${id}"`,
|
|
2531
2578
|
path: ["datastores", i, "id"]
|
|
2532
2579
|
});
|
|
@@ -2541,7 +2588,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2541
2588
|
const key = manifest.permissions[i].key;
|
|
2542
2589
|
if (seenPermissionKeys.has(key)) {
|
|
2543
2590
|
ctx.addIssue({
|
|
2544
|
-
code:
|
|
2591
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2545
2592
|
message: `duplicate permission key "${key}"`,
|
|
2546
2593
|
path: ["permissions", i, "key"]
|
|
2547
2594
|
});
|
|
@@ -2553,35 +2600,35 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2553
2600
|
if (manifest.kind === "library") {
|
|
2554
2601
|
if (manifest.tables.length > 0) {
|
|
2555
2602
|
ctx.addIssue({
|
|
2556
|
-
code:
|
|
2603
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2557
2604
|
message: "library modules must not declare tables",
|
|
2558
2605
|
path: ["tables"]
|
|
2559
2606
|
});
|
|
2560
2607
|
}
|
|
2561
2608
|
if (manifest.mounts.length > 0) {
|
|
2562
2609
|
ctx.addIssue({
|
|
2563
|
-
code:
|
|
2610
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2564
2611
|
message: "library modules must not declare mounts",
|
|
2565
2612
|
path: ["mounts"]
|
|
2566
2613
|
});
|
|
2567
2614
|
}
|
|
2568
2615
|
if (manifest.seeds != null && manifest.seeds.length > 0) {
|
|
2569
2616
|
ctx.addIssue({
|
|
2570
|
-
code:
|
|
2617
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2571
2618
|
message: "library modules must not declare seeds",
|
|
2572
2619
|
path: ["seeds"]
|
|
2573
2620
|
});
|
|
2574
2621
|
}
|
|
2575
2622
|
if (manifest.datastores != null && manifest.datastores.length > 0) {
|
|
2576
2623
|
ctx.addIssue({
|
|
2577
|
-
code:
|
|
2624
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2578
2625
|
message: "library modules must not declare datastores",
|
|
2579
2626
|
path: ["datastores"]
|
|
2580
2627
|
});
|
|
2581
2628
|
}
|
|
2582
2629
|
if (manifest.permissions != null && manifest.permissions.length > 0) {
|
|
2583
2630
|
ctx.addIssue({
|
|
2584
|
-
code:
|
|
2631
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2585
2632
|
message: "library modules must not declare permissions",
|
|
2586
2633
|
path: ["permissions"]
|
|
2587
2634
|
});
|
|
@@ -2589,7 +2636,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2589
2636
|
}
|
|
2590
2637
|
if (manifest.kind === "contract" && manifest.mounts.length > 0) {
|
|
2591
2638
|
ctx.addIssue({
|
|
2592
|
-
code:
|
|
2639
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2593
2640
|
message: "contract modules must not declare mounts",
|
|
2594
2641
|
path: ["mounts"]
|
|
2595
2642
|
});
|
|
@@ -2600,7 +2647,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2600
2647
|
const bundle = manifest.seeds[i];
|
|
2601
2648
|
if (!ownedTables.has(bundle.table)) {
|
|
2602
2649
|
ctx.addIssue({
|
|
2603
|
-
code:
|
|
2650
|
+
code: import_zod2.z.ZodIssueCode.custom,
|
|
2604
2651
|
message: `seeds may only target tables listed in tables (got "${bundle.table}")`,
|
|
2605
2652
|
path: ["seeds", i, "table"]
|
|
2606
2653
|
});
|
|
@@ -2610,8 +2657,8 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2610
2657
|
}).transform(({ version: _legacyVersion, ...manifest }) => manifest);
|
|
2611
2658
|
function formatZodErrors(error) {
|
|
2612
2659
|
return error.issues.map((issue) => {
|
|
2613
|
-
const
|
|
2614
|
-
return `${
|
|
2660
|
+
const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2661
|
+
return `${path4}: ${issue.message}`;
|
|
2615
2662
|
});
|
|
2616
2663
|
}
|
|
2617
2664
|
function parseModuleManifest(raw) {
|
|
@@ -2651,10 +2698,10 @@ var MANDATORY_MODULE_CHECK_DESCRIPTORS = Object.freeze([
|
|
|
2651
2698
|
]);
|
|
2652
2699
|
|
|
2653
2700
|
// ../../packages/module-contract/src/module-structure.ts
|
|
2654
|
-
var
|
|
2701
|
+
var import_zod4 = require("zod");
|
|
2655
2702
|
|
|
2656
2703
|
// ../../packages/module-contract/src/module-e2e-manifest.ts
|
|
2657
|
-
var
|
|
2704
|
+
var import_zod3 = require("zod");
|
|
2658
2705
|
function e2eSpecFileValidationError(file) {
|
|
2659
2706
|
if (file.length === 0) {
|
|
2660
2707
|
return "must be a non-empty relative path ending in .spec.ts";
|
|
@@ -2682,22 +2729,22 @@ function e2eSpecFileValidationError(file) {
|
|
|
2682
2729
|
}
|
|
2683
2730
|
return null;
|
|
2684
2731
|
}
|
|
2685
|
-
var e2eSpecFileSchema =
|
|
2732
|
+
var e2eSpecFileSchema = import_zod3.z.string().superRefine((file, ctx) => {
|
|
2686
2733
|
const error = e2eSpecFileValidationError(file);
|
|
2687
2734
|
if (error != null) {
|
|
2688
|
-
ctx.addIssue({ code:
|
|
2735
|
+
ctx.addIssue({ code: import_zod3.z.ZodIssueCode.custom, message: error });
|
|
2689
2736
|
}
|
|
2690
2737
|
});
|
|
2691
|
-
var e2eSpecSchema =
|
|
2738
|
+
var e2eSpecSchema = import_zod3.z.object({
|
|
2692
2739
|
file: e2eSpecFileSchema,
|
|
2693
|
-
requires:
|
|
2740
|
+
requires: import_zod3.z.array(moduleNameSchema).min(1, "requires must be non-empty")
|
|
2694
2741
|
}).strict().superRefine((spec, ctx) => {
|
|
2695
2742
|
const seen = /* @__PURE__ */ new Set();
|
|
2696
2743
|
for (let i = 0; i < spec.requires.length; i += 1) {
|
|
2697
2744
|
const name = spec.requires[i];
|
|
2698
2745
|
if (seen.has(name)) {
|
|
2699
2746
|
ctx.addIssue({
|
|
2700
|
-
code:
|
|
2747
|
+
code: import_zod3.z.ZodIssueCode.custom,
|
|
2701
2748
|
message: `duplicate requirement "${name}"`,
|
|
2702
2749
|
path: ["requires", i]
|
|
2703
2750
|
});
|
|
@@ -2706,15 +2753,15 @@ var e2eSpecSchema = import_zod2.z.object({
|
|
|
2706
2753
|
}
|
|
2707
2754
|
}
|
|
2708
2755
|
});
|
|
2709
|
-
var moduleE2eManifestSchema =
|
|
2710
|
-
specs:
|
|
2756
|
+
var moduleE2eManifestSchema = import_zod3.z.object({
|
|
2757
|
+
specs: import_zod3.z.array(e2eSpecSchema).min(1, "specs must contain at least one entry")
|
|
2711
2758
|
}).strict().superRefine((manifest, ctx) => {
|
|
2712
2759
|
const seenFiles = /* @__PURE__ */ new Set();
|
|
2713
2760
|
for (let i = 0; i < manifest.specs.length; i += 1) {
|
|
2714
2761
|
const file = manifest.specs[i].file;
|
|
2715
2762
|
if (seenFiles.has(file)) {
|
|
2716
2763
|
ctx.addIssue({
|
|
2717
|
-
code:
|
|
2764
|
+
code: import_zod3.z.ZodIssueCode.custom,
|
|
2718
2765
|
message: `duplicate file "${file}"`,
|
|
2719
2766
|
path: ["specs", i, "file"]
|
|
2720
2767
|
});
|
|
@@ -2956,7 +3003,7 @@ function compareCodeUnit(a, b) {
|
|
|
2956
3003
|
// ../../packages/module-contract/src/module-structure.ts
|
|
2957
3004
|
var MODULE_STRUCTURE_JSON_BYTE_CAP = 64 * 1024;
|
|
2958
3005
|
function uniqueSortedStringArray(label) {
|
|
2959
|
-
return
|
|
3006
|
+
return import_zod4.z.array(import_zod4.z.string()).superRefine((arr, ctx) => {
|
|
2960
3007
|
if (new Set(arr).size !== arr.length) {
|
|
2961
3008
|
ctx.addIssue({ code: "custom", message: `${label} must be unique` });
|
|
2962
3009
|
return;
|
|
@@ -2972,10 +3019,10 @@ function uniqueSortedStringArray(label) {
|
|
|
2972
3019
|
});
|
|
2973
3020
|
}
|
|
2974
3021
|
function contributionPermissionsSchema(label) {
|
|
2975
|
-
return
|
|
2976
|
-
|
|
2977
|
-
contributionId:
|
|
2978
|
-
permission:
|
|
3022
|
+
return import_zod4.z.array(
|
|
3023
|
+
import_zod4.z.object({
|
|
3024
|
+
contributionId: import_zod4.z.string().min(1),
|
|
3025
|
+
permission: import_zod4.z.string().min(1)
|
|
2979
3026
|
}).strict()
|
|
2980
3027
|
).superRefine((entries, ctx) => {
|
|
2981
3028
|
const keys = entries.map((entry) => `${entry.contributionId}\0${entry.permission}`);
|
|
@@ -2993,17 +3040,17 @@ function contributionPermissionsSchema(label) {
|
|
|
2993
3040
|
}
|
|
2994
3041
|
}).optional();
|
|
2995
3042
|
}
|
|
2996
|
-
var collectedFilePayloadSchema =
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3043
|
+
var collectedFilePayloadSchema = import_zod4.z.discriminatedUnion("status", [
|
|
3044
|
+
import_zod4.z.object({ status: import_zod4.z.literal("ok"), text: import_zod4.z.string() }).strict(),
|
|
3045
|
+
import_zod4.z.object({ status: import_zod4.z.literal("missing") }).strict(),
|
|
3046
|
+
import_zod4.z.object({ status: import_zod4.z.literal("too-large") }).strict(),
|
|
3047
|
+
import_zod4.z.object({ status: import_zod4.z.literal("unreadable") }).strict()
|
|
3001
3048
|
]);
|
|
3002
|
-
var collectedLocaleFileSchema =
|
|
3003
|
-
locale:
|
|
3049
|
+
var collectedLocaleFileSchema = import_zod4.z.object({
|
|
3050
|
+
locale: import_zod4.z.string().min(1),
|
|
3004
3051
|
payload: collectedFilePayloadSchema
|
|
3005
3052
|
}).strict();
|
|
3006
|
-
var collectedE2eFactsSchema =
|
|
3053
|
+
var collectedE2eFactsSchema = import_zod4.z.object({
|
|
3007
3054
|
manifest: collectedFilePayloadSchema,
|
|
3008
3055
|
specFiles: uniqueSortedStringArray("specFiles"),
|
|
3009
3056
|
escapingSymlinks: uniqueSortedStringArray("escapingSymlinks")
|
|
@@ -3011,34 +3058,34 @@ var collectedE2eFactsSchema = import_zod3.z.object({
|
|
|
3011
3058
|
function validateRouteExportEvidenceCongruence(input) {
|
|
3012
3059
|
const entrySet = new Set(input.routeEntries);
|
|
3013
3060
|
const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
|
|
3014
|
-
for (const
|
|
3015
|
-
if (!entrySet.has(
|
|
3016
|
-
input.onIssue(`routePageExports path "${
|
|
3061
|
+
for (const path4 of exportPaths) {
|
|
3062
|
+
if (!entrySet.has(path4)) {
|
|
3063
|
+
input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
|
|
3017
3064
|
}
|
|
3018
3065
|
}
|
|
3019
3066
|
if (input.routeCollectionErrors.length === 0) {
|
|
3020
3067
|
const exportSet = new Set(exportPaths);
|
|
3021
|
-
for (const
|
|
3022
|
-
if (!exportSet.has(
|
|
3023
|
-
input.onIssue(`routeEntries path "${
|
|
3068
|
+
for (const path4 of input.routeEntries) {
|
|
3069
|
+
if (!exportSet.has(path4)) {
|
|
3070
|
+
input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
|
|
3024
3071
|
}
|
|
3025
3072
|
}
|
|
3026
3073
|
}
|
|
3027
3074
|
}
|
|
3028
|
-
var collectedEnhancementFactsSchema =
|
|
3029
|
-
peer:
|
|
3030
|
-
hasContributionsTs:
|
|
3075
|
+
var collectedEnhancementFactsSchema = import_zod4.z.object({
|
|
3076
|
+
peer: import_zod4.z.string().min(1),
|
|
3077
|
+
hasContributionsTs: import_zod4.z.boolean(),
|
|
3031
3078
|
contributionPermissions: contributionPermissionsSchema("enhancement contributionPermissions"),
|
|
3032
|
-
hasInitializeServerTs:
|
|
3079
|
+
hasInitializeServerTs: import_zod4.z.boolean(),
|
|
3033
3080
|
contributionsCollectionErrors: uniqueSortedStringArray(
|
|
3034
3081
|
"enhancement contributionsCollectionErrors"
|
|
3035
3082
|
),
|
|
3036
3083
|
initializeServerCollectionErrors: uniqueSortedStringArray(
|
|
3037
3084
|
"enhancement initializeServerCollectionErrors"
|
|
3038
3085
|
),
|
|
3039
|
-
hasRoutesDir:
|
|
3086
|
+
hasRoutesDir: import_zod4.z.boolean(),
|
|
3040
3087
|
routeCollectionErrors: uniqueSortedStringArray("enhancement routeCollectionErrors"),
|
|
3041
|
-
locales:
|
|
3088
|
+
locales: import_zod4.z.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
|
|
3042
3089
|
const names = locales.map((l) => l.locale);
|
|
3043
3090
|
if (new Set(names).size !== names.length) {
|
|
3044
3091
|
ctx.addIssue({ code: "custom", message: "enhancement locales must be unique by locale" });
|
|
@@ -3053,27 +3100,27 @@ var collectedEnhancementFactsSchema = import_zod3.z.object({
|
|
|
3053
3100
|
}),
|
|
3054
3101
|
e2e: collectedE2eFactsSchema.nullable()
|
|
3055
3102
|
}).strict();
|
|
3056
|
-
var moduleStructureFactsSchema =
|
|
3057
|
-
collected:
|
|
3058
|
-
dirName:
|
|
3059
|
-
hasIndexTs:
|
|
3060
|
-
hasContributionsTs:
|
|
3103
|
+
var moduleStructureFactsSchema = import_zod4.z.object({
|
|
3104
|
+
collected: import_zod4.z.literal(true),
|
|
3105
|
+
dirName: import_zod4.z.string().min(1),
|
|
3106
|
+
hasIndexTs: import_zod4.z.boolean(),
|
|
3107
|
+
hasContributionsTs: import_zod4.z.boolean(),
|
|
3061
3108
|
contributionPermissions: contributionPermissionsSchema("contributionPermissions"),
|
|
3062
|
-
hasSlotsTs:
|
|
3109
|
+
hasSlotsTs: import_zod4.z.boolean(),
|
|
3063
3110
|
contributionsCollectionErrors: uniqueSortedStringArray("contributionsCollectionErrors"),
|
|
3064
3111
|
slotsCollectionErrors: uniqueSortedStringArray("slotsCollectionErrors"),
|
|
3065
|
-
hasTestsDir:
|
|
3066
|
-
hasDatastoreDir:
|
|
3067
|
-
datastoreFileCount:
|
|
3068
|
-
hasServerIndexTs:
|
|
3069
|
-
hasOtherServerTs:
|
|
3070
|
-
hasE2eDir:
|
|
3071
|
-
hasRoutesDir:
|
|
3112
|
+
hasTestsDir: import_zod4.z.boolean(),
|
|
3113
|
+
hasDatastoreDir: import_zod4.z.boolean(),
|
|
3114
|
+
datastoreFileCount: import_zod4.z.number().int().nonnegative(),
|
|
3115
|
+
hasServerIndexTs: import_zod4.z.boolean(),
|
|
3116
|
+
hasOtherServerTs: import_zod4.z.boolean(),
|
|
3117
|
+
hasE2eDir: import_zod4.z.boolean(),
|
|
3118
|
+
hasRoutesDir: import_zod4.z.boolean(),
|
|
3072
3119
|
routeEntries: uniqueSortedStringArray("routeEntries"),
|
|
3073
|
-
routePageExports:
|
|
3074
|
-
|
|
3075
|
-
nextRelativePath:
|
|
3076
|
-
hasGenerateMetadata:
|
|
3120
|
+
routePageExports: import_zod4.z.array(
|
|
3121
|
+
import_zod4.z.object({
|
|
3122
|
+
nextRelativePath: import_zod4.z.string().min(1),
|
|
3123
|
+
hasGenerateMetadata: import_zod4.z.boolean()
|
|
3077
3124
|
}).strict()
|
|
3078
3125
|
).superRefine((entries, ctx) => {
|
|
3079
3126
|
const paths = entries.map((e) => e.nextRelativePath);
|
|
@@ -3095,7 +3142,7 @@ var moduleStructureFactsSchema = import_zod3.z.object({
|
|
|
3095
3142
|
}
|
|
3096
3143
|
}),
|
|
3097
3144
|
routeCollectionErrors: uniqueSortedStringArray("routeCollectionErrors"),
|
|
3098
|
-
rootLocales:
|
|
3145
|
+
rootLocales: import_zod4.z.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
|
|
3099
3146
|
const names = locales.map((l) => l.locale);
|
|
3100
3147
|
if (new Set(names).size !== names.length) {
|
|
3101
3148
|
ctx.addIssue({ code: "custom", message: "rootLocales must be unique by locale" });
|
|
@@ -3109,7 +3156,7 @@ var moduleStructureFactsSchema = import_zod3.z.object({
|
|
|
3109
3156
|
}
|
|
3110
3157
|
}),
|
|
3111
3158
|
rootE2e: collectedE2eFactsSchema.nullable(),
|
|
3112
|
-
enhancements:
|
|
3159
|
+
enhancements: import_zod4.z.array(collectedEnhancementFactsSchema).superRefine((enhancements, ctx) => {
|
|
3113
3160
|
const peers = enhancements.map((e) => e.peer);
|
|
3114
3161
|
if (new Set(peers).size !== peers.length) {
|
|
3115
3162
|
ctx.addIssue({ code: "custom", message: "enhancement peers must be unique" });
|
|
@@ -3130,10 +3177,10 @@ var moduleStructureFactsSchema = import_zod3.z.object({
|
|
|
3130
3177
|
onIssue: (message) => ctx.addIssue({ code: "custom", message })
|
|
3131
3178
|
});
|
|
3132
3179
|
});
|
|
3133
|
-
var moduleStructureCollectionSchema =
|
|
3180
|
+
var moduleStructureCollectionSchema = import_zod4.z.object({
|
|
3134
3181
|
homeLocales: uniqueSortedStringArray("homeLocales"),
|
|
3135
|
-
modules:
|
|
3136
|
-
truncated:
|
|
3182
|
+
modules: import_zod4.z.array(moduleStructureFactsSchema),
|
|
3183
|
+
truncated: import_zod4.z.boolean().optional(),
|
|
3137
3184
|
collectionErrors: uniqueSortedStringArray("collectionErrors").optional()
|
|
3138
3185
|
}).strict().superRefine((collection, ctx) => {
|
|
3139
3186
|
const dirs = collection.modules.map((m) => m.dirName);
|
|
@@ -3150,9 +3197,9 @@ var moduleStructureCollectionSchema = import_zod3.z.object({
|
|
|
3150
3197
|
});
|
|
3151
3198
|
|
|
3152
3199
|
// ../../packages/module-contract/src/locale.ts
|
|
3153
|
-
var
|
|
3200
|
+
var import_zod5 = require("zod");
|
|
3154
3201
|
var LOCALE_BASENAME_REGEX = /^[a-z]{2,3}(-[A-Za-z0-9]+)*$/;
|
|
3155
|
-
var localeBasenameSchema =
|
|
3202
|
+
var localeBasenameSchema = import_zod5.z.string().max(35).regex(LOCALE_BASENAME_REGEX, "must be a BCP47-like locale tag (e.g. en, th, en-US)");
|
|
3156
3203
|
function isValidLocaleBasename(value) {
|
|
3157
3204
|
return localeBasenameSchema.safeParse(value).success;
|
|
3158
3205
|
}
|
|
@@ -3555,43 +3602,43 @@ function formatRoutePageExportErrors(nextRelativePath, validation) {
|
|
|
3555
3602
|
var import_typescript2 = __toESM(require("typescript"), 1);
|
|
3556
3603
|
|
|
3557
3604
|
// ../../packages/module-contract/src/module-registry.ts
|
|
3558
|
-
var
|
|
3559
|
-
var projectSlugSchema2 =
|
|
3560
|
-
var registryEnhancementSchema =
|
|
3605
|
+
var import_zod6 = require("zod");
|
|
3606
|
+
var projectSlugSchema2 = import_zod6.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
3607
|
+
var registryEnhancementSchema = import_zod6.z.object({
|
|
3561
3608
|
peer: moduleNameSchema,
|
|
3562
|
-
active:
|
|
3609
|
+
active: import_zod6.z.boolean()
|
|
3563
3610
|
}).strict();
|
|
3564
|
-
var registryModuleSchema =
|
|
3611
|
+
var registryModuleSchema = import_zod6.z.object({
|
|
3565
3612
|
name: moduleNameSchema,
|
|
3566
3613
|
/** Installed version as recorded by the rail (non-empty string). */
|
|
3567
|
-
version:
|
|
3614
|
+
version: import_zod6.z.string().min(1),
|
|
3568
3615
|
home: projectSlugSchema2,
|
|
3569
3616
|
kind: moduleKindSchema,
|
|
3570
|
-
mounts:
|
|
3571
|
-
enhancements:
|
|
3617
|
+
mounts: import_zod6.z.array(mountSchema),
|
|
3618
|
+
enhancements: import_zod6.z.array(registryEnhancementSchema)
|
|
3572
3619
|
}).strict().superRefine((mod, ctx) => {
|
|
3573
3620
|
for (let i = 0; i < mod.enhancements.length; i += 1) {
|
|
3574
3621
|
if (mod.enhancements[i].peer === mod.name) {
|
|
3575
3622
|
ctx.addIssue({
|
|
3576
|
-
code:
|
|
3623
|
+
code: import_zod6.z.ZodIssueCode.custom,
|
|
3577
3624
|
message: "enhancement peer must not equal the module's own name",
|
|
3578
3625
|
path: ["enhancements", i, "peer"]
|
|
3579
3626
|
});
|
|
3580
3627
|
}
|
|
3581
3628
|
}
|
|
3582
3629
|
});
|
|
3583
|
-
var generatedModuleRegistrySchema =
|
|
3584
|
-
registryVersion:
|
|
3630
|
+
var generatedModuleRegistrySchema = import_zod6.z.object({
|
|
3631
|
+
registryVersion: import_zod6.z.literal(1),
|
|
3585
3632
|
/** Rail run id, or `"manual"` for fixtures. */
|
|
3586
|
-
generatedBy:
|
|
3587
|
-
modules:
|
|
3633
|
+
generatedBy: import_zod6.z.string().min(1),
|
|
3634
|
+
modules: import_zod6.z.array(registryModuleSchema)
|
|
3588
3635
|
}).strict().superRefine((registry, ctx) => {
|
|
3589
3636
|
const seen = /* @__PURE__ */ new Set();
|
|
3590
3637
|
for (let i = 0; i < registry.modules.length; i += 1) {
|
|
3591
3638
|
const name = registry.modules[i].name;
|
|
3592
3639
|
if (seen.has(name)) {
|
|
3593
3640
|
ctx.addIssue({
|
|
3594
|
-
code:
|
|
3641
|
+
code: import_zod6.z.ZodIssueCode.custom,
|
|
3595
3642
|
message: `duplicate module name "${name}"`,
|
|
3596
3643
|
path: ["modules", i, "name"]
|
|
3597
3644
|
});
|
|
@@ -3962,169 +4009,6 @@ function analyzeClientSafeEntryImports(sourceText, fileName) {
|
|
|
3962
4009
|
return analyzeClientSafeEntryImportsWithTs(import_typescript3.default, sourceText, fileName);
|
|
3963
4010
|
}
|
|
3964
4011
|
|
|
3965
|
-
// ../../packages/lib/src/shared/module-composition.ts
|
|
3966
|
-
var moduleRefSchema = import_zod6.z.object({
|
|
3967
|
-
homeProjectId: import_zod6.z.string().uuid(),
|
|
3968
|
-
moduleName: moduleNameSchema
|
|
3969
|
-
});
|
|
3970
|
-
var creationSourceBlankSchema = import_zod6.z.object({
|
|
3971
|
-
type: import_zod6.z.literal("blank")
|
|
3972
|
-
});
|
|
3973
|
-
var creationSourceBlueprintSchema = import_zod6.z.object({
|
|
3974
|
-
type: import_zod6.z.literal("blueprint"),
|
|
3975
|
-
/** Internal template/fork project id — keep template naming in identifiers. */
|
|
3976
|
-
blueprintProjectId: import_zod6.z.string().uuid(),
|
|
3977
|
-
/**
|
|
3978
|
-
* `null`/absent means every feature Module. `[]` is refused: the platform
|
|
3979
|
-
* serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
|
|
3980
|
-
* enablement parser reads back as "all" (`module-enablement.ts`) — the
|
|
3981
|
-
* opposite of what an empty selection means.
|
|
3982
|
-
*/
|
|
3983
|
-
enabledModules: import_zod6.z.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
|
|
3984
|
-
});
|
|
3985
|
-
var creationSourceModulesSchema = import_zod6.z.object({
|
|
3986
|
-
type: import_zod6.z.literal("modules"),
|
|
3987
|
-
requested: import_zod6.z.array(moduleRefSchema).min(1)
|
|
3988
|
-
});
|
|
3989
|
-
var creationSourceSchema = import_zod6.z.discriminatedUnion("type", [
|
|
3990
|
-
creationSourceBlankSchema,
|
|
3991
|
-
creationSourceBlueprintSchema,
|
|
3992
|
-
creationSourceModulesSchema
|
|
3993
|
-
]);
|
|
3994
|
-
var moduleCompositionStatusSchema = import_zod6.z.enum([
|
|
3995
|
-
"pending",
|
|
3996
|
-
"installing",
|
|
3997
|
-
"ready",
|
|
3998
|
-
"partial",
|
|
3999
|
-
"failed"
|
|
4000
|
-
]);
|
|
4001
|
-
var resolvedModuleSnapshotSchema = moduleRefSchema.extend({
|
|
4002
|
-
version: import_zod6.z.string().min(1),
|
|
4003
|
-
homeCommitSha: import_zod6.z.string().min(1)
|
|
4004
|
-
});
|
|
4005
|
-
var moduleInstallHandoffSchema = import_zod6.z.object({
|
|
4006
|
-
moduleName: import_zod6.z.string().min(1),
|
|
4007
|
-
homeProjectId: import_zod6.z.string().uuid(),
|
|
4008
|
-
installedVersion: import_zod6.z.string().min(1),
|
|
4009
|
-
baselineSha: import_zod6.z.string().min(1),
|
|
4010
|
-
mounts: import_zod6.z.array(
|
|
4011
|
-
import_zod6.z.object({
|
|
4012
|
-
group: import_zod6.z.string(),
|
|
4013
|
-
segment: import_zod6.z.string()
|
|
4014
|
-
})
|
|
4015
|
-
),
|
|
4016
|
-
integration: import_zod6.z.array(import_zod6.z.string()),
|
|
4017
|
-
workflows: import_zod6.z.array(import_zod6.z.string()),
|
|
4018
|
-
prunedEnhancements: import_zod6.z.array(import_zod6.z.string()),
|
|
4019
|
-
restoredEnhancements: import_zod6.z.array(import_zod6.z.string()).optional(),
|
|
4020
|
-
permissions: import_zod6.z.array(
|
|
4021
|
-
import_zod6.z.object({
|
|
4022
|
-
key: import_zod6.z.string().min(1),
|
|
4023
|
-
description: import_zod6.z.string().min(1)
|
|
4024
|
-
})
|
|
4025
|
-
).optional()
|
|
4026
|
-
});
|
|
4027
|
-
var moduleCompositionRunAttemptSchema = import_zod6.z.object({
|
|
4028
|
-
module: moduleRefSchema,
|
|
4029
|
-
runId: import_zod6.z.string().min(1),
|
|
4030
|
-
status: import_zod6.z.string().min(1),
|
|
4031
|
-
schemaApplied: import_zod6.z.boolean(),
|
|
4032
|
-
error: import_zod6.z.string().optional(),
|
|
4033
|
-
/** Prefer full ModuleInstallHandoff; identity fields optional for older rows. */
|
|
4034
|
-
handoff: moduleInstallHandoffSchema.partial({ moduleName: true, homeProjectId: true }).optional()
|
|
4035
|
-
});
|
|
4036
|
-
var initialModuleCompositionSchema = import_zod6.z.object({
|
|
4037
|
-
version: import_zod6.z.literal(1),
|
|
4038
|
-
status: moduleCompositionStatusSchema,
|
|
4039
|
-
requested: import_zod6.z.array(moduleRefSchema).min(1),
|
|
4040
|
-
resolved: import_zod6.z.array(resolvedModuleSnapshotSchema),
|
|
4041
|
-
installOrder: import_zod6.z.array(moduleRefSchema),
|
|
4042
|
-
runs: import_zod6.z.array(moduleCompositionRunAttemptSchema).default([]),
|
|
4043
|
-
/**
|
|
4044
|
-
* True when any failed/partial attempt applied schema before failing.
|
|
4045
|
-
* Never claim transactional rollback across git/schema.
|
|
4046
|
-
*/
|
|
4047
|
-
schemaApplied: import_zod6.z.boolean().default(false),
|
|
4048
|
-
/**
|
|
4049
|
-
* Present when retry would silently change pinned home SHAs — caller must
|
|
4050
|
-
* reconfirm the new plan rather than continue.
|
|
4051
|
-
*/
|
|
4052
|
-
reconfirmationRequired: import_zod6.z.object({
|
|
4053
|
-
reason: import_zod6.z.string(),
|
|
4054
|
-
previous: import_zod6.z.array(resolvedModuleSnapshotSchema),
|
|
4055
|
-
next: import_zod6.z.array(resolvedModuleSnapshotSchema)
|
|
4056
|
-
}).optional(),
|
|
4057
|
-
error: import_zod6.z.string().optional(),
|
|
4058
|
-
/**
|
|
4059
|
-
* First-turn system handoff lifecycle. Absent/`pending` means inject when
|
|
4060
|
-
* status is ready; `consumed` means a successful agent turn already used it.
|
|
4061
|
-
*/
|
|
4062
|
-
systemHandoffStatus: import_zod6.z.enum(["pending", "consumed"]).optional(),
|
|
4063
|
-
/**
|
|
4064
|
-
* CAS ownership token for concurrent retry attempts. Only the attempt that
|
|
4065
|
-
* currently owns this value may reset or compensate durable composition state.
|
|
4066
|
-
*/
|
|
4067
|
-
retryAttemptId: import_zod6.z.string().min(1).optional(),
|
|
4068
|
-
/**
|
|
4069
|
-
* When the current `installing` attempt was claimed. A setup run that dies
|
|
4070
|
-
* with its host (a killed Vercel instance) leaves the composition `installing`
|
|
4071
|
-
* forever, and the creating page's poll only stops on ready/partial/failed —
|
|
4072
|
-
* so staleness, not a queue's stall detector, is what settles it. Absent on
|
|
4073
|
-
* rows claimed before this field existed; treated as stale.
|
|
4074
|
-
*/
|
|
4075
|
-
attemptStartedAt: import_zod6.z.string().datetime().optional(),
|
|
4076
|
-
/** Outside-module files the agent changed during connection work. */
|
|
4077
|
-
integrationFiles: import_zod6.z.array(import_zod6.z.string()).optional(),
|
|
4078
|
-
verificationSummary: import_zod6.z.string().optional()
|
|
4079
|
-
});
|
|
4080
|
-
var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
|
|
4081
|
-
|
|
4082
|
-
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
4083
|
-
var import_zod7 = require("zod");
|
|
4084
|
-
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
4085
|
-
var composeInputsSchema = import_zod7.z.strictObject({
|
|
4086
|
-
connectedStores: import_zod7.z.array(
|
|
4087
|
-
import_zod7.z.strictObject({
|
|
4088
|
-
storeId: import_zod7.z.string().min(1),
|
|
4089
|
-
slug: import_zod7.z.string().min(1),
|
|
4090
|
-
// A row with an empty binding key must not fail a deploy: the rail
|
|
4091
|
-
// treats falsy as "no binding key", so normalise rather than reject.
|
|
4092
|
-
bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
|
|
4093
|
-
accessLevel: import_zod7.z.enum(["read", "write", "admin"]),
|
|
4094
|
-
// Defaulted so a file written before the platform emitted store types
|
|
4095
|
-
// (database rows only) still parses. The platform starts writing
|
|
4096
|
-
// storage rows, with `type` set, once forks run a compose that reads it.
|
|
4097
|
-
type: import_zod7.z.enum(["database", "storage"]).default("database")
|
|
4098
|
-
})
|
|
4099
|
-
).default([]),
|
|
4100
|
-
installs: import_zod7.z.record(
|
|
4101
|
-
import_zod7.z.string(),
|
|
4102
|
-
import_zod7.z.strictObject({
|
|
4103
|
-
installedVersion: import_zod7.z.string().min(1),
|
|
4104
|
-
datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
|
|
4105
|
-
})
|
|
4106
|
-
).default({})
|
|
4107
|
-
});
|
|
4108
|
-
var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
4109
|
-
var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
4110
|
-
function escapeStubIgnorePattern(pattern) {
|
|
4111
|
-
const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
|
|
4112
|
-
return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
|
|
4113
|
-
}
|
|
4114
|
-
function buildStubIgnore(artifactPaths) {
|
|
4115
|
-
const prefix = "apps/web/src/app/";
|
|
4116
|
-
const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
|
|
4117
|
-
return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
4118
|
-
}
|
|
4119
|
-
|
|
4120
|
-
// ../../packages/lib/src/shared/module-enablement.ts
|
|
4121
|
-
function isModuleEnabledByList(raw, name, kindOf) {
|
|
4122
|
-
if (!raw) return true;
|
|
4123
|
-
const kind = kindOf(name);
|
|
4124
|
-
if (kind != null && kind !== "feature") return true;
|
|
4125
|
-
return raw.split(",").includes(name);
|
|
4126
|
-
}
|
|
4127
|
-
|
|
4128
4012
|
// ../../packages/lib/src/shared/platform-owned-paths.ts
|
|
4129
4013
|
var APP_WEB_PREFIX = "apps/web/";
|
|
4130
4014
|
var MODULE_STUB_MARKER = "@stardeck-module-rail-generated";
|
|
@@ -4271,13 +4155,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4271
4155
|
function createMessageTree() {
|
|
4272
4156
|
return /* @__PURE__ */ Object.create(null);
|
|
4273
4157
|
}
|
|
4274
|
-
function assertMessageTree(value,
|
|
4158
|
+
function assertMessageTree(value, path4) {
|
|
4275
4159
|
if (typeof value === "string") return;
|
|
4276
4160
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4277
|
-
throw new Error(`Invalid message at ${
|
|
4161
|
+
throw new Error(`Invalid message at ${path4}: expected string or nested object`);
|
|
4278
4162
|
}
|
|
4279
4163
|
for (const [key, child] of Object.entries(value)) {
|
|
4280
|
-
assertMessageTree(child, `${
|
|
4164
|
+
assertMessageTree(child, `${path4}.${key}`);
|
|
4281
4165
|
}
|
|
4282
4166
|
}
|
|
4283
4167
|
function cloneTree(value) {
|
|
@@ -4287,17 +4171,17 @@ function cloneTree(value) {
|
|
|
4287
4171
|
}
|
|
4288
4172
|
return clone;
|
|
4289
4173
|
}
|
|
4290
|
-
function collectLeafPaths(value,
|
|
4174
|
+
function collectLeafPaths(value, path4, out) {
|
|
4291
4175
|
if (typeof value === "string") {
|
|
4292
|
-
out.push(
|
|
4176
|
+
out.push(path4);
|
|
4293
4177
|
return;
|
|
4294
4178
|
}
|
|
4295
|
-
assertMessageTree(value,
|
|
4296
|
-
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${
|
|
4179
|
+
assertMessageTree(value, path4);
|
|
4180
|
+
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
|
|
4297
4181
|
}
|
|
4298
|
-
function overrideInto(target, source, owner,
|
|
4182
|
+
function overrideInto(target, source, owner, path4, missing) {
|
|
4299
4183
|
for (const [key, value] of Object.entries(source)) {
|
|
4300
|
-
const nextPath =
|
|
4184
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4301
4185
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4302
4186
|
collectLeafPaths(value, nextPath, missing);
|
|
4303
4187
|
continue;
|
|
@@ -4324,9 +4208,9 @@ function overrideInto(target, source, owner, path3, missing) {
|
|
|
4324
4208
|
function composeLocaleMessagesForPlan(layers) {
|
|
4325
4209
|
const result = createMessageTree();
|
|
4326
4210
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4327
|
-
function mergeInto(target, source, owner,
|
|
4211
|
+
function mergeInto(target, source, owner, path4) {
|
|
4328
4212
|
for (const [key, value] of Object.entries(source)) {
|
|
4329
|
-
const nextPath =
|
|
4213
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4330
4214
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4331
4215
|
const existing = hasExisting ? target[key] : void 0;
|
|
4332
4216
|
if (typeof value === "string") {
|
|
@@ -4870,8 +4754,8 @@ var METHOD_ORDER = {
|
|
|
4870
4754
|
function compareNames2(a, b) {
|
|
4871
4755
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4872
4756
|
}
|
|
4873
|
-
function endpointPairKey(method,
|
|
4874
|
-
return `${method} ${
|
|
4757
|
+
function endpointPairKey(method, path4) {
|
|
4758
|
+
return `${method} ${path4}`;
|
|
4875
4759
|
}
|
|
4876
4760
|
function apiPathToRouteFile(apiPath) {
|
|
4877
4761
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4885,11 +4769,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4885
4769
|
}
|
|
4886
4770
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4887
4771
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4888
|
-
function appRouteFileToNextRelativePath(
|
|
4889
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
4772
|
+
function appRouteFileToNextRelativePath(path4) {
|
|
4773
|
+
const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
4890
4774
|
if (pagePath != null) return pagePath;
|
|
4891
|
-
if (!
|
|
4892
|
-
const relative =
|
|
4775
|
+
if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
4776
|
+
const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4893
4777
|
if (!relative.startsWith("api/")) return null;
|
|
4894
4778
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4895
4779
|
if (!suffix) return null;
|
|
@@ -4981,9 +4865,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4981
4865
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4982
4866
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4983
4867
|
}
|
|
4984
|
-
function parseLegacyApiAdapter(
|
|
4985
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4986
|
-
if (expectedMarkerKindForPath(
|
|
4868
|
+
function parseLegacyApiAdapter(path4, source) {
|
|
4869
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
4870
|
+
if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
|
|
4987
4871
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4988
4872
|
const match = firstLine.match(
|
|
4989
4873
|
new RegExp(
|
|
@@ -4993,7 +4877,7 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4993
4877
|
if (!match || match[2] !== physicalPath) return null;
|
|
4994
4878
|
const owner = match[1];
|
|
4995
4879
|
if (!isValidModuleName(owner)) return null;
|
|
4996
|
-
const file = import_typescript4.default.createSourceFile(
|
|
4880
|
+
const file = import_typescript4.default.createSourceFile(path4, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
|
|
4997
4881
|
if (file.parseDiagnostics.length > 0) {
|
|
4998
4882
|
return null;
|
|
4999
4883
|
}
|
|
@@ -5035,9 +4919,9 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
5035
4919
|
}
|
|
5036
4920
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
5037
4921
|
}
|
|
5038
|
-
function parseLegacyRouteAdapter(
|
|
5039
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
5040
|
-
if (expectedMarkerKindForPath(
|
|
4922
|
+
function parseLegacyRouteAdapter(path4, source) {
|
|
4923
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
4924
|
+
if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
|
|
5041
4925
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
5042
4926
|
const match = normalized.match(
|
|
5043
4927
|
new RegExp(
|
|
@@ -5049,30 +4933,30 @@ function parseLegacyRouteAdapter(path3, source) {
|
|
|
5049
4933
|
const nextRelativePath = match[2];
|
|
5050
4934
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
5051
4935
|
}
|
|
5052
|
-
function legacyRouteAdapterMatchesStub(
|
|
5053
|
-
const legacy = parseLegacyRouteAdapter(
|
|
4936
|
+
function legacyRouteAdapterMatchesStub(path4, source, stub) {
|
|
4937
|
+
const legacy = parseLegacyRouteAdapter(path4, source);
|
|
5054
4938
|
return legacy != null && legacy.owner === stub.owner;
|
|
5055
4939
|
}
|
|
5056
4940
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
5057
4941
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
5058
4942
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
5059
|
-
function expectedMarkerKindForPath(
|
|
5060
|
-
if (
|
|
5061
|
-
if (
|
|
5062
|
-
if (
|
|
5063
|
-
if (
|
|
5064
|
-
if (
|
|
5065
|
-
if (
|
|
5066
|
-
if (
|
|
5067
|
-
if (
|
|
4943
|
+
function expectedMarkerKindForPath(path4) {
|
|
4944
|
+
if (path4 === MODULES_GEN_PATH) return "registry";
|
|
4945
|
+
if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
|
|
4946
|
+
if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
|
|
4947
|
+
if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
|
|
4948
|
+
if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
|
|
4949
|
+
if (path4 === MODULE_SERVER_GEN_PATH) return "server";
|
|
4950
|
+
if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
|
|
4951
|
+
if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
|
|
5068
4952
|
return "route";
|
|
5069
4953
|
}
|
|
5070
4954
|
return null;
|
|
5071
4955
|
}
|
|
5072
|
-
function markerCongruentWithPath(
|
|
5073
|
-
if (expectedMarkerKindForPath(
|
|
4956
|
+
function markerCongruentWithPath(path4, marker) {
|
|
4957
|
+
if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
|
|
5074
4958
|
if (marker.kind === "route") {
|
|
5075
|
-
const derived = appPageFileToNextRelativePath(
|
|
4959
|
+
const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
5076
4960
|
return derived != null && derived === marker.nextRelativePath;
|
|
5077
4961
|
}
|
|
5078
4962
|
return true;
|
|
@@ -5343,8 +5227,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5343
5227
|
function activeEnhancementPeers(registryModule) {
|
|
5344
5228
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5345
5229
|
}
|
|
5346
|
-
function endpointBucketKey(
|
|
5347
|
-
return `${
|
|
5230
|
+
function endpointBucketKey(path4, owner, scope) {
|
|
5231
|
+
return `${path4}\0${owner}\0${scopeLabel(scope)}`;
|
|
5348
5232
|
}
|
|
5349
5233
|
function planRouteStubs(input) {
|
|
5350
5234
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5468,7 +5352,7 @@ function planRouteStubs(input) {
|
|
|
5468
5352
|
for (const entry of entries) {
|
|
5469
5353
|
const nextRelativePath = entry.nextRelativePath;
|
|
5470
5354
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5471
|
-
const
|
|
5355
|
+
const path4 = routeEntryToAppPageFile(nextRelativePath);
|
|
5472
5356
|
const existing = routeDestinationOwners.get(destination);
|
|
5473
5357
|
if (existing) {
|
|
5474
5358
|
throw new Error(
|
|
@@ -5478,13 +5362,13 @@ function planRouteStubs(input) {
|
|
|
5478
5362
|
routeDestinationOwners.set(destination, {
|
|
5479
5363
|
owner: regMod.name,
|
|
5480
5364
|
nextRelativePath,
|
|
5481
|
-
path:
|
|
5365
|
+
path: path4
|
|
5482
5366
|
});
|
|
5483
5367
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5484
5368
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5485
5369
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5486
5370
|
routeStubs.push({
|
|
5487
|
-
path:
|
|
5371
|
+
path: path4,
|
|
5488
5372
|
owner: regMod.name,
|
|
5489
5373
|
nextRelativePath,
|
|
5490
5374
|
importModule,
|
|
@@ -5545,16 +5429,16 @@ function planCompositionArtifacts(input) {
|
|
|
5545
5429
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5546
5430
|
]);
|
|
5547
5431
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5548
|
-
const isProvenStaleDeletionCandidate = (
|
|
5549
|
-
if (
|
|
5550
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5551
|
-
if (desiredPaths.has(
|
|
5552
|
-
if (!existingGeneratedPathSet.has(
|
|
5553
|
-
const legacy = parseLegacyApiAdapter(
|
|
5432
|
+
const isProvenStaleDeletionCandidate = (path4, content) => {
|
|
5433
|
+
if (path4 === MODULES_GEN_PATH) return false;
|
|
5434
|
+
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
|
|
5435
|
+
if (desiredPaths.has(path4)) return false;
|
|
5436
|
+
if (!existingGeneratedPathSet.has(path4)) return false;
|
|
5437
|
+
const legacy = parseLegacyApiAdapter(path4, content);
|
|
5554
5438
|
if (legacy) return true;
|
|
5555
|
-
if (parseLegacyRouteAdapter(
|
|
5439
|
+
if (parseLegacyRouteAdapter(path4, content)) return true;
|
|
5556
5440
|
const marker = parseCompositionArtifactMarker(content);
|
|
5557
|
-
return marker != null && markerCongruentWithPath(
|
|
5441
|
+
return marker != null && markerCongruentWithPath(path4, marker);
|
|
5558
5442
|
};
|
|
5559
5443
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5560
5444
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5708,13 +5592,13 @@ function planCompositionArtifacts(input) {
|
|
|
5708
5592
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5709
5593
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5710
5594
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5711
|
-
for (const [
|
|
5712
|
-
const existing = input.existingFiles[
|
|
5595
|
+
for (const [path4] of desired) {
|
|
5596
|
+
const existing = input.existingFiles[path4];
|
|
5713
5597
|
if (existing == null) continue;
|
|
5714
5598
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5715
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5716
|
-
const plannedRoute = routeStubByPath.get(
|
|
5717
|
-
const legacy = parseLegacyApiAdapter(
|
|
5599
|
+
const plannedEndpoint = endpointStubByPath.get(path4);
|
|
5600
|
+
const plannedRoute = routeStubByPath.get(path4);
|
|
5601
|
+
const legacy = parseLegacyApiAdapter(path4, existing);
|
|
5718
5602
|
if (legacy && plannedEndpoint) {
|
|
5719
5603
|
marker = {
|
|
5720
5604
|
kind: "endpoint",
|
|
@@ -5722,66 +5606,66 @@ function planCompositionArtifacts(input) {
|
|
|
5722
5606
|
scope: plannedEndpoint.scope
|
|
5723
5607
|
};
|
|
5724
5608
|
}
|
|
5725
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5609
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
|
|
5726
5610
|
marker = {
|
|
5727
5611
|
kind: "route",
|
|
5728
5612
|
owner: plannedRoute.owner,
|
|
5729
5613
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5730
5614
|
};
|
|
5731
5615
|
}
|
|
5732
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5616
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
|
|
5733
5617
|
if (!marker) {
|
|
5734
5618
|
return {
|
|
5735
5619
|
ok: false,
|
|
5736
|
-
error:
|
|
5737
|
-
path:
|
|
5620
|
+
error: path4 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path4}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path4}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path4}`,
|
|
5621
|
+
path: path4
|
|
5738
5622
|
};
|
|
5739
5623
|
}
|
|
5740
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5624
|
+
const expectedKind = expectedMarkerKindForPath(path4);
|
|
5741
5625
|
if (marker.kind !== expectedKind) {
|
|
5742
5626
|
return {
|
|
5743
5627
|
ok: false,
|
|
5744
|
-
error:
|
|
5745
|
-
path:
|
|
5628
|
+
error: path4 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path4} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path4} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
|
|
5629
|
+
path: path4
|
|
5746
5630
|
};
|
|
5747
5631
|
}
|
|
5748
5632
|
if (marker.kind === "endpoint") {
|
|
5749
|
-
const stub = endpointStubByPath.get(
|
|
5633
|
+
const stub = endpointStubByPath.get(path4);
|
|
5750
5634
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5751
5635
|
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5752
5636
|
if (!ownerMatches || !scopeMatches) {
|
|
5753
5637
|
return {
|
|
5754
5638
|
ok: false,
|
|
5755
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5756
|
-
path:
|
|
5639
|
+
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
|
|
5640
|
+
path: path4,
|
|
5757
5641
|
owner: stub?.owner
|
|
5758
5642
|
};
|
|
5759
5643
|
}
|
|
5760
5644
|
} else if (marker.kind === "route") {
|
|
5761
|
-
const stub = routeStubByPath.get(
|
|
5645
|
+
const stub = routeStubByPath.get(path4);
|
|
5762
5646
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5763
5647
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5764
5648
|
if (!ownerMatches || !pathMatches) {
|
|
5765
5649
|
return {
|
|
5766
5650
|
ok: false,
|
|
5767
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5768
|
-
path:
|
|
5651
|
+
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
|
|
5652
|
+
path: path4,
|
|
5769
5653
|
owner: stub?.owner
|
|
5770
5654
|
};
|
|
5771
5655
|
}
|
|
5772
5656
|
}
|
|
5773
5657
|
}
|
|
5774
5658
|
const deletes = [];
|
|
5775
|
-
for (const
|
|
5776
|
-
const existing = input.existingFiles[
|
|
5659
|
+
for (const path4 of input.existingGeneratedPaths) {
|
|
5660
|
+
const existing = input.existingFiles[path4];
|
|
5777
5661
|
if (existing == null) continue;
|
|
5778
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5662
|
+
if (!isProvenStaleDeletionCandidate(path4, existing)) {
|
|
5779
5663
|
continue;
|
|
5780
5664
|
}
|
|
5781
|
-
deletes.push(
|
|
5665
|
+
deletes.push(path4);
|
|
5782
5666
|
}
|
|
5783
5667
|
deletes.sort(compareNames2);
|
|
5784
|
-
const writes = [...desired.entries()].filter(([
|
|
5668
|
+
const writes = [...desired.entries()].filter(([path4, content]) => input.existingFiles[path4] !== content).map(([path4, content]) => ({ path: path4, content })).sort((a, b) => compareNames2(a.path, b.path));
|
|
5785
5669
|
return {
|
|
5786
5670
|
ok: true,
|
|
5787
5671
|
plan: {
|
|
@@ -5810,8 +5694,16 @@ async function checkoutParsesTypedStores(sandbox) {
|
|
|
5810
5694
|
try {
|
|
5811
5695
|
if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
|
|
5812
5696
|
const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
|
|
5813
|
-
|
|
5814
|
-
|
|
5697
|
+
return composeRangeParsesTypedStores(
|
|
5698
|
+
pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
|
|
5699
|
+
);
|
|
5700
|
+
} catch {
|
|
5701
|
+
return false;
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
function composeRangeParsesTypedStores(range) {
|
|
5705
|
+
if (!range) return false;
|
|
5706
|
+
try {
|
|
5815
5707
|
const lowest = import_semver.default.minVersion(range);
|
|
5816
5708
|
return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
|
|
5817
5709
|
} catch {
|
|
@@ -6043,6 +5935,50 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
|
|
|
6043
5935
|
return dirs;
|
|
6044
5936
|
}
|
|
6045
5937
|
|
|
5938
|
+
// ../../packages/lib/src/shared/module-composition.ts
|
|
5939
|
+
var import_zod7 = require("zod");
|
|
5940
|
+
var creationSourceBlankSchema = import_zod7.z.object({
|
|
5941
|
+
type: import_zod7.z.literal("blank")
|
|
5942
|
+
});
|
|
5943
|
+
var creationSourceBlueprintSchema = import_zod7.z.object({
|
|
5944
|
+
type: import_zod7.z.literal("blueprint"),
|
|
5945
|
+
/** Internal template/fork project id — keep template naming in identifiers. */
|
|
5946
|
+
blueprintProjectId: import_zod7.z.string().uuid(),
|
|
5947
|
+
/**
|
|
5948
|
+
* `null`/absent means every feature Module. `[]` is refused: the platform
|
|
5949
|
+
* serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
|
|
5950
|
+
* enablement parser reads back as "all" (`module-enablement.ts`) — the
|
|
5951
|
+
* opposite of what an empty selection means.
|
|
5952
|
+
*/
|
|
5953
|
+
enabledModules: import_zod7.z.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
|
|
5954
|
+
});
|
|
5955
|
+
var creationSourceSchema = import_zod7.z.discriminatedUnion("type", [
|
|
5956
|
+
creationSourceBlankSchema,
|
|
5957
|
+
creationSourceBlueprintSchema
|
|
5958
|
+
]);
|
|
5959
|
+
var moduleInstallHandoffSchema = import_zod7.z.object({
|
|
5960
|
+
moduleName: import_zod7.z.string().min(1),
|
|
5961
|
+
homeProjectId: import_zod7.z.string().uuid(),
|
|
5962
|
+
installedVersion: import_zod7.z.string().min(1),
|
|
5963
|
+
baselineSha: import_zod7.z.string().min(1),
|
|
5964
|
+
mounts: import_zod7.z.array(
|
|
5965
|
+
import_zod7.z.object({
|
|
5966
|
+
group: import_zod7.z.string(),
|
|
5967
|
+
segment: import_zod7.z.string()
|
|
5968
|
+
})
|
|
5969
|
+
),
|
|
5970
|
+
integration: import_zod7.z.array(import_zod7.z.string()),
|
|
5971
|
+
workflows: import_zod7.z.array(import_zod7.z.string()),
|
|
5972
|
+
prunedEnhancements: import_zod7.z.array(import_zod7.z.string()),
|
|
5973
|
+
restoredEnhancements: import_zod7.z.array(import_zod7.z.string()).optional(),
|
|
5974
|
+
permissions: import_zod7.z.array(
|
|
5975
|
+
import_zod7.z.object({
|
|
5976
|
+
key: import_zod7.z.string().min(1),
|
|
5977
|
+
description: import_zod7.z.string().min(1)
|
|
5978
|
+
})
|
|
5979
|
+
).optional()
|
|
5980
|
+
});
|
|
5981
|
+
|
|
6046
5982
|
// ../../packages/lib/src/shared/schema-ops.ts
|
|
6047
5983
|
var import_zod8 = require("zod");
|
|
6048
5984
|
var columnSpecSchema = import_zod8.z.object({
|
|
@@ -6256,11 +6192,11 @@ function countedCompositionContext(context) {
|
|
|
6256
6192
|
counts.sandboxExecs += 1;
|
|
6257
6193
|
return context.sandbox.exec(...args);
|
|
6258
6194
|
},
|
|
6259
|
-
readFile: (
|
|
6195
|
+
readFile: (path4) => {
|
|
6260
6196
|
counts.sandboxReads += 1;
|
|
6261
|
-
return context.sandbox.readFile(
|
|
6197
|
+
return context.sandbox.readFile(path4);
|
|
6262
6198
|
},
|
|
6263
|
-
fileExists: (
|
|
6199
|
+
fileExists: (path4) => context.sandbox.fileExists(path4),
|
|
6264
6200
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6265
6201
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6266
6202
|
}
|
|
@@ -6472,14 +6408,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6472
6408
|
}
|
|
6473
6409
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6474
6410
|
}
|
|
6475
|
-
async function writeSandboxFileAtomic(target,
|
|
6476
|
-
const dir = dirnamePosix(
|
|
6477
|
-
const tmp = `${
|
|
6411
|
+
async function writeSandboxFileAtomic(target, path4, content) {
|
|
6412
|
+
const dir = dirnamePosix(path4);
|
|
6413
|
+
const tmp = `${path4}.tmp.${target.runId}`;
|
|
6478
6414
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6479
6415
|
const cmd = [
|
|
6480
6416
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6481
6417
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6482
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6418
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
|
|
6483
6419
|
].join(" && ");
|
|
6484
6420
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6485
6421
|
if (result.exitCode !== 0) {
|
|
@@ -6494,14 +6430,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
|
|
|
6494
6430
|
}
|
|
6495
6431
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6496
6432
|
throw new Error(
|
|
6497
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6433
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6498
6434
|
);
|
|
6499
6435
|
}
|
|
6500
6436
|
}
|
|
6501
6437
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6502
6438
|
const absent = /* @__PURE__ */ new Set();
|
|
6503
|
-
for (const
|
|
6504
|
-
let dir = dirnamePosix(
|
|
6439
|
+
for (const path4 of writePaths) {
|
|
6440
|
+
let dir = dirnamePosix(path4);
|
|
6505
6441
|
while (dir !== "." && dir !== "") {
|
|
6506
6442
|
if (absent.has(dir)) {
|
|
6507
6443
|
dir = dirnamePosix(dir);
|
|
@@ -6538,12 +6474,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6538
6474
|
);
|
|
6539
6475
|
}
|
|
6540
6476
|
}
|
|
6541
|
-
async function deleteSandboxFile(context,
|
|
6542
|
-
const result = await context.sandbox.exec(`rm -f ${shellQuote(
|
|
6477
|
+
async function deleteSandboxFile(context, path4) {
|
|
6478
|
+
const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
|
|
6543
6479
|
if (result.exitCode !== 0) {
|
|
6544
6480
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6545
6481
|
throw new Error(
|
|
6546
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6482
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6547
6483
|
);
|
|
6548
6484
|
}
|
|
6549
6485
|
}
|
|
@@ -6610,37 +6546,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6610
6546
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6611
6547
|
appliedWrites.push({ path: write.path, previous });
|
|
6612
6548
|
}
|
|
6613
|
-
for (const
|
|
6614
|
-
const previous = existingFiles[
|
|
6549
|
+
for (const path4 of plan.deletes) {
|
|
6550
|
+
const previous = existingFiles[path4];
|
|
6615
6551
|
if (previous == null) continue;
|
|
6616
|
-
await deleteSandboxFile(context,
|
|
6617
|
-
appliedDeletes.push({ path:
|
|
6552
|
+
await deleteSandboxFile(context, path4);
|
|
6553
|
+
appliedDeletes.push({ path: path4, previous });
|
|
6618
6554
|
}
|
|
6619
6555
|
} catch (error) {
|
|
6620
6556
|
console.log(
|
|
6621
6557
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6622
6558
|
);
|
|
6623
|
-
for (const { path:
|
|
6559
|
+
for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
|
|
6624
6560
|
try {
|
|
6625
|
-
await writeSandboxFileAtomic(context,
|
|
6561
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6626
6562
|
} catch (rollbackError) {
|
|
6627
6563
|
console.log(
|
|
6628
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(
|
|
6564
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6629
6565
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6630
6566
|
)}`
|
|
6631
6567
|
);
|
|
6632
6568
|
}
|
|
6633
6569
|
}
|
|
6634
|
-
for (const { path:
|
|
6570
|
+
for (const { path: path4, previous } of [...appliedWrites].reverse()) {
|
|
6635
6571
|
try {
|
|
6636
6572
|
if (previous == null) {
|
|
6637
|
-
await deleteSandboxFile(context,
|
|
6573
|
+
await deleteSandboxFile(context, path4);
|
|
6638
6574
|
} else {
|
|
6639
|
-
await writeSandboxFileAtomic(context,
|
|
6575
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6640
6576
|
}
|
|
6641
6577
|
} catch (rollbackError) {
|
|
6642
6578
|
console.log(
|
|
6643
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(
|
|
6579
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6644
6580
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6645
6581
|
)}`
|
|
6646
6582
|
);
|
|
@@ -6674,39 +6610,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6674
6610
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6675
6611
|
);
|
|
6676
6612
|
const presentPaths = [];
|
|
6677
|
-
for (const
|
|
6613
|
+
for (const path4 of candidates) {
|
|
6678
6614
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6679
|
-
|
|
6680
|
-
kinds.get(
|
|
6615
|
+
path4,
|
|
6616
|
+
kinds.get(path4) ?? "unreadable"
|
|
6681
6617
|
);
|
|
6682
|
-
if (presence === "file") presentPaths.push(
|
|
6618
|
+
if (presence === "file") presentPaths.push(path4);
|
|
6683
6619
|
}
|
|
6684
6620
|
let contents;
|
|
6685
6621
|
try {
|
|
6686
6622
|
contents = await readFilesMany(context, presentPaths);
|
|
6687
6623
|
} catch (error) {
|
|
6688
|
-
const
|
|
6624
|
+
const path4 = readFailurePath(error);
|
|
6689
6625
|
throw new Error(
|
|
6690
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6626
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
|
|
6691
6627
|
error instanceof Error ? error.message : String(error)
|
|
6692
6628
|
)}`
|
|
6693
6629
|
);
|
|
6694
6630
|
}
|
|
6695
6631
|
const owned = [];
|
|
6696
|
-
for (const
|
|
6697
|
-
const content = contents.get(
|
|
6632
|
+
for (const path4 of presentPaths) {
|
|
6633
|
+
const content = contents.get(path4);
|
|
6698
6634
|
const marker = parseCompositionArtifactMarker(content);
|
|
6699
|
-
if (parseLegacyApiAdapter(
|
|
6700
|
-
owned.push(
|
|
6635
|
+
if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
|
|
6636
|
+
owned.push(path4);
|
|
6701
6637
|
continue;
|
|
6702
6638
|
}
|
|
6703
6639
|
if (marker == null) continue;
|
|
6704
|
-
if (!markerCongruentWithPath(
|
|
6640
|
+
if (!markerCongruentWithPath(path4, marker)) {
|
|
6705
6641
|
throw new Error(
|
|
6706
|
-
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(
|
|
6642
|
+
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path4)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path4) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
|
|
6707
6643
|
);
|
|
6708
6644
|
}
|
|
6709
|
-
owned.push(
|
|
6645
|
+
owned.push(path4);
|
|
6710
6646
|
}
|
|
6711
6647
|
return owned;
|
|
6712
6648
|
}
|
|
@@ -6839,7 +6775,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6839
6775
|
{ raiseOnError: false }
|
|
6840
6776
|
);
|
|
6841
6777
|
if (result.exitCode !== 0) {
|
|
6842
|
-
for (const
|
|
6778
|
+
for (const path4 of chunk) kinds.set(path4, "unreadable");
|
|
6843
6779
|
continue;
|
|
6844
6780
|
}
|
|
6845
6781
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6850,31 +6786,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6850
6786
|
}
|
|
6851
6787
|
} else {
|
|
6852
6788
|
for (const line of lines) {
|
|
6853
|
-
const [rawKind,
|
|
6789
|
+
const [rawKind, path4] = line.split(" ", 2);
|
|
6854
6790
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6855
|
-
if (
|
|
6856
|
-
kinds.set(
|
|
6791
|
+
if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
6792
|
+
kinds.set(path4, kind);
|
|
6857
6793
|
}
|
|
6858
6794
|
}
|
|
6859
6795
|
}
|
|
6860
|
-
for (const
|
|
6861
|
-
if (!kinds.has(
|
|
6796
|
+
for (const path4 of chunk) {
|
|
6797
|
+
if (!kinds.has(path4)) kinds.set(path4, "unreadable");
|
|
6862
6798
|
}
|
|
6863
6799
|
}
|
|
6864
6800
|
return kinds;
|
|
6865
6801
|
}
|
|
6866
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6867
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
6802
|
+
async function classifySandboxPathPresenceKind(context, path4, probeToken) {
|
|
6803
|
+
return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
|
|
6868
6804
|
}
|
|
6869
6805
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6870
|
-
function assertRealLocaleJsonFilePresence(
|
|
6806
|
+
function assertRealLocaleJsonFilePresence(path4, presence) {
|
|
6871
6807
|
if (presence === "file") return;
|
|
6872
6808
|
if (presence === "absent") {
|
|
6873
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
6809
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
|
|
6874
6810
|
}
|
|
6875
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6811
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6876
6812
|
throw new Error(
|
|
6877
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6813
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
|
|
6878
6814
|
);
|
|
6879
6815
|
}
|
|
6880
6816
|
function splitTaggedLine(line) {
|
|
@@ -6949,11 +6885,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6949
6885
|
}
|
|
6950
6886
|
async function readFilesMany(context, paths) {
|
|
6951
6887
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6952
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
6888
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
|
|
6953
6889
|
try {
|
|
6954
|
-
return { path:
|
|
6890
|
+
return { path: path4, value: await context.sandbox.readFile(path4) };
|
|
6955
6891
|
} catch (error) {
|
|
6956
|
-
return { path:
|
|
6892
|
+
return { path: path4, error };
|
|
6957
6893
|
}
|
|
6958
6894
|
});
|
|
6959
6895
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6976,58 +6912,58 @@ function readFailurePath(error) {
|
|
|
6976
6912
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6977
6913
|
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6978
6914
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6979
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6980
|
-
return classifySandboxPathPresenceKind(context,
|
|
6915
|
+
async function classifyCompositionArtifactPathKind(context, path4) {
|
|
6916
|
+
return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6981
6917
|
}
|
|
6982
|
-
function assertRealCompositionArtifactFilePresence(
|
|
6918
|
+
function assertRealCompositionArtifactFilePresence(path4, presence) {
|
|
6983
6919
|
if (presence === "file") return "file";
|
|
6984
6920
|
if (presence === "absent") return "absent";
|
|
6985
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6921
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6986
6922
|
throw new Error(
|
|
6987
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6923
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
|
|
6988
6924
|
);
|
|
6989
6925
|
}
|
|
6990
|
-
async function readConventionalEntryIfRealFile(
|
|
6991
|
-
const presence = presences.get(
|
|
6992
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6926
|
+
async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
6927
|
+
const presence = presences.get(path4) ?? "unreadable";
|
|
6928
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6993
6929
|
if (presenceError) {
|
|
6994
6930
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6995
6931
|
}
|
|
6996
6932
|
if (presence === "absent") {
|
|
6997
6933
|
return null;
|
|
6998
6934
|
}
|
|
6999
|
-
const source = files.get(
|
|
6935
|
+
const source = files.get(path4);
|
|
7000
6936
|
switch (kind) {
|
|
7001
6937
|
case "contributions":
|
|
7002
|
-
assertContributionsExport(source,
|
|
7003
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6938
|
+
assertContributionsExport(source, path4);
|
|
6939
|
+
assertClientSafeConventionalEntryImports(source, path4, "contributions");
|
|
7004
6940
|
break;
|
|
7005
6941
|
case "slotCatalogs":
|
|
7006
|
-
assertSlotCatalogsExport(source,
|
|
7007
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6942
|
+
assertSlotCatalogsExport(source, path4);
|
|
6943
|
+
assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
|
|
7008
6944
|
break;
|
|
7009
6945
|
case "initializeEnhancement":
|
|
7010
|
-
assertInitializeEnhancementExport(source,
|
|
6946
|
+
assertInitializeEnhancementExport(source, path4);
|
|
7011
6947
|
break;
|
|
7012
6948
|
}
|
|
7013
6949
|
return source;
|
|
7014
6950
|
}
|
|
7015
6951
|
function hasRealServerRegistrations(modulePath, presences) {
|
|
7016
|
-
const
|
|
7017
|
-
return assertRealCompositionArtifactFilePresence(
|
|
6952
|
+
const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
6953
|
+
return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
|
|
7018
6954
|
}
|
|
7019
|
-
async function readJsonObjectFile(
|
|
7020
|
-
const raw = files.get(
|
|
6955
|
+
async function readJsonObjectFile(path4, files) {
|
|
6956
|
+
const raw = files.get(path4);
|
|
7021
6957
|
let parsed;
|
|
7022
6958
|
try {
|
|
7023
6959
|
parsed = JSON.parse(raw);
|
|
7024
6960
|
} catch (error) {
|
|
7025
6961
|
throw new Error(
|
|
7026
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
6962
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
|
|
7027
6963
|
);
|
|
7028
6964
|
}
|
|
7029
6965
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
7030
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
6966
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
|
|
7031
6967
|
}
|
|
7032
6968
|
return parsed;
|
|
7033
6969
|
}
|
|
@@ -7076,11 +7012,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7076
7012
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7077
7013
|
]);
|
|
7078
7014
|
const presentConventionalPaths = [];
|
|
7079
|
-
for (const
|
|
7080
|
-
const presence = conventionalPresences.get(
|
|
7081
|
-
if (presence === "file") presentConventionalPaths.push(
|
|
7015
|
+
for (const path4 of conventionalPaths) {
|
|
7016
|
+
const presence = conventionalPresences.get(path4) ?? "unreadable";
|
|
7017
|
+
if (presence === "file") presentConventionalPaths.push(path4);
|
|
7082
7018
|
}
|
|
7083
|
-
const presentLocalePaths = localePaths.filter((
|
|
7019
|
+
const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
|
|
7084
7020
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7085
7021
|
const substrateLocales = [];
|
|
7086
7022
|
for (const locale of substrateNames) {
|
|
@@ -7178,8 +7114,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7178
7114
|
});
|
|
7179
7115
|
if (rootLocales.length > 0) {
|
|
7180
7116
|
for (const locale of rootLocales) {
|
|
7181
|
-
const
|
|
7182
|
-
assertRealLocaleJsonFilePresence(
|
|
7117
|
+
const path4 = `${modulePath}/i18n/${locale}.json`;
|
|
7118
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7183
7119
|
}
|
|
7184
7120
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7185
7121
|
const localeMessages = { en: enMessages };
|
|
@@ -7203,8 +7139,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7203
7139
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7204
7140
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7205
7141
|
for (const locale of enh.locales) {
|
|
7206
|
-
const
|
|
7207
|
-
assertRealLocaleJsonFilePresence(
|
|
7142
|
+
const path4 = `${peerDir}/i18n/${locale}.json`;
|
|
7143
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7208
7144
|
}
|
|
7209
7145
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7210
7146
|
const localeMessages = { en: enMessages };
|
|
@@ -7405,16 +7341,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7405
7341
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7406
7342
|
const existingFiles = {};
|
|
7407
7343
|
const remainingCandidatePaths = [];
|
|
7408
|
-
for (const
|
|
7409
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7410
|
-
existingFiles[
|
|
7344
|
+
for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
|
|
7345
|
+
if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
|
|
7346
|
+
existingFiles[path4] = existingAppPages[path4];
|
|
7411
7347
|
continue;
|
|
7412
7348
|
}
|
|
7413
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7414
|
-
existingFiles[
|
|
7349
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
|
|
7350
|
+
existingFiles[path4] = existingAppRoutes[path4];
|
|
7415
7351
|
continue;
|
|
7416
7352
|
}
|
|
7417
|
-
remainingCandidatePaths.push(
|
|
7353
|
+
remainingCandidatePaths.push(path4);
|
|
7418
7354
|
}
|
|
7419
7355
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7420
7356
|
context,
|
|
@@ -7422,12 +7358,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7422
7358
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7423
7359
|
);
|
|
7424
7360
|
const presentCandidatePaths = [];
|
|
7425
|
-
for (const
|
|
7361
|
+
for (const path4 of remainingCandidatePaths) {
|
|
7426
7362
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7427
|
-
|
|
7428
|
-
candidateKinds.get(
|
|
7363
|
+
path4,
|
|
7364
|
+
candidateKinds.get(path4) ?? "unreadable"
|
|
7429
7365
|
);
|
|
7430
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7366
|
+
if (presence === "file") presentCandidatePaths.push(path4);
|
|
7431
7367
|
}
|
|
7432
7368
|
try {
|
|
7433
7369
|
Object.assign(
|
|
@@ -7435,9 +7371,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7435
7371
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7436
7372
|
);
|
|
7437
7373
|
} catch (error) {
|
|
7438
|
-
const
|
|
7374
|
+
const path4 = readFailurePath(error);
|
|
7439
7375
|
throw new Error(
|
|
7440
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7376
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
|
|
7441
7377
|
error instanceof Error ? error.message : String(error)
|
|
7442
7378
|
)}`
|
|
7443
7379
|
);
|
|
@@ -7506,14 +7442,142 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7506
7442
|
var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
7507
7443
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
7508
7444
|
|
|
7445
|
+
// src/index.ts
|
|
7446
|
+
var import_typescript6 = __toESM(require("typescript"));
|
|
7447
|
+
|
|
7448
|
+
// src/module-boundaries.ts
|
|
7449
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
7450
|
+
var import_node_path2 = __toESM(require("path"));
|
|
7451
|
+
var import_typescript5 = __toESM(require("typescript"));
|
|
7452
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
7453
|
+
var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
7454
|
+
var DATA_FILE = /\.(?:sql|json)$/;
|
|
7455
|
+
function dataPathsIn(fileName, source) {
|
|
7456
|
+
if (!/\.(?:sql|json)["'`]/.test(source)) return [];
|
|
7457
|
+
const paths = [];
|
|
7458
|
+
const visit = (node) => {
|
|
7459
|
+
if ((import_typescript5.default.isStringLiteralLike(node) || import_typescript5.default.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
|
|
7460
|
+
paths.push(node.text);
|
|
7461
|
+
}
|
|
7462
|
+
import_typescript5.default.forEachChild(node, visit);
|
|
7463
|
+
};
|
|
7464
|
+
visit(import_typescript5.default.createSourceFile(fileName, source, import_typescript5.default.ScriptTarget.Latest, false));
|
|
7465
|
+
return paths;
|
|
7466
|
+
}
|
|
7467
|
+
function scanModule(modulesDir, module2) {
|
|
7468
|
+
const scanned = /* @__PURE__ */ new Map();
|
|
7469
|
+
const moduleDir = import_node_path2.default.join(modulesDir, module2);
|
|
7470
|
+
if (!import_node_fs2.default.existsSync(moduleDir)) return scanned;
|
|
7471
|
+
const files = import_node_fs2.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
|
|
7472
|
+
for (const relative of files) {
|
|
7473
|
+
const absolute = import_node_path2.default.join(moduleDir, relative);
|
|
7474
|
+
if (!import_node_fs2.default.statSync(absolute).isFile()) continue;
|
|
7475
|
+
const source = import_node_fs2.default.readFileSync(absolute, "utf8");
|
|
7476
|
+
const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
|
|
7477
|
+
const dataPaths = dataPathsIn(absolute, source);
|
|
7478
|
+
const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
|
|
7479
|
+
if (spec.startsWith("@/modules/"))
|
|
7480
|
+
return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
|
|
7481
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
7482
|
+
return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
|
|
7483
|
+
}
|
|
7484
|
+
return [];
|
|
7485
|
+
});
|
|
7486
|
+
scanned.set(absolute, {
|
|
7487
|
+
module: module2,
|
|
7488
|
+
relative: relative.split(import_node_path2.default.sep).join("/"),
|
|
7489
|
+
targets,
|
|
7490
|
+
dataPaths
|
|
7491
|
+
});
|
|
7492
|
+
}
|
|
7493
|
+
return scanned;
|
|
7494
|
+
}
|
|
7495
|
+
function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
|
|
7496
|
+
const files = /* @__PURE__ */ new Map();
|
|
7497
|
+
for (const [module2, peers] of Object.entries(peersByModule)) {
|
|
7498
|
+
if (peers.length === 0) continue;
|
|
7499
|
+
for (const [absolute, file] of scanModule(modulesDir, module2)) files.set(absolute, file);
|
|
7500
|
+
}
|
|
7501
|
+
const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
|
|
7502
|
+
(q) => file.relative.startsWith(`enhancements/${q}/`)
|
|
7503
|
+
);
|
|
7504
|
+
const goneWith = (target, peer) => {
|
|
7505
|
+
const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
|
|
7506
|
+
return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
|
|
7507
|
+
};
|
|
7508
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
7509
|
+
const queue = [];
|
|
7510
|
+
const taint = (absolute, peer) => {
|
|
7511
|
+
const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
|
|
7512
|
+
if (peers.has(peer)) return;
|
|
7513
|
+
peers.add(peer);
|
|
7514
|
+
tainted.set(absolute, peers);
|
|
7515
|
+
queue.push([absolute, peer]);
|
|
7516
|
+
};
|
|
7517
|
+
const importers = /* @__PURE__ */ new Map();
|
|
7518
|
+
for (const [absolute, file] of files) {
|
|
7519
|
+
for (const target of file.targets) {
|
|
7520
|
+
const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
|
|
7521
|
+
if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
|
|
7522
|
+
}
|
|
7523
|
+
for (const peer of peersByModule[file.module] ?? []) {
|
|
7524
|
+
if (exempt(file, peer)) continue;
|
|
7525
|
+
if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
|
|
7526
|
+
(p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
|
|
7527
|
+
)) {
|
|
7528
|
+
taint(absolute, peer);
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
7532
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
7533
|
+
const [target, peer] = next;
|
|
7534
|
+
for (const importer of importers.get(target) ?? []) {
|
|
7535
|
+
const file = files.get(importer);
|
|
7536
|
+
if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
|
|
7537
|
+
taint(importer, peer);
|
|
7538
|
+
}
|
|
7539
|
+
}
|
|
7540
|
+
}
|
|
7541
|
+
const violations = [];
|
|
7542
|
+
for (const [absolute, peers] of tainted) {
|
|
7543
|
+
const file = files.get(absolute);
|
|
7544
|
+
for (const peer of peers) {
|
|
7545
|
+
violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
|
|
7546
|
+
}
|
|
7547
|
+
}
|
|
7548
|
+
return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
|
|
7549
|
+
}
|
|
7550
|
+
function findOptionalPeerViolations(modulesDir) {
|
|
7551
|
+
if (!import_node_fs2.default.existsSync(modulesDir)) return [];
|
|
7552
|
+
const manifests = {};
|
|
7553
|
+
for (const name of import_node_fs2.default.readdirSync(modulesDir)) {
|
|
7554
|
+
const manifestPath = import_node_path2.default.join(modulesDir, name, "module.json");
|
|
7555
|
+
if (!import_node_fs2.default.existsSync(manifestPath)) continue;
|
|
7556
|
+
manifests[name] = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf8"));
|
|
7557
|
+
}
|
|
7558
|
+
const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
|
|
7559
|
+
const peersByModule = {};
|
|
7560
|
+
for (const [name, manifest] of Object.entries(manifests)) {
|
|
7561
|
+
const uses = new Set(manifest.uses ?? []);
|
|
7562
|
+
peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
|
|
7563
|
+
}
|
|
7564
|
+
return findModuleBoundaryViolations(modulesDir, peersByModule);
|
|
7565
|
+
}
|
|
7566
|
+
function formatBoundaryViolations(modulesDirLabel, violations) {
|
|
7567
|
+
const files = new Set(violations.map((v) => v.file)).size;
|
|
7568
|
+
return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
|
|
7569
|
+
\`next build\` type-checks them after the peer is gone. For each line, move the file into ${modulesDirLabel}/<module>/enhancements/<peer>/ or declare <peer> in the module's module.json \`uses\` (and enable it).
|
|
7570
|
+
` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
|
|
7571
|
+
}
|
|
7572
|
+
|
|
7509
7573
|
// src/index.ts
|
|
7510
7574
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7511
7575
|
function readComposeInputs(cwd) {
|
|
7512
|
-
const file =
|
|
7513
|
-
if (!
|
|
7576
|
+
const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7577
|
+
if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7514
7578
|
let raw;
|
|
7515
7579
|
try {
|
|
7516
|
-
raw = JSON.parse(
|
|
7580
|
+
raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
|
|
7517
7581
|
} catch (error) {
|
|
7518
7582
|
throw new Error(
|
|
7519
7583
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7555,18 +7619,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7555
7619
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7556
7620
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7557
7621
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7558
|
-
const file =
|
|
7559
|
-
const existing =
|
|
7622
|
+
const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7623
|
+
const existing = import_node_fs3.default.existsSync(file) ? import_node_fs3.default.readFileSync(file, "utf8") : null;
|
|
7560
7624
|
if (existing !== content) {
|
|
7561
|
-
|
|
7562
|
-
|
|
7625
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
|
|
7626
|
+
import_node_fs3.default.writeFileSync(file, content);
|
|
7563
7627
|
}
|
|
7564
7628
|
return entries.length;
|
|
7565
7629
|
}
|
|
7566
7630
|
function assertStubGitignoreOwned(cwd) {
|
|
7567
|
-
const file =
|
|
7568
|
-
if (!
|
|
7569
|
-
const firstLine =
|
|
7631
|
+
const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7632
|
+
if (!import_node_fs3.default.existsSync(file)) return;
|
|
7633
|
+
const firstLine = import_node_fs3.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
|
|
7570
7634
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7571
7635
|
throw new Error(
|
|
7572
7636
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7574,8 +7638,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7574
7638
|
}
|
|
7575
7639
|
}
|
|
7576
7640
|
function assertAppRoot(cwd) {
|
|
7577
|
-
const appPath =
|
|
7578
|
-
if (!
|
|
7641
|
+
const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
|
|
7642
|
+
if (!import_node_fs3.default.existsSync(appPath)) {
|
|
7579
7643
|
throw new Error(
|
|
7580
7644
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7581
7645
|
);
|
|
@@ -7621,9 +7685,9 @@ ${porcelain.trim()}`
|
|
|
7621
7685
|
}
|
|
7622
7686
|
var APP_SRC_DIR = "apps/web/src";
|
|
7623
7687
|
function walkSources(dir, skip, out) {
|
|
7624
|
-
if (!
|
|
7625
|
-
for (const entry of
|
|
7626
|
-
const full =
|
|
7688
|
+
if (!import_node_fs3.default.existsSync(dir)) return;
|
|
7689
|
+
for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
|
|
7690
|
+
const full = import_node_path3.default.join(dir, entry.name);
|
|
7627
7691
|
if (full === skip) continue;
|
|
7628
7692
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7629
7693
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7632,13 +7696,13 @@ function walkSources(dir, skip, out) {
|
|
|
7632
7696
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7633
7697
|
if (excluded.length === 0) return;
|
|
7634
7698
|
const files = [];
|
|
7635
|
-
walkSources(
|
|
7699
|
+
walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
|
|
7636
7700
|
const offenders = [];
|
|
7637
7701
|
for (const file of files) {
|
|
7638
|
-
const source =
|
|
7702
|
+
const source = import_node_fs3.default.readFileSync(file, "utf8");
|
|
7639
7703
|
for (const name of excluded) {
|
|
7640
7704
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7641
|
-
offenders.push(`${
|
|
7705
|
+
offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
|
|
7642
7706
|
}
|
|
7643
7707
|
}
|
|
7644
7708
|
}
|
|
@@ -7650,9 +7714,44 @@ function assertNoAppLayerImporters(cwd, excluded) {
|
|
|
7650
7714
|
);
|
|
7651
7715
|
}
|
|
7652
7716
|
}
|
|
7717
|
+
var TSCONFIG_PATH = "apps/web/tsconfig.json";
|
|
7718
|
+
var BUILD_TSCONFIG_PATH = "apps/web/tsconfig.build.gen.json";
|
|
7719
|
+
var BUILD_TSCONFIG_HEADER = "// Generated by stardeck-compose \u2014 the tsconfig `next build` type-checks. Do not edit or commit.";
|
|
7720
|
+
function writeBuildTsconfig(cwd, excluded) {
|
|
7721
|
+
const baseFile = import_node_path3.default.join(cwd, TSCONFIG_PATH);
|
|
7722
|
+
if (!import_node_fs3.default.existsSync(import_node_path3.default.join(cwd, MODULES_DIR2)) || !import_node_fs3.default.existsSync(baseFile)) return;
|
|
7723
|
+
const base = import_typescript6.default.readConfigFile(baseFile, import_typescript6.default.sys.readFile);
|
|
7724
|
+
if (base.error) {
|
|
7725
|
+
throw new Error(
|
|
7726
|
+
`[compose] cannot read ${TSCONFIG_PATH}: ${import_typescript6.default.flattenDiagnosticMessageText(base.error.messageText, "\n")}`
|
|
7727
|
+
);
|
|
7728
|
+
}
|
|
7729
|
+
const baseExclude = base.config?.exclude ?? ["node_modules"];
|
|
7730
|
+
const modulesDir = import_node_path3.default.posix.relative("apps/web", MODULES_DIR2);
|
|
7731
|
+
const config = {
|
|
7732
|
+
extends: "./tsconfig.json",
|
|
7733
|
+
exclude: [
|
|
7734
|
+
...baseExclude,
|
|
7735
|
+
...excluded.flatMap((name) => [
|
|
7736
|
+
`${modulesDir}/${name}`,
|
|
7737
|
+
`${modulesDir}/*/enhancements/${name}`
|
|
7738
|
+
])
|
|
7739
|
+
]
|
|
7740
|
+
};
|
|
7741
|
+
const content = `${BUILD_TSCONFIG_HEADER}
|
|
7742
|
+
${JSON.stringify(config, null, 2)}
|
|
7743
|
+
`;
|
|
7744
|
+
const file = import_node_path3.default.join(cwd, BUILD_TSCONFIG_PATH);
|
|
7745
|
+
if (!import_node_fs3.default.existsSync(file) || import_node_fs3.default.readFileSync(file, "utf8") !== content) {
|
|
7746
|
+
import_node_fs3.default.writeFileSync(file, content);
|
|
7747
|
+
}
|
|
7748
|
+
console.log(
|
|
7749
|
+
`[compose] ${BUILD_TSCONFIG_PATH} excludes ${excluded.length} Module(s): ${excluded.join(",") || "none"}`
|
|
7750
|
+
);
|
|
7751
|
+
}
|
|
7653
7752
|
function removeDir(absolute) {
|
|
7654
|
-
if (!
|
|
7655
|
-
|
|
7753
|
+
if (!import_node_fs3.default.existsSync(absolute)) return false;
|
|
7754
|
+
import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
|
|
7656
7755
|
return true;
|
|
7657
7756
|
}
|
|
7658
7757
|
async function compose(options) {
|
|
@@ -7679,6 +7778,16 @@ async function compose(options) {
|
|
|
7679
7778
|
dryRun: true
|
|
7680
7779
|
});
|
|
7681
7780
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
7781
|
+
const violations = findModuleBoundaryViolations(
|
|
7782
|
+
import_node_path3.default.join(options.cwd, MODULES_DIR2),
|
|
7783
|
+
Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
|
|
7784
|
+
{ pruning: true }
|
|
7785
|
+
);
|
|
7786
|
+
if (violations.length > 0) {
|
|
7787
|
+
throw new Error(
|
|
7788
|
+
`[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
|
|
7789
|
+
);
|
|
7790
|
+
}
|
|
7682
7791
|
}
|
|
7683
7792
|
const result = await reconcileCompositionArtifacts(context, {
|
|
7684
7793
|
enabledModulesRaw: options.enabled,
|
|
@@ -7687,14 +7796,15 @@ async function compose(options) {
|
|
|
7687
7796
|
skipComposeInputs: true
|
|
7688
7797
|
});
|
|
7689
7798
|
const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
|
|
7799
|
+
writeBuildTsconfig(options.cwd, result.excludedModules);
|
|
7690
7800
|
let pruned = 0;
|
|
7691
7801
|
if (options.prune) {
|
|
7692
7802
|
for (const name of result.excludedModules) {
|
|
7693
|
-
if (removeDir(
|
|
7803
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7694
7804
|
}
|
|
7695
7805
|
for (const owner of result.composedModules) {
|
|
7696
7806
|
for (const peer of result.excludedModules) {
|
|
7697
|
-
if (removeDir(
|
|
7807
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7698
7808
|
}
|
|
7699
7809
|
}
|
|
7700
7810
|
}
|
|
@@ -7709,9 +7819,13 @@ async function compose(options) {
|
|
|
7709
7819
|
}
|
|
7710
7820
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7711
7821
|
0 && (module.exports = {
|
|
7822
|
+
BUILD_TSCONFIG_PATH,
|
|
7712
7823
|
COMPOSE_INPUTS_PATH,
|
|
7713
7824
|
assertSupportedTypescript,
|
|
7714
7825
|
compose,
|
|
7715
7826
|
escapeGitignore,
|
|
7827
|
+
findModuleBoundaryViolations,
|
|
7828
|
+
findOptionalPeerViolations,
|
|
7829
|
+
formatBoundaryViolations,
|
|
7716
7830
|
readComposeInputs
|
|
7717
7831
|
});
|