@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.mjs
CHANGED
|
@@ -2020,8 +2020,8 @@ var require_semver2 = __commonJS({
|
|
|
2020
2020
|
|
|
2021
2021
|
// src/index.ts
|
|
2022
2022
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2023
|
-
import
|
|
2024
|
-
import
|
|
2023
|
+
import fs3 from "fs";
|
|
2024
|
+
import path3 from "path";
|
|
2025
2025
|
|
|
2026
2026
|
// ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
|
|
2027
2027
|
import { spawnSync } from "child_process";
|
|
@@ -2080,11 +2080,54 @@ function shellQuote(value) {
|
|
|
2080
2080
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
2081
2081
|
}
|
|
2082
2082
|
|
|
2083
|
-
// ../../packages/lib/src/shared/
|
|
2084
|
-
import { z
|
|
2083
|
+
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
2084
|
+
import { z } from "zod";
|
|
2085
|
+
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
2086
|
+
var composeInputsSchema = z.strictObject({
|
|
2087
|
+
connectedStores: z.array(
|
|
2088
|
+
z.strictObject({
|
|
2089
|
+
storeId: z.string().min(1),
|
|
2090
|
+
slug: z.string().min(1),
|
|
2091
|
+
// A row with an empty binding key must not fail a deploy: the rail
|
|
2092
|
+
// treats falsy as "no binding key", so normalise rather than reject.
|
|
2093
|
+
bindingKey: z.string().nullish().transform((value) => value || null),
|
|
2094
|
+
accessLevel: z.enum(["read", "write", "admin"]),
|
|
2095
|
+
// Defaulted so a file written before the platform emitted store types
|
|
2096
|
+
// (database rows only) still parses. The platform starts writing
|
|
2097
|
+
// storage rows, with `type` set, once forks run a compose that reads it.
|
|
2098
|
+
type: z.enum(["database", "storage"]).default("database")
|
|
2099
|
+
})
|
|
2100
|
+
).default([]),
|
|
2101
|
+
installs: z.record(
|
|
2102
|
+
z.string(),
|
|
2103
|
+
z.strictObject({
|
|
2104
|
+
installedVersion: z.string().min(1),
|
|
2105
|
+
datastoreBindings: z.record(z.string(), z.string()).nullish()
|
|
2106
|
+
})
|
|
2107
|
+
).default({})
|
|
2108
|
+
});
|
|
2109
|
+
var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
2110
|
+
var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
2111
|
+
function escapeStubIgnorePattern(pattern) {
|
|
2112
|
+
const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
|
|
2113
|
+
return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
|
|
2114
|
+
}
|
|
2115
|
+
function buildStubIgnore(artifactPaths) {
|
|
2116
|
+
const prefix = "apps/web/src/app/";
|
|
2117
|
+
const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
|
|
2118
|
+
return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// ../../packages/lib/src/shared/module-enablement.ts
|
|
2122
|
+
function isModuleEnabledByList(raw, name, kindOf) {
|
|
2123
|
+
if (!raw) return true;
|
|
2124
|
+
const kind = kindOf(name);
|
|
2125
|
+
if (kind != null && kind !== "feature") return true;
|
|
2126
|
+
return raw.split(",").includes(name);
|
|
2127
|
+
}
|
|
2085
2128
|
|
|
2086
2129
|
// ../../packages/module-contract/src/module-manifest.ts
|
|
2087
|
-
import { z } from "zod";
|
|
2130
|
+
import { z as z2 } from "zod";
|
|
2088
2131
|
|
|
2089
2132
|
// ../../packages/module-contract/src/slug.ts
|
|
2090
2133
|
function validateSlug(slug) {
|
|
@@ -2119,30 +2162,30 @@ var ROUTE_GROUP_REGEX = /^\([a-z0-9-]+\)$/;
|
|
|
2119
2162
|
var ENDPOINT_STATIC_SEGMENT_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2120
2163
|
var ENDPOINT_PARAM_NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
|
|
2121
2164
|
var ENDPOINT_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
2122
|
-
var moduleNameSchema =
|
|
2165
|
+
var moduleNameSchema = z2.string().max(40).regex(KEBAB_SLUG_REGEX, "must be a kebab-case slug (lowercase letters, digits, hyphens)");
|
|
2123
2166
|
function isValidModuleName(value) {
|
|
2124
2167
|
return moduleNameSchema.safeParse(value).success;
|
|
2125
2168
|
}
|
|
2126
|
-
var projectSlugSchema =
|
|
2127
|
-
var moduleKindSchema =
|
|
2128
|
-
var endpointMethodSchema =
|
|
2129
|
-
function endpointPathValidationError(
|
|
2130
|
-
if (!
|
|
2169
|
+
var projectSlugSchema = z2.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
2170
|
+
var moduleKindSchema = z2.enum(["feature", "library", "contract"]);
|
|
2171
|
+
var endpointMethodSchema = z2.enum(ENDPOINT_METHODS);
|
|
2172
|
+
function endpointPathValidationError(path4) {
|
|
2173
|
+
if (!path4.startsWith("/api/")) {
|
|
2131
2174
|
return 'must begin with "/api/"';
|
|
2132
2175
|
}
|
|
2133
|
-
if (
|
|
2176
|
+
if (path4.endsWith("/")) {
|
|
2134
2177
|
return "must not end with a trailing slash";
|
|
2135
2178
|
}
|
|
2136
|
-
if (
|
|
2179
|
+
if (path4.includes("?") || path4.includes("#")) {
|
|
2137
2180
|
return "must not include query or hash";
|
|
2138
2181
|
}
|
|
2139
|
-
if (
|
|
2182
|
+
if (path4.includes("\\")) {
|
|
2140
2183
|
return "must not include backslashes";
|
|
2141
2184
|
}
|
|
2142
|
-
if (
|
|
2185
|
+
if (path4.includes("%")) {
|
|
2143
2186
|
return "must not include percent-encoded segments";
|
|
2144
2187
|
}
|
|
2145
|
-
const parts =
|
|
2188
|
+
const parts = path4.split("/");
|
|
2146
2189
|
if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
|
|
2147
2190
|
return 'must begin with "/api/"';
|
|
2148
2191
|
}
|
|
@@ -2192,12 +2235,12 @@ function endpointPathValidationError(path3) {
|
|
|
2192
2235
|
}
|
|
2193
2236
|
return null;
|
|
2194
2237
|
}
|
|
2195
|
-
function normalizeEndpointPathForCollision(
|
|
2196
|
-
const error = endpointPathValidationError(
|
|
2238
|
+
function normalizeEndpointPathForCollision(path4) {
|
|
2239
|
+
const error = endpointPathValidationError(path4);
|
|
2197
2240
|
if (error != null) {
|
|
2198
2241
|
throw new Error(`invalid endpoint path: ${error}`);
|
|
2199
2242
|
}
|
|
2200
|
-
return
|
|
2243
|
+
return path4.split("/").map((segment, index) => {
|
|
2201
2244
|
if (index < 2) {
|
|
2202
2245
|
return segment;
|
|
2203
2246
|
}
|
|
@@ -2210,21 +2253,21 @@ function normalizeEndpointPathForCollision(path3) {
|
|
|
2210
2253
|
return segment;
|
|
2211
2254
|
}).join("/");
|
|
2212
2255
|
}
|
|
2213
|
-
var endpointPathSchema =
|
|
2214
|
-
const error = endpointPathValidationError(
|
|
2256
|
+
var endpointPathSchema = z2.string().superRefine((path4, ctx) => {
|
|
2257
|
+
const error = endpointPathValidationError(path4);
|
|
2215
2258
|
if (error != null) {
|
|
2216
|
-
ctx.addIssue({ code:
|
|
2259
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: error });
|
|
2217
2260
|
}
|
|
2218
2261
|
});
|
|
2219
|
-
var endpointClaimSchema =
|
|
2262
|
+
var endpointClaimSchema = z2.object({
|
|
2220
2263
|
path: endpointPathSchema,
|
|
2221
|
-
methods:
|
|
2264
|
+
methods: z2.array(endpointMethodSchema).min(1, "methods must be non-empty").superRefine((methods, ctx) => {
|
|
2222
2265
|
const seen = /* @__PURE__ */ new Set();
|
|
2223
2266
|
for (let i = 0; i < methods.length; i += 1) {
|
|
2224
2267
|
const method = methods[i];
|
|
2225
2268
|
if (seen.has(method)) {
|
|
2226
2269
|
ctx.addIssue({
|
|
2227
|
-
code:
|
|
2270
|
+
code: z2.ZodIssueCode.custom,
|
|
2228
2271
|
message: `duplicate method "${method}"`,
|
|
2229
2272
|
path: [i]
|
|
2230
2273
|
});
|
|
@@ -2261,14 +2304,14 @@ function mountSegmentValidationError(segment) {
|
|
|
2261
2304
|
}
|
|
2262
2305
|
return null;
|
|
2263
2306
|
}
|
|
2264
|
-
var mountSegmentSchema =
|
|
2307
|
+
var mountSegmentSchema = z2.string().superRefine((segment, ctx) => {
|
|
2265
2308
|
const error = mountSegmentValidationError(segment);
|
|
2266
2309
|
if (error != null) {
|
|
2267
|
-
ctx.addIssue({ code:
|
|
2310
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: error });
|
|
2268
2311
|
}
|
|
2269
2312
|
});
|
|
2270
|
-
var mountSchema =
|
|
2271
|
-
group:
|
|
2313
|
+
var mountSchema = z2.object({
|
|
2314
|
+
group: z2.string().regex(ROUTE_GROUP_REGEX, 'must be a Next.js route group like "(admin)"'),
|
|
2272
2315
|
segment: mountSegmentSchema
|
|
2273
2316
|
}).strict();
|
|
2274
2317
|
function splitMountSegment(segment) {
|
|
@@ -2281,21 +2324,21 @@ function splitMountSegment(segment) {
|
|
|
2281
2324
|
}
|
|
2282
2325
|
return segment.split("/");
|
|
2283
2326
|
}
|
|
2284
|
-
var tableNameSchema =
|
|
2327
|
+
var tableNameSchema = z2.string().regex(SNAKE_TABLE_REGEX, "must be snake_case");
|
|
2285
2328
|
var NPM_PACKAGE_NAME_REGEX = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
2286
|
-
var npmPackageNameSchema =
|
|
2287
|
-
var descriptionSchema =
|
|
2288
|
-
var crossAppSchema =
|
|
2289
|
-
exposes:
|
|
2290
|
-
consumes:
|
|
2329
|
+
var npmPackageNameSchema = z2.string().max(214).regex(NPM_PACKAGE_NAME_REGEX, "must be a valid npm package name");
|
|
2330
|
+
var descriptionSchema = z2.string().max(200, "must be at most 200 characters").refine((value) => value.trim().length > 0, "must be non-empty after trimming");
|
|
2331
|
+
var crossAppSchema = z2.object({
|
|
2332
|
+
exposes: z2.array(z2.unknown()).max(0, "must be empty until cross-app contracts are defined"),
|
|
2333
|
+
consumes: z2.array(z2.unknown()).max(0, "must be empty until cross-app contracts are defined")
|
|
2291
2334
|
}).strict();
|
|
2292
|
-
var enhancementWorkflowsSchema =
|
|
2293
|
-
var enhancementEndpointsSchema =
|
|
2294
|
-
var datastoreRequirementSchema =
|
|
2335
|
+
var enhancementWorkflowsSchema = z2.record(moduleNameSchema, z2.array(z2.string().min(1))).default({});
|
|
2336
|
+
var enhancementEndpointsSchema = z2.record(moduleNameSchema, z2.array(endpointClaimSchema)).default({});
|
|
2337
|
+
var datastoreRequirementSchema = z2.object({
|
|
2295
2338
|
/** Logical name, module-local, snake_case. Never a physical store key. */
|
|
2296
2339
|
id: tableNameSchema,
|
|
2297
2340
|
/** "app" = this project's app-local store; "org" = an org-level shared store. */
|
|
2298
|
-
scope:
|
|
2341
|
+
scope: z2.enum(["app", "org"]).default("app"),
|
|
2299
2342
|
/**
|
|
2300
2343
|
* Which kind of connected store satisfies this requirement. A "database"
|
|
2301
2344
|
* requirement only ever binds to a database store and a "storage" one
|
|
@@ -2303,34 +2346,34 @@ var datastoreRequirementSchema = z.object({
|
|
|
2303
2346
|
* rail never crosses the two. Defaults to "database", the only kind the
|
|
2304
2347
|
* rail resolved before this field existed.
|
|
2305
2348
|
*/
|
|
2306
|
-
type:
|
|
2349
|
+
type: z2.enum(["database", "storage"]).default("database"),
|
|
2307
2350
|
/** true → install fails when no store resolves; false → binding omitted, module degrades. */
|
|
2308
|
-
required:
|
|
2351
|
+
required: z2.boolean().default(true),
|
|
2309
2352
|
/** Checked against the connected store's accessLevel at install. */
|
|
2310
|
-
access:
|
|
2353
|
+
access: z2.enum(["read", "write", "admin"]).default("write")
|
|
2311
2354
|
}).strict();
|
|
2312
|
-
var permissionClaimSchema =
|
|
2355
|
+
var permissionClaimSchema = z2.object({
|
|
2313
2356
|
/**
|
|
2314
2357
|
* Lowercase resource:action with one colon and no underscores. This grammar
|
|
2315
2358
|
* is deliberately narrower than the platform permission tool's charset.
|
|
2316
2359
|
*/
|
|
2317
|
-
key:
|
|
2360
|
+
key: z2.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)"),
|
|
2318
2361
|
/** Human label for the deployment auth config row. */
|
|
2319
2362
|
description: descriptionSchema
|
|
2320
2363
|
}).strict();
|
|
2321
|
-
var seedBundleSchema =
|
|
2364
|
+
var seedBundleSchema = z2.object({
|
|
2322
2365
|
table: tableNameSchema,
|
|
2323
2366
|
/** Columns forming the ON CONFLICT key — must appear in every row. */
|
|
2324
|
-
conflictTarget:
|
|
2367
|
+
conflictTarget: z2.array(z2.string().min(1)).min(1),
|
|
2325
2368
|
/** Scalar JSON values only (no nested objects/arrays). */
|
|
2326
|
-
rows:
|
|
2369
|
+
rows: z2.array(z2.record(z2.string(), z2.union([z2.string(), z2.number(), z2.boolean(), z2.null()]))).min(1)
|
|
2327
2370
|
}).strict().superRefine((bundle, ctx) => {
|
|
2328
2371
|
const rowKeys = bundle.rows.map((row) => Object.keys(row).sort().join("\0"));
|
|
2329
2372
|
const canonical = rowKeys[0];
|
|
2330
2373
|
for (let i = 1; i < rowKeys.length; i += 1) {
|
|
2331
2374
|
if (rowKeys[i] !== canonical) {
|
|
2332
2375
|
ctx.addIssue({
|
|
2333
|
-
code:
|
|
2376
|
+
code: z2.ZodIssueCode.custom,
|
|
2334
2377
|
message: "all rows in a seed bundle must have identical key sets",
|
|
2335
2378
|
path: ["rows", i]
|
|
2336
2379
|
});
|
|
@@ -2342,7 +2385,7 @@ var seedBundleSchema = z.object({
|
|
|
2342
2385
|
for (const col of bundle.conflictTarget) {
|
|
2343
2386
|
if (!keys.has(col)) {
|
|
2344
2387
|
ctx.addIssue({
|
|
2345
|
-
code:
|
|
2388
|
+
code: z2.ZodIssueCode.custom,
|
|
2346
2389
|
message: `conflictTarget column "${col}" must be present in every row`,
|
|
2347
2390
|
path: ["rows", i]
|
|
2348
2391
|
});
|
|
@@ -2350,35 +2393,35 @@ var seedBundleSchema = z.object({
|
|
|
2350
2393
|
}
|
|
2351
2394
|
}
|
|
2352
2395
|
});
|
|
2353
|
-
var moduleManifestSchema =
|
|
2396
|
+
var moduleManifestSchema = z2.object({
|
|
2354
2397
|
name: moduleNameSchema,
|
|
2355
2398
|
kind: moduleKindSchema,
|
|
2356
2399
|
// Release versions are platform-owned in module_versions. Accept this
|
|
2357
2400
|
// legacy field while existing Module source ages out, but strip it from the
|
|
2358
2401
|
// parsed contract so no runtime can treat module.json as version authority.
|
|
2359
|
-
version:
|
|
2402
|
+
version: z2.unknown().optional(),
|
|
2360
2403
|
home: projectSlugSchema,
|
|
2361
2404
|
/** Machine-checkable purpose line — required, ≤200 code units, non-blank when trimmed. */
|
|
2362
2405
|
description: descriptionSchema,
|
|
2363
|
-
uses:
|
|
2406
|
+
uses: z2.array(moduleNameSchema).default([]),
|
|
2364
2407
|
/**
|
|
2365
2408
|
* Soft peers: this module ships `enhancements/<peer>/` directories that are
|
|
2366
2409
|
* composition-pruned — the installer includes them only when the peer is
|
|
2367
2410
|
* present, so the peer is NOT an install-time requirement. Disjoint from
|
|
2368
2411
|
* `uses`; entries may name modules absent from the composition.
|
|
2369
2412
|
*/
|
|
2370
|
-
enhancements:
|
|
2371
|
-
tables:
|
|
2372
|
-
mounts:
|
|
2373
|
-
integration:
|
|
2374
|
-
workflows:
|
|
2413
|
+
enhancements: z2.array(moduleNameSchema).default([]),
|
|
2414
|
+
tables: z2.array(tableNameSchema).default([]),
|
|
2415
|
+
mounts: z2.array(mountSchema).default([]),
|
|
2416
|
+
integration: z2.array(z2.string()).default([]),
|
|
2417
|
+
workflows: z2.array(z2.string().min(1)).default([]),
|
|
2375
2418
|
/**
|
|
2376
2419
|
* Slice-scoped workflow names keyed by peer. Keys must be ⊆ `enhancements`;
|
|
2377
2420
|
* names must be unique across root `workflows` and every slice list.
|
|
2378
2421
|
*/
|
|
2379
2422
|
enhancementWorkflows: enhancementWorkflowsSchema,
|
|
2380
2423
|
/** Root API endpoint path claims. */
|
|
2381
|
-
endpoints:
|
|
2424
|
+
endpoints: z2.array(endpointClaimSchema).default([]),
|
|
2382
2425
|
/**
|
|
2383
2426
|
* Slice-scoped endpoint claims keyed by peer. Keys must be ⊆ `enhancements`;
|
|
2384
2427
|
* normalized paths must be unique across root and every slice.
|
|
@@ -2394,38 +2437,38 @@ var moduleManifestSchema = z.object({
|
|
|
2394
2437
|
* to a physical connected store and generates module-datastores.gen.ts;
|
|
2395
2438
|
* unlike seeds, this field drives generated output.
|
|
2396
2439
|
*/
|
|
2397
|
-
datastores:
|
|
2440
|
+
datastores: z2.array(datastoreRequirementSchema).optional(),
|
|
2398
2441
|
/**
|
|
2399
2442
|
* Keys this module's guards enforce. Install registers them in deployment
|
|
2400
2443
|
* auth config; registration only — declaring is never granting.
|
|
2401
2444
|
*/
|
|
2402
|
-
permissions:
|
|
2445
|
+
permissions: z2.array(permissionClaimSchema).optional(),
|
|
2403
2446
|
/**
|
|
2404
2447
|
* Optional starter rows for tables this module owns. Replay is
|
|
2405
2448
|
* `INSERT ... ON CONFLICT (conflictTarget) DO NOTHING` — idempotent by
|
|
2406
2449
|
* construction; the rail (not this schema) performs the replay.
|
|
2407
2450
|
*/
|
|
2408
|
-
seeds:
|
|
2451
|
+
seeds: z2.array(seedBundleSchema).optional(),
|
|
2409
2452
|
/** npm packages the source imports beyond the base app. Names only; the rail installs each at the home app's range. */
|
|
2410
|
-
packages:
|
|
2453
|
+
packages: z2.array(npmPackageNameSchema).optional(),
|
|
2411
2454
|
/**
|
|
2412
2455
|
* UI-layer contract. `separable: true` asserts the Module's components/ and
|
|
2413
2456
|
* routes/ hold presentation only — the Blueprint's
|
|
2414
2457
|
* `module-boundaries/ui-layer` lint rule enforces it — so a fork may
|
|
2415
2458
|
* soft-eject them. See docs/module-ui-soft-eject-plan.md.
|
|
2416
2459
|
*/
|
|
2417
|
-
ui:
|
|
2460
|
+
ui: z2.object({ separable: z2.boolean() }).strict().optional()
|
|
2418
2461
|
}).strict().superRefine((manifest, ctx) => {
|
|
2419
2462
|
if (manifest.uses.includes(manifest.name)) {
|
|
2420
2463
|
ctx.addIssue({
|
|
2421
|
-
code:
|
|
2464
|
+
code: z2.ZodIssueCode.custom,
|
|
2422
2465
|
message: "a module cannot list itself in uses",
|
|
2423
2466
|
path: ["uses"]
|
|
2424
2467
|
});
|
|
2425
2468
|
}
|
|
2426
2469
|
if (manifest.enhancements.includes(manifest.name)) {
|
|
2427
2470
|
ctx.addIssue({
|
|
2428
|
-
code:
|
|
2471
|
+
code: z2.ZodIssueCode.custom,
|
|
2429
2472
|
message: "a module cannot list itself in enhancements",
|
|
2430
2473
|
path: ["enhancements"]
|
|
2431
2474
|
});
|
|
@@ -2433,7 +2476,7 @@ var moduleManifestSchema = z.object({
|
|
|
2433
2476
|
const hardAndSoft = manifest.enhancements.filter((peer) => manifest.uses.includes(peer));
|
|
2434
2477
|
if (hardAndSoft.length > 0) {
|
|
2435
2478
|
ctx.addIssue({
|
|
2436
|
-
code:
|
|
2479
|
+
code: z2.ZodIssueCode.custom,
|
|
2437
2480
|
message: `uses and enhancements must be disjoint (a peer is either a hard dep or a soft one): ${hardAndSoft.join(", ")}`,
|
|
2438
2481
|
path: ["enhancements"]
|
|
2439
2482
|
});
|
|
@@ -2442,7 +2485,7 @@ var moduleManifestSchema = z.object({
|
|
|
2442
2485
|
for (const peer of Object.keys(manifest.enhancementWorkflows)) {
|
|
2443
2486
|
if (!enhancementPeers.has(peer)) {
|
|
2444
2487
|
ctx.addIssue({
|
|
2445
|
-
code:
|
|
2488
|
+
code: z2.ZodIssueCode.custom,
|
|
2446
2489
|
message: `enhancementWorkflows key "${peer}" must be listed in enhancements`,
|
|
2447
2490
|
path: ["enhancementWorkflows", peer]
|
|
2448
2491
|
});
|
|
@@ -2451,23 +2494,23 @@ var moduleManifestSchema = z.object({
|
|
|
2451
2494
|
for (const peer of Object.keys(manifest.enhancementEndpoints)) {
|
|
2452
2495
|
if (!enhancementPeers.has(peer)) {
|
|
2453
2496
|
ctx.addIssue({
|
|
2454
|
-
code:
|
|
2497
|
+
code: z2.ZodIssueCode.custom,
|
|
2455
2498
|
message: `enhancementEndpoints key "${peer}" must be listed in enhancements`,
|
|
2456
2499
|
path: ["enhancementEndpoints", peer]
|
|
2457
2500
|
});
|
|
2458
2501
|
}
|
|
2459
2502
|
}
|
|
2460
2503
|
const seenWorkflows = /* @__PURE__ */ new Map();
|
|
2461
|
-
const noteWorkflow = (name,
|
|
2504
|
+
const noteWorkflow = (name, path4) => {
|
|
2462
2505
|
const existing = seenWorkflows.get(name);
|
|
2463
2506
|
if (existing != null) {
|
|
2464
2507
|
ctx.addIssue({
|
|
2465
|
-
code:
|
|
2508
|
+
code: z2.ZodIssueCode.custom,
|
|
2466
2509
|
message: `duplicate workflow name "${name}" (also at ${existing})`,
|
|
2467
|
-
path:
|
|
2510
|
+
path: path4
|
|
2468
2511
|
});
|
|
2469
2512
|
} else {
|
|
2470
|
-
seenWorkflows.set(name,
|
|
2513
|
+
seenWorkflows.set(name, path4.join("."));
|
|
2471
2514
|
}
|
|
2472
2515
|
};
|
|
2473
2516
|
for (let i = 0; i < manifest.workflows.length; i += 1) {
|
|
@@ -2479,7 +2522,7 @@ var moduleManifestSchema = z.object({
|
|
|
2479
2522
|
}
|
|
2480
2523
|
}
|
|
2481
2524
|
const seenEndpointKeys = /* @__PURE__ */ new Map();
|
|
2482
|
-
const noteEndpoint = (claim,
|
|
2525
|
+
const noteEndpoint = (claim, path4) => {
|
|
2483
2526
|
let key;
|
|
2484
2527
|
try {
|
|
2485
2528
|
key = normalizeEndpointPathForCollision(claim.path);
|
|
@@ -2489,12 +2532,12 @@ var moduleManifestSchema = z.object({
|
|
|
2489
2532
|
const existing = seenEndpointKeys.get(key);
|
|
2490
2533
|
if (existing != null) {
|
|
2491
2534
|
ctx.addIssue({
|
|
2492
|
-
code:
|
|
2535
|
+
code: z2.ZodIssueCode.custom,
|
|
2493
2536
|
message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
|
|
2494
|
-
path:
|
|
2537
|
+
path: path4
|
|
2495
2538
|
});
|
|
2496
2539
|
} else {
|
|
2497
|
-
seenEndpointKeys.set(key,
|
|
2540
|
+
seenEndpointKeys.set(key, path4.join("."));
|
|
2498
2541
|
}
|
|
2499
2542
|
};
|
|
2500
2543
|
for (let i = 0; i < manifest.endpoints.length; i += 1) {
|
|
@@ -2511,7 +2554,7 @@ var moduleManifestSchema = z.object({
|
|
|
2511
2554
|
const id = manifest.datastores[i].id;
|
|
2512
2555
|
if (seenDatastoreIds.has(id)) {
|
|
2513
2556
|
ctx.addIssue({
|
|
2514
|
-
code:
|
|
2557
|
+
code: z2.ZodIssueCode.custom,
|
|
2515
2558
|
message: `duplicate datastore id "${id}"`,
|
|
2516
2559
|
path: ["datastores", i, "id"]
|
|
2517
2560
|
});
|
|
@@ -2526,7 +2569,7 @@ var moduleManifestSchema = z.object({
|
|
|
2526
2569
|
const key = manifest.permissions[i].key;
|
|
2527
2570
|
if (seenPermissionKeys.has(key)) {
|
|
2528
2571
|
ctx.addIssue({
|
|
2529
|
-
code:
|
|
2572
|
+
code: z2.ZodIssueCode.custom,
|
|
2530
2573
|
message: `duplicate permission key "${key}"`,
|
|
2531
2574
|
path: ["permissions", i, "key"]
|
|
2532
2575
|
});
|
|
@@ -2538,35 +2581,35 @@ var moduleManifestSchema = z.object({
|
|
|
2538
2581
|
if (manifest.kind === "library") {
|
|
2539
2582
|
if (manifest.tables.length > 0) {
|
|
2540
2583
|
ctx.addIssue({
|
|
2541
|
-
code:
|
|
2584
|
+
code: z2.ZodIssueCode.custom,
|
|
2542
2585
|
message: "library modules must not declare tables",
|
|
2543
2586
|
path: ["tables"]
|
|
2544
2587
|
});
|
|
2545
2588
|
}
|
|
2546
2589
|
if (manifest.mounts.length > 0) {
|
|
2547
2590
|
ctx.addIssue({
|
|
2548
|
-
code:
|
|
2591
|
+
code: z2.ZodIssueCode.custom,
|
|
2549
2592
|
message: "library modules must not declare mounts",
|
|
2550
2593
|
path: ["mounts"]
|
|
2551
2594
|
});
|
|
2552
2595
|
}
|
|
2553
2596
|
if (manifest.seeds != null && manifest.seeds.length > 0) {
|
|
2554
2597
|
ctx.addIssue({
|
|
2555
|
-
code:
|
|
2598
|
+
code: z2.ZodIssueCode.custom,
|
|
2556
2599
|
message: "library modules must not declare seeds",
|
|
2557
2600
|
path: ["seeds"]
|
|
2558
2601
|
});
|
|
2559
2602
|
}
|
|
2560
2603
|
if (manifest.datastores != null && manifest.datastores.length > 0) {
|
|
2561
2604
|
ctx.addIssue({
|
|
2562
|
-
code:
|
|
2605
|
+
code: z2.ZodIssueCode.custom,
|
|
2563
2606
|
message: "library modules must not declare datastores",
|
|
2564
2607
|
path: ["datastores"]
|
|
2565
2608
|
});
|
|
2566
2609
|
}
|
|
2567
2610
|
if (manifest.permissions != null && manifest.permissions.length > 0) {
|
|
2568
2611
|
ctx.addIssue({
|
|
2569
|
-
code:
|
|
2612
|
+
code: z2.ZodIssueCode.custom,
|
|
2570
2613
|
message: "library modules must not declare permissions",
|
|
2571
2614
|
path: ["permissions"]
|
|
2572
2615
|
});
|
|
@@ -2574,7 +2617,7 @@ var moduleManifestSchema = z.object({
|
|
|
2574
2617
|
}
|
|
2575
2618
|
if (manifest.kind === "contract" && manifest.mounts.length > 0) {
|
|
2576
2619
|
ctx.addIssue({
|
|
2577
|
-
code:
|
|
2620
|
+
code: z2.ZodIssueCode.custom,
|
|
2578
2621
|
message: "contract modules must not declare mounts",
|
|
2579
2622
|
path: ["mounts"]
|
|
2580
2623
|
});
|
|
@@ -2585,7 +2628,7 @@ var moduleManifestSchema = z.object({
|
|
|
2585
2628
|
const bundle = manifest.seeds[i];
|
|
2586
2629
|
if (!ownedTables.has(bundle.table)) {
|
|
2587
2630
|
ctx.addIssue({
|
|
2588
|
-
code:
|
|
2631
|
+
code: z2.ZodIssueCode.custom,
|
|
2589
2632
|
message: `seeds may only target tables listed in tables (got "${bundle.table}")`,
|
|
2590
2633
|
path: ["seeds", i, "table"]
|
|
2591
2634
|
});
|
|
@@ -2595,8 +2638,8 @@ var moduleManifestSchema = z.object({
|
|
|
2595
2638
|
}).transform(({ version: _legacyVersion, ...manifest }) => manifest);
|
|
2596
2639
|
function formatZodErrors(error) {
|
|
2597
2640
|
return error.issues.map((issue) => {
|
|
2598
|
-
const
|
|
2599
|
-
return `${
|
|
2641
|
+
const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2642
|
+
return `${path4}: ${issue.message}`;
|
|
2600
2643
|
});
|
|
2601
2644
|
}
|
|
2602
2645
|
function parseModuleManifest(raw) {
|
|
@@ -2636,10 +2679,10 @@ var MANDATORY_MODULE_CHECK_DESCRIPTORS = Object.freeze([
|
|
|
2636
2679
|
]);
|
|
2637
2680
|
|
|
2638
2681
|
// ../../packages/module-contract/src/module-structure.ts
|
|
2639
|
-
import { z as
|
|
2682
|
+
import { z as z4 } from "zod";
|
|
2640
2683
|
|
|
2641
2684
|
// ../../packages/module-contract/src/module-e2e-manifest.ts
|
|
2642
|
-
import { z as
|
|
2685
|
+
import { z as z3 } from "zod";
|
|
2643
2686
|
function e2eSpecFileValidationError(file) {
|
|
2644
2687
|
if (file.length === 0) {
|
|
2645
2688
|
return "must be a non-empty relative path ending in .spec.ts";
|
|
@@ -2667,22 +2710,22 @@ function e2eSpecFileValidationError(file) {
|
|
|
2667
2710
|
}
|
|
2668
2711
|
return null;
|
|
2669
2712
|
}
|
|
2670
|
-
var e2eSpecFileSchema =
|
|
2713
|
+
var e2eSpecFileSchema = z3.string().superRefine((file, ctx) => {
|
|
2671
2714
|
const error = e2eSpecFileValidationError(file);
|
|
2672
2715
|
if (error != null) {
|
|
2673
|
-
ctx.addIssue({ code:
|
|
2716
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, message: error });
|
|
2674
2717
|
}
|
|
2675
2718
|
});
|
|
2676
|
-
var e2eSpecSchema =
|
|
2719
|
+
var e2eSpecSchema = z3.object({
|
|
2677
2720
|
file: e2eSpecFileSchema,
|
|
2678
|
-
requires:
|
|
2721
|
+
requires: z3.array(moduleNameSchema).min(1, "requires must be non-empty")
|
|
2679
2722
|
}).strict().superRefine((spec, ctx) => {
|
|
2680
2723
|
const seen = /* @__PURE__ */ new Set();
|
|
2681
2724
|
for (let i = 0; i < spec.requires.length; i += 1) {
|
|
2682
2725
|
const name = spec.requires[i];
|
|
2683
2726
|
if (seen.has(name)) {
|
|
2684
2727
|
ctx.addIssue({
|
|
2685
|
-
code:
|
|
2728
|
+
code: z3.ZodIssueCode.custom,
|
|
2686
2729
|
message: `duplicate requirement "${name}"`,
|
|
2687
2730
|
path: ["requires", i]
|
|
2688
2731
|
});
|
|
@@ -2691,15 +2734,15 @@ var e2eSpecSchema = z2.object({
|
|
|
2691
2734
|
}
|
|
2692
2735
|
}
|
|
2693
2736
|
});
|
|
2694
|
-
var moduleE2eManifestSchema =
|
|
2695
|
-
specs:
|
|
2737
|
+
var moduleE2eManifestSchema = z3.object({
|
|
2738
|
+
specs: z3.array(e2eSpecSchema).min(1, "specs must contain at least one entry")
|
|
2696
2739
|
}).strict().superRefine((manifest, ctx) => {
|
|
2697
2740
|
const seenFiles = /* @__PURE__ */ new Set();
|
|
2698
2741
|
for (let i = 0; i < manifest.specs.length; i += 1) {
|
|
2699
2742
|
const file = manifest.specs[i].file;
|
|
2700
2743
|
if (seenFiles.has(file)) {
|
|
2701
2744
|
ctx.addIssue({
|
|
2702
|
-
code:
|
|
2745
|
+
code: z3.ZodIssueCode.custom,
|
|
2703
2746
|
message: `duplicate file "${file}"`,
|
|
2704
2747
|
path: ["specs", i, "file"]
|
|
2705
2748
|
});
|
|
@@ -2941,7 +2984,7 @@ function compareCodeUnit(a, b) {
|
|
|
2941
2984
|
// ../../packages/module-contract/src/module-structure.ts
|
|
2942
2985
|
var MODULE_STRUCTURE_JSON_BYTE_CAP = 64 * 1024;
|
|
2943
2986
|
function uniqueSortedStringArray(label) {
|
|
2944
|
-
return
|
|
2987
|
+
return z4.array(z4.string()).superRefine((arr, ctx) => {
|
|
2945
2988
|
if (new Set(arr).size !== arr.length) {
|
|
2946
2989
|
ctx.addIssue({ code: "custom", message: `${label} must be unique` });
|
|
2947
2990
|
return;
|
|
@@ -2957,10 +3000,10 @@ function uniqueSortedStringArray(label) {
|
|
|
2957
3000
|
});
|
|
2958
3001
|
}
|
|
2959
3002
|
function contributionPermissionsSchema(label) {
|
|
2960
|
-
return
|
|
2961
|
-
|
|
2962
|
-
contributionId:
|
|
2963
|
-
permission:
|
|
3003
|
+
return z4.array(
|
|
3004
|
+
z4.object({
|
|
3005
|
+
contributionId: z4.string().min(1),
|
|
3006
|
+
permission: z4.string().min(1)
|
|
2964
3007
|
}).strict()
|
|
2965
3008
|
).superRefine((entries, ctx) => {
|
|
2966
3009
|
const keys = entries.map((entry) => `${entry.contributionId}\0${entry.permission}`);
|
|
@@ -2978,17 +3021,17 @@ function contributionPermissionsSchema(label) {
|
|
|
2978
3021
|
}
|
|
2979
3022
|
}).optional();
|
|
2980
3023
|
}
|
|
2981
|
-
var collectedFilePayloadSchema =
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3024
|
+
var collectedFilePayloadSchema = z4.discriminatedUnion("status", [
|
|
3025
|
+
z4.object({ status: z4.literal("ok"), text: z4.string() }).strict(),
|
|
3026
|
+
z4.object({ status: z4.literal("missing") }).strict(),
|
|
3027
|
+
z4.object({ status: z4.literal("too-large") }).strict(),
|
|
3028
|
+
z4.object({ status: z4.literal("unreadable") }).strict()
|
|
2986
3029
|
]);
|
|
2987
|
-
var collectedLocaleFileSchema =
|
|
2988
|
-
locale:
|
|
3030
|
+
var collectedLocaleFileSchema = z4.object({
|
|
3031
|
+
locale: z4.string().min(1),
|
|
2989
3032
|
payload: collectedFilePayloadSchema
|
|
2990
3033
|
}).strict();
|
|
2991
|
-
var collectedE2eFactsSchema =
|
|
3034
|
+
var collectedE2eFactsSchema = z4.object({
|
|
2992
3035
|
manifest: collectedFilePayloadSchema,
|
|
2993
3036
|
specFiles: uniqueSortedStringArray("specFiles"),
|
|
2994
3037
|
escapingSymlinks: uniqueSortedStringArray("escapingSymlinks")
|
|
@@ -2996,34 +3039,34 @@ var collectedE2eFactsSchema = z3.object({
|
|
|
2996
3039
|
function validateRouteExportEvidenceCongruence(input) {
|
|
2997
3040
|
const entrySet = new Set(input.routeEntries);
|
|
2998
3041
|
const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
|
|
2999
|
-
for (const
|
|
3000
|
-
if (!entrySet.has(
|
|
3001
|
-
input.onIssue(`routePageExports path "${
|
|
3042
|
+
for (const path4 of exportPaths) {
|
|
3043
|
+
if (!entrySet.has(path4)) {
|
|
3044
|
+
input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
|
|
3002
3045
|
}
|
|
3003
3046
|
}
|
|
3004
3047
|
if (input.routeCollectionErrors.length === 0) {
|
|
3005
3048
|
const exportSet = new Set(exportPaths);
|
|
3006
|
-
for (const
|
|
3007
|
-
if (!exportSet.has(
|
|
3008
|
-
input.onIssue(`routeEntries path "${
|
|
3049
|
+
for (const path4 of input.routeEntries) {
|
|
3050
|
+
if (!exportSet.has(path4)) {
|
|
3051
|
+
input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
|
|
3009
3052
|
}
|
|
3010
3053
|
}
|
|
3011
3054
|
}
|
|
3012
3055
|
}
|
|
3013
|
-
var collectedEnhancementFactsSchema =
|
|
3014
|
-
peer:
|
|
3015
|
-
hasContributionsTs:
|
|
3056
|
+
var collectedEnhancementFactsSchema = z4.object({
|
|
3057
|
+
peer: z4.string().min(1),
|
|
3058
|
+
hasContributionsTs: z4.boolean(),
|
|
3016
3059
|
contributionPermissions: contributionPermissionsSchema("enhancement contributionPermissions"),
|
|
3017
|
-
hasInitializeServerTs:
|
|
3060
|
+
hasInitializeServerTs: z4.boolean(),
|
|
3018
3061
|
contributionsCollectionErrors: uniqueSortedStringArray(
|
|
3019
3062
|
"enhancement contributionsCollectionErrors"
|
|
3020
3063
|
),
|
|
3021
3064
|
initializeServerCollectionErrors: uniqueSortedStringArray(
|
|
3022
3065
|
"enhancement initializeServerCollectionErrors"
|
|
3023
3066
|
),
|
|
3024
|
-
hasRoutesDir:
|
|
3067
|
+
hasRoutesDir: z4.boolean(),
|
|
3025
3068
|
routeCollectionErrors: uniqueSortedStringArray("enhancement routeCollectionErrors"),
|
|
3026
|
-
locales:
|
|
3069
|
+
locales: z4.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
|
|
3027
3070
|
const names = locales.map((l) => l.locale);
|
|
3028
3071
|
if (new Set(names).size !== names.length) {
|
|
3029
3072
|
ctx.addIssue({ code: "custom", message: "enhancement locales must be unique by locale" });
|
|
@@ -3038,27 +3081,27 @@ var collectedEnhancementFactsSchema = z3.object({
|
|
|
3038
3081
|
}),
|
|
3039
3082
|
e2e: collectedE2eFactsSchema.nullable()
|
|
3040
3083
|
}).strict();
|
|
3041
|
-
var moduleStructureFactsSchema =
|
|
3042
|
-
collected:
|
|
3043
|
-
dirName:
|
|
3044
|
-
hasIndexTs:
|
|
3045
|
-
hasContributionsTs:
|
|
3084
|
+
var moduleStructureFactsSchema = z4.object({
|
|
3085
|
+
collected: z4.literal(true),
|
|
3086
|
+
dirName: z4.string().min(1),
|
|
3087
|
+
hasIndexTs: z4.boolean(),
|
|
3088
|
+
hasContributionsTs: z4.boolean(),
|
|
3046
3089
|
contributionPermissions: contributionPermissionsSchema("contributionPermissions"),
|
|
3047
|
-
hasSlotsTs:
|
|
3090
|
+
hasSlotsTs: z4.boolean(),
|
|
3048
3091
|
contributionsCollectionErrors: uniqueSortedStringArray("contributionsCollectionErrors"),
|
|
3049
3092
|
slotsCollectionErrors: uniqueSortedStringArray("slotsCollectionErrors"),
|
|
3050
|
-
hasTestsDir:
|
|
3051
|
-
hasDatastoreDir:
|
|
3052
|
-
datastoreFileCount:
|
|
3053
|
-
hasServerIndexTs:
|
|
3054
|
-
hasOtherServerTs:
|
|
3055
|
-
hasE2eDir:
|
|
3056
|
-
hasRoutesDir:
|
|
3093
|
+
hasTestsDir: z4.boolean(),
|
|
3094
|
+
hasDatastoreDir: z4.boolean(),
|
|
3095
|
+
datastoreFileCount: z4.number().int().nonnegative(),
|
|
3096
|
+
hasServerIndexTs: z4.boolean(),
|
|
3097
|
+
hasOtherServerTs: z4.boolean(),
|
|
3098
|
+
hasE2eDir: z4.boolean(),
|
|
3099
|
+
hasRoutesDir: z4.boolean(),
|
|
3057
3100
|
routeEntries: uniqueSortedStringArray("routeEntries"),
|
|
3058
|
-
routePageExports:
|
|
3059
|
-
|
|
3060
|
-
nextRelativePath:
|
|
3061
|
-
hasGenerateMetadata:
|
|
3101
|
+
routePageExports: z4.array(
|
|
3102
|
+
z4.object({
|
|
3103
|
+
nextRelativePath: z4.string().min(1),
|
|
3104
|
+
hasGenerateMetadata: z4.boolean()
|
|
3062
3105
|
}).strict()
|
|
3063
3106
|
).superRefine((entries, ctx) => {
|
|
3064
3107
|
const paths = entries.map((e) => e.nextRelativePath);
|
|
@@ -3080,7 +3123,7 @@ var moduleStructureFactsSchema = z3.object({
|
|
|
3080
3123
|
}
|
|
3081
3124
|
}),
|
|
3082
3125
|
routeCollectionErrors: uniqueSortedStringArray("routeCollectionErrors"),
|
|
3083
|
-
rootLocales:
|
|
3126
|
+
rootLocales: z4.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
|
|
3084
3127
|
const names = locales.map((l) => l.locale);
|
|
3085
3128
|
if (new Set(names).size !== names.length) {
|
|
3086
3129
|
ctx.addIssue({ code: "custom", message: "rootLocales must be unique by locale" });
|
|
@@ -3094,7 +3137,7 @@ var moduleStructureFactsSchema = z3.object({
|
|
|
3094
3137
|
}
|
|
3095
3138
|
}),
|
|
3096
3139
|
rootE2e: collectedE2eFactsSchema.nullable(),
|
|
3097
|
-
enhancements:
|
|
3140
|
+
enhancements: z4.array(collectedEnhancementFactsSchema).superRefine((enhancements, ctx) => {
|
|
3098
3141
|
const peers = enhancements.map((e) => e.peer);
|
|
3099
3142
|
if (new Set(peers).size !== peers.length) {
|
|
3100
3143
|
ctx.addIssue({ code: "custom", message: "enhancement peers must be unique" });
|
|
@@ -3115,10 +3158,10 @@ var moduleStructureFactsSchema = z3.object({
|
|
|
3115
3158
|
onIssue: (message) => ctx.addIssue({ code: "custom", message })
|
|
3116
3159
|
});
|
|
3117
3160
|
});
|
|
3118
|
-
var moduleStructureCollectionSchema =
|
|
3161
|
+
var moduleStructureCollectionSchema = z4.object({
|
|
3119
3162
|
homeLocales: uniqueSortedStringArray("homeLocales"),
|
|
3120
|
-
modules:
|
|
3121
|
-
truncated:
|
|
3163
|
+
modules: z4.array(moduleStructureFactsSchema),
|
|
3164
|
+
truncated: z4.boolean().optional(),
|
|
3122
3165
|
collectionErrors: uniqueSortedStringArray("collectionErrors").optional()
|
|
3123
3166
|
}).strict().superRefine((collection, ctx) => {
|
|
3124
3167
|
const dirs = collection.modules.map((m) => m.dirName);
|
|
@@ -3135,9 +3178,9 @@ var moduleStructureCollectionSchema = z3.object({
|
|
|
3135
3178
|
});
|
|
3136
3179
|
|
|
3137
3180
|
// ../../packages/module-contract/src/locale.ts
|
|
3138
|
-
import { z as
|
|
3181
|
+
import { z as z5 } from "zod";
|
|
3139
3182
|
var LOCALE_BASENAME_REGEX = /^[a-z]{2,3}(-[A-Za-z0-9]+)*$/;
|
|
3140
|
-
var localeBasenameSchema =
|
|
3183
|
+
var localeBasenameSchema = z5.string().max(35).regex(LOCALE_BASENAME_REGEX, "must be a BCP47-like locale tag (e.g. en, th, en-US)");
|
|
3141
3184
|
function isValidLocaleBasename(value) {
|
|
3142
3185
|
return localeBasenameSchema.safeParse(value).success;
|
|
3143
3186
|
}
|
|
@@ -3540,43 +3583,43 @@ function formatRoutePageExportErrors(nextRelativePath, validation) {
|
|
|
3540
3583
|
import ts2 from "typescript";
|
|
3541
3584
|
|
|
3542
3585
|
// ../../packages/module-contract/src/module-registry.ts
|
|
3543
|
-
import { z as
|
|
3544
|
-
var projectSlugSchema2 =
|
|
3545
|
-
var registryEnhancementSchema =
|
|
3586
|
+
import { z as z6 } from "zod";
|
|
3587
|
+
var projectSlugSchema2 = z6.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
3588
|
+
var registryEnhancementSchema = z6.object({
|
|
3546
3589
|
peer: moduleNameSchema,
|
|
3547
|
-
active:
|
|
3590
|
+
active: z6.boolean()
|
|
3548
3591
|
}).strict();
|
|
3549
|
-
var registryModuleSchema =
|
|
3592
|
+
var registryModuleSchema = z6.object({
|
|
3550
3593
|
name: moduleNameSchema,
|
|
3551
3594
|
/** Installed version as recorded by the rail (non-empty string). */
|
|
3552
|
-
version:
|
|
3595
|
+
version: z6.string().min(1),
|
|
3553
3596
|
home: projectSlugSchema2,
|
|
3554
3597
|
kind: moduleKindSchema,
|
|
3555
|
-
mounts:
|
|
3556
|
-
enhancements:
|
|
3598
|
+
mounts: z6.array(mountSchema),
|
|
3599
|
+
enhancements: z6.array(registryEnhancementSchema)
|
|
3557
3600
|
}).strict().superRefine((mod, ctx) => {
|
|
3558
3601
|
for (let i = 0; i < mod.enhancements.length; i += 1) {
|
|
3559
3602
|
if (mod.enhancements[i].peer === mod.name) {
|
|
3560
3603
|
ctx.addIssue({
|
|
3561
|
-
code:
|
|
3604
|
+
code: z6.ZodIssueCode.custom,
|
|
3562
3605
|
message: "enhancement peer must not equal the module's own name",
|
|
3563
3606
|
path: ["enhancements", i, "peer"]
|
|
3564
3607
|
});
|
|
3565
3608
|
}
|
|
3566
3609
|
}
|
|
3567
3610
|
});
|
|
3568
|
-
var generatedModuleRegistrySchema =
|
|
3569
|
-
registryVersion:
|
|
3611
|
+
var generatedModuleRegistrySchema = z6.object({
|
|
3612
|
+
registryVersion: z6.literal(1),
|
|
3570
3613
|
/** Rail run id, or `"manual"` for fixtures. */
|
|
3571
|
-
generatedBy:
|
|
3572
|
-
modules:
|
|
3614
|
+
generatedBy: z6.string().min(1),
|
|
3615
|
+
modules: z6.array(registryModuleSchema)
|
|
3573
3616
|
}).strict().superRefine((registry, ctx) => {
|
|
3574
3617
|
const seen = /* @__PURE__ */ new Set();
|
|
3575
3618
|
for (let i = 0; i < registry.modules.length; i += 1) {
|
|
3576
3619
|
const name = registry.modules[i].name;
|
|
3577
3620
|
if (seen.has(name)) {
|
|
3578
3621
|
ctx.addIssue({
|
|
3579
|
-
code:
|
|
3622
|
+
code: z6.ZodIssueCode.custom,
|
|
3580
3623
|
message: `duplicate module name "${name}"`,
|
|
3581
3624
|
path: ["modules", i, "name"]
|
|
3582
3625
|
});
|
|
@@ -3947,169 +3990,6 @@ function analyzeClientSafeEntryImports(sourceText, fileName) {
|
|
|
3947
3990
|
return analyzeClientSafeEntryImportsWithTs(ts3, sourceText, fileName);
|
|
3948
3991
|
}
|
|
3949
3992
|
|
|
3950
|
-
// ../../packages/lib/src/shared/module-composition.ts
|
|
3951
|
-
var moduleRefSchema = z6.object({
|
|
3952
|
-
homeProjectId: z6.string().uuid(),
|
|
3953
|
-
moduleName: moduleNameSchema
|
|
3954
|
-
});
|
|
3955
|
-
var creationSourceBlankSchema = z6.object({
|
|
3956
|
-
type: z6.literal("blank")
|
|
3957
|
-
});
|
|
3958
|
-
var creationSourceBlueprintSchema = z6.object({
|
|
3959
|
-
type: z6.literal("blueprint"),
|
|
3960
|
-
/** Internal template/fork project id — keep template naming in identifiers. */
|
|
3961
|
-
blueprintProjectId: z6.string().uuid(),
|
|
3962
|
-
/**
|
|
3963
|
-
* `null`/absent means every feature Module. `[]` is refused: the platform
|
|
3964
|
-
* serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
|
|
3965
|
-
* enablement parser reads back as "all" (`module-enablement.ts`) — the
|
|
3966
|
-
* opposite of what an empty selection means.
|
|
3967
|
-
*/
|
|
3968
|
-
enabledModules: z6.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
|
|
3969
|
-
});
|
|
3970
|
-
var creationSourceModulesSchema = z6.object({
|
|
3971
|
-
type: z6.literal("modules"),
|
|
3972
|
-
requested: z6.array(moduleRefSchema).min(1)
|
|
3973
|
-
});
|
|
3974
|
-
var creationSourceSchema = z6.discriminatedUnion("type", [
|
|
3975
|
-
creationSourceBlankSchema,
|
|
3976
|
-
creationSourceBlueprintSchema,
|
|
3977
|
-
creationSourceModulesSchema
|
|
3978
|
-
]);
|
|
3979
|
-
var moduleCompositionStatusSchema = z6.enum([
|
|
3980
|
-
"pending",
|
|
3981
|
-
"installing",
|
|
3982
|
-
"ready",
|
|
3983
|
-
"partial",
|
|
3984
|
-
"failed"
|
|
3985
|
-
]);
|
|
3986
|
-
var resolvedModuleSnapshotSchema = moduleRefSchema.extend({
|
|
3987
|
-
version: z6.string().min(1),
|
|
3988
|
-
homeCommitSha: z6.string().min(1)
|
|
3989
|
-
});
|
|
3990
|
-
var moduleInstallHandoffSchema = z6.object({
|
|
3991
|
-
moduleName: z6.string().min(1),
|
|
3992
|
-
homeProjectId: z6.string().uuid(),
|
|
3993
|
-
installedVersion: z6.string().min(1),
|
|
3994
|
-
baselineSha: z6.string().min(1),
|
|
3995
|
-
mounts: z6.array(
|
|
3996
|
-
z6.object({
|
|
3997
|
-
group: z6.string(),
|
|
3998
|
-
segment: z6.string()
|
|
3999
|
-
})
|
|
4000
|
-
),
|
|
4001
|
-
integration: z6.array(z6.string()),
|
|
4002
|
-
workflows: z6.array(z6.string()),
|
|
4003
|
-
prunedEnhancements: z6.array(z6.string()),
|
|
4004
|
-
restoredEnhancements: z6.array(z6.string()).optional(),
|
|
4005
|
-
permissions: z6.array(
|
|
4006
|
-
z6.object({
|
|
4007
|
-
key: z6.string().min(1),
|
|
4008
|
-
description: z6.string().min(1)
|
|
4009
|
-
})
|
|
4010
|
-
).optional()
|
|
4011
|
-
});
|
|
4012
|
-
var moduleCompositionRunAttemptSchema = z6.object({
|
|
4013
|
-
module: moduleRefSchema,
|
|
4014
|
-
runId: z6.string().min(1),
|
|
4015
|
-
status: z6.string().min(1),
|
|
4016
|
-
schemaApplied: z6.boolean(),
|
|
4017
|
-
error: z6.string().optional(),
|
|
4018
|
-
/** Prefer full ModuleInstallHandoff; identity fields optional for older rows. */
|
|
4019
|
-
handoff: moduleInstallHandoffSchema.partial({ moduleName: true, homeProjectId: true }).optional()
|
|
4020
|
-
});
|
|
4021
|
-
var initialModuleCompositionSchema = z6.object({
|
|
4022
|
-
version: z6.literal(1),
|
|
4023
|
-
status: moduleCompositionStatusSchema,
|
|
4024
|
-
requested: z6.array(moduleRefSchema).min(1),
|
|
4025
|
-
resolved: z6.array(resolvedModuleSnapshotSchema),
|
|
4026
|
-
installOrder: z6.array(moduleRefSchema),
|
|
4027
|
-
runs: z6.array(moduleCompositionRunAttemptSchema).default([]),
|
|
4028
|
-
/**
|
|
4029
|
-
* True when any failed/partial attempt applied schema before failing.
|
|
4030
|
-
* Never claim transactional rollback across git/schema.
|
|
4031
|
-
*/
|
|
4032
|
-
schemaApplied: z6.boolean().default(false),
|
|
4033
|
-
/**
|
|
4034
|
-
* Present when retry would silently change pinned home SHAs — caller must
|
|
4035
|
-
* reconfirm the new plan rather than continue.
|
|
4036
|
-
*/
|
|
4037
|
-
reconfirmationRequired: z6.object({
|
|
4038
|
-
reason: z6.string(),
|
|
4039
|
-
previous: z6.array(resolvedModuleSnapshotSchema),
|
|
4040
|
-
next: z6.array(resolvedModuleSnapshotSchema)
|
|
4041
|
-
}).optional(),
|
|
4042
|
-
error: z6.string().optional(),
|
|
4043
|
-
/**
|
|
4044
|
-
* First-turn system handoff lifecycle. Absent/`pending` means inject when
|
|
4045
|
-
* status is ready; `consumed` means a successful agent turn already used it.
|
|
4046
|
-
*/
|
|
4047
|
-
systemHandoffStatus: z6.enum(["pending", "consumed"]).optional(),
|
|
4048
|
-
/**
|
|
4049
|
-
* CAS ownership token for concurrent retry attempts. Only the attempt that
|
|
4050
|
-
* currently owns this value may reset or compensate durable composition state.
|
|
4051
|
-
*/
|
|
4052
|
-
retryAttemptId: z6.string().min(1).optional(),
|
|
4053
|
-
/**
|
|
4054
|
-
* When the current `installing` attempt was claimed. A setup run that dies
|
|
4055
|
-
* with its host (a killed Vercel instance) leaves the composition `installing`
|
|
4056
|
-
* forever, and the creating page's poll only stops on ready/partial/failed —
|
|
4057
|
-
* so staleness, not a queue's stall detector, is what settles it. Absent on
|
|
4058
|
-
* rows claimed before this field existed; treated as stale.
|
|
4059
|
-
*/
|
|
4060
|
-
attemptStartedAt: z6.string().datetime().optional(),
|
|
4061
|
-
/** Outside-module files the agent changed during connection work. */
|
|
4062
|
-
integrationFiles: z6.array(z6.string()).optional(),
|
|
4063
|
-
verificationSummary: z6.string().optional()
|
|
4064
|
-
});
|
|
4065
|
-
var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
|
|
4066
|
-
|
|
4067
|
-
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
4068
|
-
import { z as z7 } from "zod";
|
|
4069
|
-
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
4070
|
-
var composeInputsSchema = z7.strictObject({
|
|
4071
|
-
connectedStores: z7.array(
|
|
4072
|
-
z7.strictObject({
|
|
4073
|
-
storeId: z7.string().min(1),
|
|
4074
|
-
slug: z7.string().min(1),
|
|
4075
|
-
// A row with an empty binding key must not fail a deploy: the rail
|
|
4076
|
-
// treats falsy as "no binding key", so normalise rather than reject.
|
|
4077
|
-
bindingKey: z7.string().nullish().transform((value) => value || null),
|
|
4078
|
-
accessLevel: z7.enum(["read", "write", "admin"]),
|
|
4079
|
-
// Defaulted so a file written before the platform emitted store types
|
|
4080
|
-
// (database rows only) still parses. The platform starts writing
|
|
4081
|
-
// storage rows, with `type` set, once forks run a compose that reads it.
|
|
4082
|
-
type: z7.enum(["database", "storage"]).default("database")
|
|
4083
|
-
})
|
|
4084
|
-
).default([]),
|
|
4085
|
-
installs: z7.record(
|
|
4086
|
-
z7.string(),
|
|
4087
|
-
z7.strictObject({
|
|
4088
|
-
installedVersion: z7.string().min(1),
|
|
4089
|
-
datastoreBindings: z7.record(z7.string(), z7.string()).nullish()
|
|
4090
|
-
})
|
|
4091
|
-
).default({})
|
|
4092
|
-
});
|
|
4093
|
-
var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
4094
|
-
var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
4095
|
-
function escapeStubIgnorePattern(pattern) {
|
|
4096
|
-
const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
|
|
4097
|
-
return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
|
|
4098
|
-
}
|
|
4099
|
-
function buildStubIgnore(artifactPaths) {
|
|
4100
|
-
const prefix = "apps/web/src/app/";
|
|
4101
|
-
const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
|
|
4102
|
-
return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
4103
|
-
}
|
|
4104
|
-
|
|
4105
|
-
// ../../packages/lib/src/shared/module-enablement.ts
|
|
4106
|
-
function isModuleEnabledByList(raw, name, kindOf) {
|
|
4107
|
-
if (!raw) return true;
|
|
4108
|
-
const kind = kindOf(name);
|
|
4109
|
-
if (kind != null && kind !== "feature") return true;
|
|
4110
|
-
return raw.split(",").includes(name);
|
|
4111
|
-
}
|
|
4112
|
-
|
|
4113
3993
|
// ../../packages/lib/src/shared/platform-owned-paths.ts
|
|
4114
3994
|
var APP_WEB_PREFIX = "apps/web/";
|
|
4115
3995
|
var MODULE_STUB_MARKER = "@stardeck-module-rail-generated";
|
|
@@ -4256,13 +4136,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4256
4136
|
function createMessageTree() {
|
|
4257
4137
|
return /* @__PURE__ */ Object.create(null);
|
|
4258
4138
|
}
|
|
4259
|
-
function assertMessageTree(value,
|
|
4139
|
+
function assertMessageTree(value, path4) {
|
|
4260
4140
|
if (typeof value === "string") return;
|
|
4261
4141
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4262
|
-
throw new Error(`Invalid message at ${
|
|
4142
|
+
throw new Error(`Invalid message at ${path4}: expected string or nested object`);
|
|
4263
4143
|
}
|
|
4264
4144
|
for (const [key, child] of Object.entries(value)) {
|
|
4265
|
-
assertMessageTree(child, `${
|
|
4145
|
+
assertMessageTree(child, `${path4}.${key}`);
|
|
4266
4146
|
}
|
|
4267
4147
|
}
|
|
4268
4148
|
function cloneTree(value) {
|
|
@@ -4272,17 +4152,17 @@ function cloneTree(value) {
|
|
|
4272
4152
|
}
|
|
4273
4153
|
return clone;
|
|
4274
4154
|
}
|
|
4275
|
-
function collectLeafPaths(value,
|
|
4155
|
+
function collectLeafPaths(value, path4, out) {
|
|
4276
4156
|
if (typeof value === "string") {
|
|
4277
|
-
out.push(
|
|
4157
|
+
out.push(path4);
|
|
4278
4158
|
return;
|
|
4279
4159
|
}
|
|
4280
|
-
assertMessageTree(value,
|
|
4281
|
-
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${
|
|
4160
|
+
assertMessageTree(value, path4);
|
|
4161
|
+
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
|
|
4282
4162
|
}
|
|
4283
|
-
function overrideInto(target, source, owner,
|
|
4163
|
+
function overrideInto(target, source, owner, path4, missing) {
|
|
4284
4164
|
for (const [key, value] of Object.entries(source)) {
|
|
4285
|
-
const nextPath =
|
|
4165
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4286
4166
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4287
4167
|
collectLeafPaths(value, nextPath, missing);
|
|
4288
4168
|
continue;
|
|
@@ -4309,9 +4189,9 @@ function overrideInto(target, source, owner, path3, missing) {
|
|
|
4309
4189
|
function composeLocaleMessagesForPlan(layers) {
|
|
4310
4190
|
const result = createMessageTree();
|
|
4311
4191
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4312
|
-
function mergeInto(target, source, owner,
|
|
4192
|
+
function mergeInto(target, source, owner, path4) {
|
|
4313
4193
|
for (const [key, value] of Object.entries(source)) {
|
|
4314
|
-
const nextPath =
|
|
4194
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4315
4195
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4316
4196
|
const existing = hasExisting ? target[key] : void 0;
|
|
4317
4197
|
if (typeof value === "string") {
|
|
@@ -4855,8 +4735,8 @@ var METHOD_ORDER = {
|
|
|
4855
4735
|
function compareNames2(a, b) {
|
|
4856
4736
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4857
4737
|
}
|
|
4858
|
-
function endpointPairKey(method,
|
|
4859
|
-
return `${method} ${
|
|
4738
|
+
function endpointPairKey(method, path4) {
|
|
4739
|
+
return `${method} ${path4}`;
|
|
4860
4740
|
}
|
|
4861
4741
|
function apiPathToRouteFile(apiPath) {
|
|
4862
4742
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4870,11 +4750,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4870
4750
|
}
|
|
4871
4751
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4872
4752
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4873
|
-
function appRouteFileToNextRelativePath(
|
|
4874
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
4753
|
+
function appRouteFileToNextRelativePath(path4) {
|
|
4754
|
+
const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
4875
4755
|
if (pagePath != null) return pagePath;
|
|
4876
|
-
if (!
|
|
4877
|
-
const relative =
|
|
4756
|
+
if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
4757
|
+
const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4878
4758
|
if (!relative.startsWith("api/")) return null;
|
|
4879
4759
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4880
4760
|
if (!suffix) return null;
|
|
@@ -4966,9 +4846,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4966
4846
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4967
4847
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4968
4848
|
}
|
|
4969
|
-
function parseLegacyApiAdapter(
|
|
4970
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4971
|
-
if (expectedMarkerKindForPath(
|
|
4849
|
+
function parseLegacyApiAdapter(path4, source) {
|
|
4850
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
4851
|
+
if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
|
|
4972
4852
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4973
4853
|
const match = firstLine.match(
|
|
4974
4854
|
new RegExp(
|
|
@@ -4978,7 +4858,7 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4978
4858
|
if (!match || match[2] !== physicalPath) return null;
|
|
4979
4859
|
const owner = match[1];
|
|
4980
4860
|
if (!isValidModuleName(owner)) return null;
|
|
4981
|
-
const file = ts4.createSourceFile(
|
|
4861
|
+
const file = ts4.createSourceFile(path4, source, ts4.ScriptTarget.Latest, false, ts4.ScriptKind.TS);
|
|
4982
4862
|
if (file.parseDiagnostics.length > 0) {
|
|
4983
4863
|
return null;
|
|
4984
4864
|
}
|
|
@@ -5020,9 +4900,9 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
5020
4900
|
}
|
|
5021
4901
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
5022
4902
|
}
|
|
5023
|
-
function parseLegacyRouteAdapter(
|
|
5024
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
5025
|
-
if (expectedMarkerKindForPath(
|
|
4903
|
+
function parseLegacyRouteAdapter(path4, source) {
|
|
4904
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
4905
|
+
if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
|
|
5026
4906
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
5027
4907
|
const match = normalized.match(
|
|
5028
4908
|
new RegExp(
|
|
@@ -5034,30 +4914,30 @@ function parseLegacyRouteAdapter(path3, source) {
|
|
|
5034
4914
|
const nextRelativePath = match[2];
|
|
5035
4915
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
5036
4916
|
}
|
|
5037
|
-
function legacyRouteAdapterMatchesStub(
|
|
5038
|
-
const legacy = parseLegacyRouteAdapter(
|
|
4917
|
+
function legacyRouteAdapterMatchesStub(path4, source, stub) {
|
|
4918
|
+
const legacy = parseLegacyRouteAdapter(path4, source);
|
|
5039
4919
|
return legacy != null && legacy.owner === stub.owner;
|
|
5040
4920
|
}
|
|
5041
4921
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
5042
4922
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
5043
4923
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
5044
|
-
function expectedMarkerKindForPath(
|
|
5045
|
-
if (
|
|
5046
|
-
if (
|
|
5047
|
-
if (
|
|
5048
|
-
if (
|
|
5049
|
-
if (
|
|
5050
|
-
if (
|
|
5051
|
-
if (
|
|
5052
|
-
if (
|
|
4924
|
+
function expectedMarkerKindForPath(path4) {
|
|
4925
|
+
if (path4 === MODULES_GEN_PATH) return "registry";
|
|
4926
|
+
if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
|
|
4927
|
+
if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
|
|
4928
|
+
if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
|
|
4929
|
+
if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
|
|
4930
|
+
if (path4 === MODULE_SERVER_GEN_PATH) return "server";
|
|
4931
|
+
if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
|
|
4932
|
+
if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
|
|
5053
4933
|
return "route";
|
|
5054
4934
|
}
|
|
5055
4935
|
return null;
|
|
5056
4936
|
}
|
|
5057
|
-
function markerCongruentWithPath(
|
|
5058
|
-
if (expectedMarkerKindForPath(
|
|
4937
|
+
function markerCongruentWithPath(path4, marker) {
|
|
4938
|
+
if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
|
|
5059
4939
|
if (marker.kind === "route") {
|
|
5060
|
-
const derived = appPageFileToNextRelativePath(
|
|
4940
|
+
const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
5061
4941
|
return derived != null && derived === marker.nextRelativePath;
|
|
5062
4942
|
}
|
|
5063
4943
|
return true;
|
|
@@ -5328,8 +5208,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5328
5208
|
function activeEnhancementPeers(registryModule) {
|
|
5329
5209
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5330
5210
|
}
|
|
5331
|
-
function endpointBucketKey(
|
|
5332
|
-
return `${
|
|
5211
|
+
function endpointBucketKey(path4, owner, scope) {
|
|
5212
|
+
return `${path4}\0${owner}\0${scopeLabel(scope)}`;
|
|
5333
5213
|
}
|
|
5334
5214
|
function planRouteStubs(input) {
|
|
5335
5215
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5453,7 +5333,7 @@ function planRouteStubs(input) {
|
|
|
5453
5333
|
for (const entry of entries) {
|
|
5454
5334
|
const nextRelativePath = entry.nextRelativePath;
|
|
5455
5335
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5456
|
-
const
|
|
5336
|
+
const path4 = routeEntryToAppPageFile(nextRelativePath);
|
|
5457
5337
|
const existing = routeDestinationOwners.get(destination);
|
|
5458
5338
|
if (existing) {
|
|
5459
5339
|
throw new Error(
|
|
@@ -5463,13 +5343,13 @@ function planRouteStubs(input) {
|
|
|
5463
5343
|
routeDestinationOwners.set(destination, {
|
|
5464
5344
|
owner: regMod.name,
|
|
5465
5345
|
nextRelativePath,
|
|
5466
|
-
path:
|
|
5346
|
+
path: path4
|
|
5467
5347
|
});
|
|
5468
5348
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5469
5349
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5470
5350
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5471
5351
|
routeStubs.push({
|
|
5472
|
-
path:
|
|
5352
|
+
path: path4,
|
|
5473
5353
|
owner: regMod.name,
|
|
5474
5354
|
nextRelativePath,
|
|
5475
5355
|
importModule,
|
|
@@ -5530,16 +5410,16 @@ function planCompositionArtifacts(input) {
|
|
|
5530
5410
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5531
5411
|
]);
|
|
5532
5412
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5533
|
-
const isProvenStaleDeletionCandidate = (
|
|
5534
|
-
if (
|
|
5535
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5536
|
-
if (desiredPaths.has(
|
|
5537
|
-
if (!existingGeneratedPathSet.has(
|
|
5538
|
-
const legacy = parseLegacyApiAdapter(
|
|
5413
|
+
const isProvenStaleDeletionCandidate = (path4, content) => {
|
|
5414
|
+
if (path4 === MODULES_GEN_PATH) return false;
|
|
5415
|
+
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
|
|
5416
|
+
if (desiredPaths.has(path4)) return false;
|
|
5417
|
+
if (!existingGeneratedPathSet.has(path4)) return false;
|
|
5418
|
+
const legacy = parseLegacyApiAdapter(path4, content);
|
|
5539
5419
|
if (legacy) return true;
|
|
5540
|
-
if (parseLegacyRouteAdapter(
|
|
5420
|
+
if (parseLegacyRouteAdapter(path4, content)) return true;
|
|
5541
5421
|
const marker = parseCompositionArtifactMarker(content);
|
|
5542
|
-
return marker != null && markerCongruentWithPath(
|
|
5422
|
+
return marker != null && markerCongruentWithPath(path4, marker);
|
|
5543
5423
|
};
|
|
5544
5424
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5545
5425
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5693,13 +5573,13 @@ function planCompositionArtifacts(input) {
|
|
|
5693
5573
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5694
5574
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5695
5575
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5696
|
-
for (const [
|
|
5697
|
-
const existing = input.existingFiles[
|
|
5576
|
+
for (const [path4] of desired) {
|
|
5577
|
+
const existing = input.existingFiles[path4];
|
|
5698
5578
|
if (existing == null) continue;
|
|
5699
5579
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5700
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5701
|
-
const plannedRoute = routeStubByPath.get(
|
|
5702
|
-
const legacy = parseLegacyApiAdapter(
|
|
5580
|
+
const plannedEndpoint = endpointStubByPath.get(path4);
|
|
5581
|
+
const plannedRoute = routeStubByPath.get(path4);
|
|
5582
|
+
const legacy = parseLegacyApiAdapter(path4, existing);
|
|
5703
5583
|
if (legacy && plannedEndpoint) {
|
|
5704
5584
|
marker = {
|
|
5705
5585
|
kind: "endpoint",
|
|
@@ -5707,66 +5587,66 @@ function planCompositionArtifacts(input) {
|
|
|
5707
5587
|
scope: plannedEndpoint.scope
|
|
5708
5588
|
};
|
|
5709
5589
|
}
|
|
5710
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5590
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
|
|
5711
5591
|
marker = {
|
|
5712
5592
|
kind: "route",
|
|
5713
5593
|
owner: plannedRoute.owner,
|
|
5714
5594
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5715
5595
|
};
|
|
5716
5596
|
}
|
|
5717
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5597
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
|
|
5718
5598
|
if (!marker) {
|
|
5719
5599
|
return {
|
|
5720
5600
|
ok: false,
|
|
5721
|
-
error:
|
|
5722
|
-
path:
|
|
5601
|
+
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}`,
|
|
5602
|
+
path: path4
|
|
5723
5603
|
};
|
|
5724
5604
|
}
|
|
5725
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5605
|
+
const expectedKind = expectedMarkerKindForPath(path4);
|
|
5726
5606
|
if (marker.kind !== expectedKind) {
|
|
5727
5607
|
return {
|
|
5728
5608
|
ok: false,
|
|
5729
|
-
error:
|
|
5730
|
-
path:
|
|
5609
|
+
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)`,
|
|
5610
|
+
path: path4
|
|
5731
5611
|
};
|
|
5732
5612
|
}
|
|
5733
5613
|
if (marker.kind === "endpoint") {
|
|
5734
|
-
const stub = endpointStubByPath.get(
|
|
5614
|
+
const stub = endpointStubByPath.get(path4);
|
|
5735
5615
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5736
5616
|
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5737
5617
|
if (!ownerMatches || !scopeMatches) {
|
|
5738
5618
|
return {
|
|
5739
5619
|
ok: false,
|
|
5740
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5741
|
-
path:
|
|
5620
|
+
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) : "?"})`,
|
|
5621
|
+
path: path4,
|
|
5742
5622
|
owner: stub?.owner
|
|
5743
5623
|
};
|
|
5744
5624
|
}
|
|
5745
5625
|
} else if (marker.kind === "route") {
|
|
5746
|
-
const stub = routeStubByPath.get(
|
|
5626
|
+
const stub = routeStubByPath.get(path4);
|
|
5747
5627
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5748
5628
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5749
5629
|
if (!ownerMatches || !pathMatches) {
|
|
5750
5630
|
return {
|
|
5751
5631
|
ok: false,
|
|
5752
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5753
|
-
path:
|
|
5632
|
+
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 ?? "?"})`,
|
|
5633
|
+
path: path4,
|
|
5754
5634
|
owner: stub?.owner
|
|
5755
5635
|
};
|
|
5756
5636
|
}
|
|
5757
5637
|
}
|
|
5758
5638
|
}
|
|
5759
5639
|
const deletes = [];
|
|
5760
|
-
for (const
|
|
5761
|
-
const existing = input.existingFiles[
|
|
5640
|
+
for (const path4 of input.existingGeneratedPaths) {
|
|
5641
|
+
const existing = input.existingFiles[path4];
|
|
5762
5642
|
if (existing == null) continue;
|
|
5763
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5643
|
+
if (!isProvenStaleDeletionCandidate(path4, existing)) {
|
|
5764
5644
|
continue;
|
|
5765
5645
|
}
|
|
5766
|
-
deletes.push(
|
|
5646
|
+
deletes.push(path4);
|
|
5767
5647
|
}
|
|
5768
5648
|
deletes.sort(compareNames2);
|
|
5769
|
-
const writes = [...desired.entries()].filter(([
|
|
5649
|
+
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));
|
|
5770
5650
|
return {
|
|
5771
5651
|
ok: true,
|
|
5772
5652
|
plan: {
|
|
@@ -5795,8 +5675,16 @@ async function checkoutParsesTypedStores(sandbox) {
|
|
|
5795
5675
|
try {
|
|
5796
5676
|
if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
|
|
5797
5677
|
const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
|
|
5798
|
-
|
|
5799
|
-
|
|
5678
|
+
return composeRangeParsesTypedStores(
|
|
5679
|
+
pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
|
|
5680
|
+
);
|
|
5681
|
+
} catch {
|
|
5682
|
+
return false;
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
function composeRangeParsesTypedStores(range) {
|
|
5686
|
+
if (!range) return false;
|
|
5687
|
+
try {
|
|
5800
5688
|
const lowest = import_semver.default.minVersion(range);
|
|
5801
5689
|
return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
|
|
5802
5690
|
} catch {
|
|
@@ -6028,6 +5916,50 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
|
|
|
6028
5916
|
return dirs;
|
|
6029
5917
|
}
|
|
6030
5918
|
|
|
5919
|
+
// ../../packages/lib/src/shared/module-composition.ts
|
|
5920
|
+
import { z as z7 } from "zod";
|
|
5921
|
+
var creationSourceBlankSchema = z7.object({
|
|
5922
|
+
type: z7.literal("blank")
|
|
5923
|
+
});
|
|
5924
|
+
var creationSourceBlueprintSchema = z7.object({
|
|
5925
|
+
type: z7.literal("blueprint"),
|
|
5926
|
+
/** Internal template/fork project id — keep template naming in identifiers. */
|
|
5927
|
+
blueprintProjectId: z7.string().uuid(),
|
|
5928
|
+
/**
|
|
5929
|
+
* `null`/absent means every feature Module. `[]` is refused: the platform
|
|
5930
|
+
* serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
|
|
5931
|
+
* enablement parser reads back as "all" (`module-enablement.ts`) — the
|
|
5932
|
+
* opposite of what an empty selection means.
|
|
5933
|
+
*/
|
|
5934
|
+
enabledModules: z7.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
|
|
5935
|
+
});
|
|
5936
|
+
var creationSourceSchema = z7.discriminatedUnion("type", [
|
|
5937
|
+
creationSourceBlankSchema,
|
|
5938
|
+
creationSourceBlueprintSchema
|
|
5939
|
+
]);
|
|
5940
|
+
var moduleInstallHandoffSchema = z7.object({
|
|
5941
|
+
moduleName: z7.string().min(1),
|
|
5942
|
+
homeProjectId: z7.string().uuid(),
|
|
5943
|
+
installedVersion: z7.string().min(1),
|
|
5944
|
+
baselineSha: z7.string().min(1),
|
|
5945
|
+
mounts: z7.array(
|
|
5946
|
+
z7.object({
|
|
5947
|
+
group: z7.string(),
|
|
5948
|
+
segment: z7.string()
|
|
5949
|
+
})
|
|
5950
|
+
),
|
|
5951
|
+
integration: z7.array(z7.string()),
|
|
5952
|
+
workflows: z7.array(z7.string()),
|
|
5953
|
+
prunedEnhancements: z7.array(z7.string()),
|
|
5954
|
+
restoredEnhancements: z7.array(z7.string()).optional(),
|
|
5955
|
+
permissions: z7.array(
|
|
5956
|
+
z7.object({
|
|
5957
|
+
key: z7.string().min(1),
|
|
5958
|
+
description: z7.string().min(1)
|
|
5959
|
+
})
|
|
5960
|
+
).optional()
|
|
5961
|
+
});
|
|
5962
|
+
|
|
6031
5963
|
// ../../packages/lib/src/shared/schema-ops.ts
|
|
6032
5964
|
import { z as z8 } from "zod";
|
|
6033
5965
|
var columnSpecSchema = z8.object({
|
|
@@ -6241,11 +6173,11 @@ function countedCompositionContext(context) {
|
|
|
6241
6173
|
counts.sandboxExecs += 1;
|
|
6242
6174
|
return context.sandbox.exec(...args);
|
|
6243
6175
|
},
|
|
6244
|
-
readFile: (
|
|
6176
|
+
readFile: (path4) => {
|
|
6245
6177
|
counts.sandboxReads += 1;
|
|
6246
|
-
return context.sandbox.readFile(
|
|
6178
|
+
return context.sandbox.readFile(path4);
|
|
6247
6179
|
},
|
|
6248
|
-
fileExists: (
|
|
6180
|
+
fileExists: (path4) => context.sandbox.fileExists(path4),
|
|
6249
6181
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6250
6182
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6251
6183
|
}
|
|
@@ -6457,14 +6389,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6457
6389
|
}
|
|
6458
6390
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6459
6391
|
}
|
|
6460
|
-
async function writeSandboxFileAtomic(target,
|
|
6461
|
-
const dir = dirnamePosix(
|
|
6462
|
-
const tmp = `${
|
|
6392
|
+
async function writeSandboxFileAtomic(target, path4, content) {
|
|
6393
|
+
const dir = dirnamePosix(path4);
|
|
6394
|
+
const tmp = `${path4}.tmp.${target.runId}`;
|
|
6463
6395
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6464
6396
|
const cmd = [
|
|
6465
6397
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6466
6398
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6467
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6399
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
|
|
6468
6400
|
].join(" && ");
|
|
6469
6401
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6470
6402
|
if (result.exitCode !== 0) {
|
|
@@ -6479,14 +6411,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
|
|
|
6479
6411
|
}
|
|
6480
6412
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6481
6413
|
throw new Error(
|
|
6482
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6414
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6483
6415
|
);
|
|
6484
6416
|
}
|
|
6485
6417
|
}
|
|
6486
6418
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6487
6419
|
const absent = /* @__PURE__ */ new Set();
|
|
6488
|
-
for (const
|
|
6489
|
-
let dir = dirnamePosix(
|
|
6420
|
+
for (const path4 of writePaths) {
|
|
6421
|
+
let dir = dirnamePosix(path4);
|
|
6490
6422
|
while (dir !== "." && dir !== "") {
|
|
6491
6423
|
if (absent.has(dir)) {
|
|
6492
6424
|
dir = dirnamePosix(dir);
|
|
@@ -6523,12 +6455,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6523
6455
|
);
|
|
6524
6456
|
}
|
|
6525
6457
|
}
|
|
6526
|
-
async function deleteSandboxFile(context,
|
|
6527
|
-
const result = await context.sandbox.exec(`rm -f ${shellQuote(
|
|
6458
|
+
async function deleteSandboxFile(context, path4) {
|
|
6459
|
+
const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
|
|
6528
6460
|
if (result.exitCode !== 0) {
|
|
6529
6461
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6530
6462
|
throw new Error(
|
|
6531
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6463
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6532
6464
|
);
|
|
6533
6465
|
}
|
|
6534
6466
|
}
|
|
@@ -6595,37 +6527,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6595
6527
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6596
6528
|
appliedWrites.push({ path: write.path, previous });
|
|
6597
6529
|
}
|
|
6598
|
-
for (const
|
|
6599
|
-
const previous = existingFiles[
|
|
6530
|
+
for (const path4 of plan.deletes) {
|
|
6531
|
+
const previous = existingFiles[path4];
|
|
6600
6532
|
if (previous == null) continue;
|
|
6601
|
-
await deleteSandboxFile(context,
|
|
6602
|
-
appliedDeletes.push({ path:
|
|
6533
|
+
await deleteSandboxFile(context, path4);
|
|
6534
|
+
appliedDeletes.push({ path: path4, previous });
|
|
6603
6535
|
}
|
|
6604
6536
|
} catch (error) {
|
|
6605
6537
|
console.log(
|
|
6606
6538
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6607
6539
|
);
|
|
6608
|
-
for (const { path:
|
|
6540
|
+
for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
|
|
6609
6541
|
try {
|
|
6610
|
-
await writeSandboxFileAtomic(context,
|
|
6542
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6611
6543
|
} catch (rollbackError) {
|
|
6612
6544
|
console.log(
|
|
6613
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(
|
|
6545
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6614
6546
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6615
6547
|
)}`
|
|
6616
6548
|
);
|
|
6617
6549
|
}
|
|
6618
6550
|
}
|
|
6619
|
-
for (const { path:
|
|
6551
|
+
for (const { path: path4, previous } of [...appliedWrites].reverse()) {
|
|
6620
6552
|
try {
|
|
6621
6553
|
if (previous == null) {
|
|
6622
|
-
await deleteSandboxFile(context,
|
|
6554
|
+
await deleteSandboxFile(context, path4);
|
|
6623
6555
|
} else {
|
|
6624
|
-
await writeSandboxFileAtomic(context,
|
|
6556
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6625
6557
|
}
|
|
6626
6558
|
} catch (rollbackError) {
|
|
6627
6559
|
console.log(
|
|
6628
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(
|
|
6560
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6629
6561
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6630
6562
|
)}`
|
|
6631
6563
|
);
|
|
@@ -6659,39 +6591,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6659
6591
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6660
6592
|
);
|
|
6661
6593
|
const presentPaths = [];
|
|
6662
|
-
for (const
|
|
6594
|
+
for (const path4 of candidates) {
|
|
6663
6595
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6664
|
-
|
|
6665
|
-
kinds.get(
|
|
6596
|
+
path4,
|
|
6597
|
+
kinds.get(path4) ?? "unreadable"
|
|
6666
6598
|
);
|
|
6667
|
-
if (presence === "file") presentPaths.push(
|
|
6599
|
+
if (presence === "file") presentPaths.push(path4);
|
|
6668
6600
|
}
|
|
6669
6601
|
let contents;
|
|
6670
6602
|
try {
|
|
6671
6603
|
contents = await readFilesMany(context, presentPaths);
|
|
6672
6604
|
} catch (error) {
|
|
6673
|
-
const
|
|
6605
|
+
const path4 = readFailurePath(error);
|
|
6674
6606
|
throw new Error(
|
|
6675
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6607
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
|
|
6676
6608
|
error instanceof Error ? error.message : String(error)
|
|
6677
6609
|
)}`
|
|
6678
6610
|
);
|
|
6679
6611
|
}
|
|
6680
6612
|
const owned = [];
|
|
6681
|
-
for (const
|
|
6682
|
-
const content = contents.get(
|
|
6613
|
+
for (const path4 of presentPaths) {
|
|
6614
|
+
const content = contents.get(path4);
|
|
6683
6615
|
const marker = parseCompositionArtifactMarker(content);
|
|
6684
|
-
if (parseLegacyApiAdapter(
|
|
6685
|
-
owned.push(
|
|
6616
|
+
if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
|
|
6617
|
+
owned.push(path4);
|
|
6686
6618
|
continue;
|
|
6687
6619
|
}
|
|
6688
6620
|
if (marker == null) continue;
|
|
6689
|
-
if (!markerCongruentWithPath(
|
|
6621
|
+
if (!markerCongruentWithPath(path4, marker)) {
|
|
6690
6622
|
throw new Error(
|
|
6691
|
-
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(
|
|
6623
|
+
`[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)`
|
|
6692
6624
|
);
|
|
6693
6625
|
}
|
|
6694
|
-
owned.push(
|
|
6626
|
+
owned.push(path4);
|
|
6695
6627
|
}
|
|
6696
6628
|
return owned;
|
|
6697
6629
|
}
|
|
@@ -6824,7 +6756,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6824
6756
|
{ raiseOnError: false }
|
|
6825
6757
|
);
|
|
6826
6758
|
if (result.exitCode !== 0) {
|
|
6827
|
-
for (const
|
|
6759
|
+
for (const path4 of chunk) kinds.set(path4, "unreadable");
|
|
6828
6760
|
continue;
|
|
6829
6761
|
}
|
|
6830
6762
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6835,31 +6767,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6835
6767
|
}
|
|
6836
6768
|
} else {
|
|
6837
6769
|
for (const line of lines) {
|
|
6838
|
-
const [rawKind,
|
|
6770
|
+
const [rawKind, path4] = line.split(" ", 2);
|
|
6839
6771
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6840
|
-
if (
|
|
6841
|
-
kinds.set(
|
|
6772
|
+
if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
6773
|
+
kinds.set(path4, kind);
|
|
6842
6774
|
}
|
|
6843
6775
|
}
|
|
6844
6776
|
}
|
|
6845
|
-
for (const
|
|
6846
|
-
if (!kinds.has(
|
|
6777
|
+
for (const path4 of chunk) {
|
|
6778
|
+
if (!kinds.has(path4)) kinds.set(path4, "unreadable");
|
|
6847
6779
|
}
|
|
6848
6780
|
}
|
|
6849
6781
|
return kinds;
|
|
6850
6782
|
}
|
|
6851
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6852
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
6783
|
+
async function classifySandboxPathPresenceKind(context, path4, probeToken) {
|
|
6784
|
+
return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
|
|
6853
6785
|
}
|
|
6854
6786
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6855
|
-
function assertRealLocaleJsonFilePresence(
|
|
6787
|
+
function assertRealLocaleJsonFilePresence(path4, presence) {
|
|
6856
6788
|
if (presence === "file") return;
|
|
6857
6789
|
if (presence === "absent") {
|
|
6858
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
6790
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
|
|
6859
6791
|
}
|
|
6860
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6792
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6861
6793
|
throw new Error(
|
|
6862
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6794
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
|
|
6863
6795
|
);
|
|
6864
6796
|
}
|
|
6865
6797
|
function splitTaggedLine(line) {
|
|
@@ -6934,11 +6866,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6934
6866
|
}
|
|
6935
6867
|
async function readFilesMany(context, paths) {
|
|
6936
6868
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6937
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
6869
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
|
|
6938
6870
|
try {
|
|
6939
|
-
return { path:
|
|
6871
|
+
return { path: path4, value: await context.sandbox.readFile(path4) };
|
|
6940
6872
|
} catch (error) {
|
|
6941
|
-
return { path:
|
|
6873
|
+
return { path: path4, error };
|
|
6942
6874
|
}
|
|
6943
6875
|
});
|
|
6944
6876
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6961,58 +6893,58 @@ function readFailurePath(error) {
|
|
|
6961
6893
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6962
6894
|
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6963
6895
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6964
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6965
|
-
return classifySandboxPathPresenceKind(context,
|
|
6896
|
+
async function classifyCompositionArtifactPathKind(context, path4) {
|
|
6897
|
+
return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6966
6898
|
}
|
|
6967
|
-
function assertRealCompositionArtifactFilePresence(
|
|
6899
|
+
function assertRealCompositionArtifactFilePresence(path4, presence) {
|
|
6968
6900
|
if (presence === "file") return "file";
|
|
6969
6901
|
if (presence === "absent") return "absent";
|
|
6970
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6902
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6971
6903
|
throw new Error(
|
|
6972
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6904
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
|
|
6973
6905
|
);
|
|
6974
6906
|
}
|
|
6975
|
-
async function readConventionalEntryIfRealFile(
|
|
6976
|
-
const presence = presences.get(
|
|
6977
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6907
|
+
async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
6908
|
+
const presence = presences.get(path4) ?? "unreadable";
|
|
6909
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6978
6910
|
if (presenceError) {
|
|
6979
6911
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6980
6912
|
}
|
|
6981
6913
|
if (presence === "absent") {
|
|
6982
6914
|
return null;
|
|
6983
6915
|
}
|
|
6984
|
-
const source = files.get(
|
|
6916
|
+
const source = files.get(path4);
|
|
6985
6917
|
switch (kind) {
|
|
6986
6918
|
case "contributions":
|
|
6987
|
-
assertContributionsExport(source,
|
|
6988
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6919
|
+
assertContributionsExport(source, path4);
|
|
6920
|
+
assertClientSafeConventionalEntryImports(source, path4, "contributions");
|
|
6989
6921
|
break;
|
|
6990
6922
|
case "slotCatalogs":
|
|
6991
|
-
assertSlotCatalogsExport(source,
|
|
6992
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6923
|
+
assertSlotCatalogsExport(source, path4);
|
|
6924
|
+
assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
|
|
6993
6925
|
break;
|
|
6994
6926
|
case "initializeEnhancement":
|
|
6995
|
-
assertInitializeEnhancementExport(source,
|
|
6927
|
+
assertInitializeEnhancementExport(source, path4);
|
|
6996
6928
|
break;
|
|
6997
6929
|
}
|
|
6998
6930
|
return source;
|
|
6999
6931
|
}
|
|
7000
6932
|
function hasRealServerRegistrations(modulePath, presences) {
|
|
7001
|
-
const
|
|
7002
|
-
return assertRealCompositionArtifactFilePresence(
|
|
6933
|
+
const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
6934
|
+
return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
|
|
7003
6935
|
}
|
|
7004
|
-
async function readJsonObjectFile(
|
|
7005
|
-
const raw = files.get(
|
|
6936
|
+
async function readJsonObjectFile(path4, files) {
|
|
6937
|
+
const raw = files.get(path4);
|
|
7006
6938
|
let parsed;
|
|
7007
6939
|
try {
|
|
7008
6940
|
parsed = JSON.parse(raw);
|
|
7009
6941
|
} catch (error) {
|
|
7010
6942
|
throw new Error(
|
|
7011
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
6943
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
|
|
7012
6944
|
);
|
|
7013
6945
|
}
|
|
7014
6946
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
7015
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
6947
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
|
|
7016
6948
|
}
|
|
7017
6949
|
return parsed;
|
|
7018
6950
|
}
|
|
@@ -7061,11 +6993,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7061
6993
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7062
6994
|
]);
|
|
7063
6995
|
const presentConventionalPaths = [];
|
|
7064
|
-
for (const
|
|
7065
|
-
const presence = conventionalPresences.get(
|
|
7066
|
-
if (presence === "file") presentConventionalPaths.push(
|
|
6996
|
+
for (const path4 of conventionalPaths) {
|
|
6997
|
+
const presence = conventionalPresences.get(path4) ?? "unreadable";
|
|
6998
|
+
if (presence === "file") presentConventionalPaths.push(path4);
|
|
7067
6999
|
}
|
|
7068
|
-
const presentLocalePaths = localePaths.filter((
|
|
7000
|
+
const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
|
|
7069
7001
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7070
7002
|
const substrateLocales = [];
|
|
7071
7003
|
for (const locale of substrateNames) {
|
|
@@ -7163,8 +7095,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7163
7095
|
});
|
|
7164
7096
|
if (rootLocales.length > 0) {
|
|
7165
7097
|
for (const locale of rootLocales) {
|
|
7166
|
-
const
|
|
7167
|
-
assertRealLocaleJsonFilePresence(
|
|
7098
|
+
const path4 = `${modulePath}/i18n/${locale}.json`;
|
|
7099
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7168
7100
|
}
|
|
7169
7101
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7170
7102
|
const localeMessages = { en: enMessages };
|
|
@@ -7188,8 +7120,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7188
7120
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7189
7121
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7190
7122
|
for (const locale of enh.locales) {
|
|
7191
|
-
const
|
|
7192
|
-
assertRealLocaleJsonFilePresence(
|
|
7123
|
+
const path4 = `${peerDir}/i18n/${locale}.json`;
|
|
7124
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7193
7125
|
}
|
|
7194
7126
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7195
7127
|
const localeMessages = { en: enMessages };
|
|
@@ -7390,16 +7322,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7390
7322
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7391
7323
|
const existingFiles = {};
|
|
7392
7324
|
const remainingCandidatePaths = [];
|
|
7393
|
-
for (const
|
|
7394
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7395
|
-
existingFiles[
|
|
7325
|
+
for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
|
|
7326
|
+
if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
|
|
7327
|
+
existingFiles[path4] = existingAppPages[path4];
|
|
7396
7328
|
continue;
|
|
7397
7329
|
}
|
|
7398
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7399
|
-
existingFiles[
|
|
7330
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
|
|
7331
|
+
existingFiles[path4] = existingAppRoutes[path4];
|
|
7400
7332
|
continue;
|
|
7401
7333
|
}
|
|
7402
|
-
remainingCandidatePaths.push(
|
|
7334
|
+
remainingCandidatePaths.push(path4);
|
|
7403
7335
|
}
|
|
7404
7336
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7405
7337
|
context,
|
|
@@ -7407,12 +7339,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7407
7339
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7408
7340
|
);
|
|
7409
7341
|
const presentCandidatePaths = [];
|
|
7410
|
-
for (const
|
|
7342
|
+
for (const path4 of remainingCandidatePaths) {
|
|
7411
7343
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7412
|
-
|
|
7413
|
-
candidateKinds.get(
|
|
7344
|
+
path4,
|
|
7345
|
+
candidateKinds.get(path4) ?? "unreadable"
|
|
7414
7346
|
);
|
|
7415
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7347
|
+
if (presence === "file") presentCandidatePaths.push(path4);
|
|
7416
7348
|
}
|
|
7417
7349
|
try {
|
|
7418
7350
|
Object.assign(
|
|
@@ -7420,9 +7352,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7420
7352
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7421
7353
|
);
|
|
7422
7354
|
} catch (error) {
|
|
7423
|
-
const
|
|
7355
|
+
const path4 = readFailurePath(error);
|
|
7424
7356
|
throw new Error(
|
|
7425
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7357
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
|
|
7426
7358
|
error instanceof Error ? error.message : String(error)
|
|
7427
7359
|
)}`
|
|
7428
7360
|
);
|
|
@@ -7491,14 +7423,142 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7491
7423
|
var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
7492
7424
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
7493
7425
|
|
|
7426
|
+
// src/index.ts
|
|
7427
|
+
import ts6 from "typescript";
|
|
7428
|
+
|
|
7429
|
+
// src/module-boundaries.ts
|
|
7430
|
+
import fs2 from "fs";
|
|
7431
|
+
import path2 from "path";
|
|
7432
|
+
import ts5 from "typescript";
|
|
7433
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
7434
|
+
var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
7435
|
+
var DATA_FILE = /\.(?:sql|json)$/;
|
|
7436
|
+
function dataPathsIn(fileName, source) {
|
|
7437
|
+
if (!/\.(?:sql|json)["'`]/.test(source)) return [];
|
|
7438
|
+
const paths = [];
|
|
7439
|
+
const visit = (node) => {
|
|
7440
|
+
if ((ts5.isStringLiteralLike(node) || ts5.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
|
|
7441
|
+
paths.push(node.text);
|
|
7442
|
+
}
|
|
7443
|
+
ts5.forEachChild(node, visit);
|
|
7444
|
+
};
|
|
7445
|
+
visit(ts5.createSourceFile(fileName, source, ts5.ScriptTarget.Latest, false));
|
|
7446
|
+
return paths;
|
|
7447
|
+
}
|
|
7448
|
+
function scanModule(modulesDir, module) {
|
|
7449
|
+
const scanned = /* @__PURE__ */ new Map();
|
|
7450
|
+
const moduleDir = path2.join(modulesDir, module);
|
|
7451
|
+
if (!fs2.existsSync(moduleDir)) return scanned;
|
|
7452
|
+
const files = fs2.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
|
|
7453
|
+
for (const relative of files) {
|
|
7454
|
+
const absolute = path2.join(moduleDir, relative);
|
|
7455
|
+
if (!fs2.statSync(absolute).isFile()) continue;
|
|
7456
|
+
const source = fs2.readFileSync(absolute, "utf8");
|
|
7457
|
+
const specifiers = ts5.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
|
|
7458
|
+
const dataPaths = dataPathsIn(absolute, source);
|
|
7459
|
+
const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
|
|
7460
|
+
if (spec.startsWith("@/modules/"))
|
|
7461
|
+
return [path2.join(modulesDir, spec.slice("@/modules/".length))];
|
|
7462
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
7463
|
+
return [path2.resolve(path2.dirname(absolute), spec)];
|
|
7464
|
+
}
|
|
7465
|
+
return [];
|
|
7466
|
+
});
|
|
7467
|
+
scanned.set(absolute, {
|
|
7468
|
+
module,
|
|
7469
|
+
relative: relative.split(path2.sep).join("/"),
|
|
7470
|
+
targets,
|
|
7471
|
+
dataPaths
|
|
7472
|
+
});
|
|
7473
|
+
}
|
|
7474
|
+
return scanned;
|
|
7475
|
+
}
|
|
7476
|
+
function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
|
|
7477
|
+
const files = /* @__PURE__ */ new Map();
|
|
7478
|
+
for (const [module, peers] of Object.entries(peersByModule)) {
|
|
7479
|
+
if (peers.length === 0) continue;
|
|
7480
|
+
for (const [absolute, file] of scanModule(modulesDir, module)) files.set(absolute, file);
|
|
7481
|
+
}
|
|
7482
|
+
const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
|
|
7483
|
+
(q) => file.relative.startsWith(`enhancements/${q}/`)
|
|
7484
|
+
);
|
|
7485
|
+
const goneWith = (target, peer) => {
|
|
7486
|
+
const relative = path2.relative(modulesDir, target).split(path2.sep).join("/");
|
|
7487
|
+
return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
|
|
7488
|
+
};
|
|
7489
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
7490
|
+
const queue = [];
|
|
7491
|
+
const taint = (absolute, peer) => {
|
|
7492
|
+
const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
|
|
7493
|
+
if (peers.has(peer)) return;
|
|
7494
|
+
peers.add(peer);
|
|
7495
|
+
tainted.set(absolute, peers);
|
|
7496
|
+
queue.push([absolute, peer]);
|
|
7497
|
+
};
|
|
7498
|
+
const importers = /* @__PURE__ */ new Map();
|
|
7499
|
+
for (const [absolute, file] of files) {
|
|
7500
|
+
for (const target of file.targets) {
|
|
7501
|
+
const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
|
|
7502
|
+
if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
|
|
7503
|
+
}
|
|
7504
|
+
for (const peer of peersByModule[file.module] ?? []) {
|
|
7505
|
+
if (exempt(file, peer)) continue;
|
|
7506
|
+
if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
|
|
7507
|
+
(p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
|
|
7508
|
+
)) {
|
|
7509
|
+
taint(absolute, peer);
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
7514
|
+
const [target, peer] = next;
|
|
7515
|
+
for (const importer of importers.get(target) ?? []) {
|
|
7516
|
+
const file = files.get(importer);
|
|
7517
|
+
if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
|
|
7518
|
+
taint(importer, peer);
|
|
7519
|
+
}
|
|
7520
|
+
}
|
|
7521
|
+
}
|
|
7522
|
+
const violations = [];
|
|
7523
|
+
for (const [absolute, peers] of tainted) {
|
|
7524
|
+
const file = files.get(absolute);
|
|
7525
|
+
for (const peer of peers) {
|
|
7526
|
+
violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
|
|
7530
|
+
}
|
|
7531
|
+
function findOptionalPeerViolations(modulesDir) {
|
|
7532
|
+
if (!fs2.existsSync(modulesDir)) return [];
|
|
7533
|
+
const manifests = {};
|
|
7534
|
+
for (const name of fs2.readdirSync(modulesDir)) {
|
|
7535
|
+
const manifestPath = path2.join(modulesDir, name, "module.json");
|
|
7536
|
+
if (!fs2.existsSync(manifestPath)) continue;
|
|
7537
|
+
manifests[name] = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
|
|
7538
|
+
}
|
|
7539
|
+
const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
|
|
7540
|
+
const peersByModule = {};
|
|
7541
|
+
for (const [name, manifest] of Object.entries(manifests)) {
|
|
7542
|
+
const uses = new Set(manifest.uses ?? []);
|
|
7543
|
+
peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
|
|
7544
|
+
}
|
|
7545
|
+
return findModuleBoundaryViolations(modulesDir, peersByModule);
|
|
7546
|
+
}
|
|
7547
|
+
function formatBoundaryViolations(modulesDirLabel, violations) {
|
|
7548
|
+
const files = new Set(violations.map((v) => v.file)).size;
|
|
7549
|
+
return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
|
|
7550
|
+
\`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).
|
|
7551
|
+
` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
|
|
7552
|
+
}
|
|
7553
|
+
|
|
7494
7554
|
// src/index.ts
|
|
7495
7555
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7496
7556
|
function readComposeInputs(cwd) {
|
|
7497
|
-
const file =
|
|
7498
|
-
if (!
|
|
7557
|
+
const file = path3.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7558
|
+
if (!fs3.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7499
7559
|
let raw;
|
|
7500
7560
|
try {
|
|
7501
|
-
raw = JSON.parse(
|
|
7561
|
+
raw = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
7502
7562
|
} catch (error) {
|
|
7503
7563
|
throw new Error(
|
|
7504
7564
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7540,18 +7600,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7540
7600
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7541
7601
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7542
7602
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7543
|
-
const file =
|
|
7544
|
-
const existing =
|
|
7603
|
+
const file = path3.join(cwd, STUB_GITIGNORE_PATH);
|
|
7604
|
+
const existing = fs3.existsSync(file) ? fs3.readFileSync(file, "utf8") : null;
|
|
7545
7605
|
if (existing !== content) {
|
|
7546
|
-
|
|
7547
|
-
|
|
7606
|
+
fs3.mkdirSync(path3.dirname(file), { recursive: true });
|
|
7607
|
+
fs3.writeFileSync(file, content);
|
|
7548
7608
|
}
|
|
7549
7609
|
return entries.length;
|
|
7550
7610
|
}
|
|
7551
7611
|
function assertStubGitignoreOwned(cwd) {
|
|
7552
|
-
const file =
|
|
7553
|
-
if (!
|
|
7554
|
-
const firstLine =
|
|
7612
|
+
const file = path3.join(cwd, STUB_GITIGNORE_PATH);
|
|
7613
|
+
if (!fs3.existsSync(file)) return;
|
|
7614
|
+
const firstLine = fs3.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
|
|
7555
7615
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7556
7616
|
throw new Error(
|
|
7557
7617
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7559,8 +7619,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7559
7619
|
}
|
|
7560
7620
|
}
|
|
7561
7621
|
function assertAppRoot(cwd) {
|
|
7562
|
-
const appPath =
|
|
7563
|
-
if (!
|
|
7622
|
+
const appPath = path3.join(cwd, APP_PACKAGE_JSON);
|
|
7623
|
+
if (!fs3.existsSync(appPath)) {
|
|
7564
7624
|
throw new Error(
|
|
7565
7625
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7566
7626
|
);
|
|
@@ -7606,9 +7666,9 @@ ${porcelain.trim()}`
|
|
|
7606
7666
|
}
|
|
7607
7667
|
var APP_SRC_DIR = "apps/web/src";
|
|
7608
7668
|
function walkSources(dir, skip, out) {
|
|
7609
|
-
if (!
|
|
7610
|
-
for (const entry of
|
|
7611
|
-
const full =
|
|
7669
|
+
if (!fs3.existsSync(dir)) return;
|
|
7670
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
7671
|
+
const full = path3.join(dir, entry.name);
|
|
7612
7672
|
if (full === skip) continue;
|
|
7613
7673
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7614
7674
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7617,13 +7677,13 @@ function walkSources(dir, skip, out) {
|
|
|
7617
7677
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7618
7678
|
if (excluded.length === 0) return;
|
|
7619
7679
|
const files = [];
|
|
7620
|
-
walkSources(
|
|
7680
|
+
walkSources(path3.join(cwd, APP_SRC_DIR), path3.join(cwd, MODULES_DIR2), files);
|
|
7621
7681
|
const offenders = [];
|
|
7622
7682
|
for (const file of files) {
|
|
7623
|
-
const source =
|
|
7683
|
+
const source = fs3.readFileSync(file, "utf8");
|
|
7624
7684
|
for (const name of excluded) {
|
|
7625
7685
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7626
|
-
offenders.push(`${
|
|
7686
|
+
offenders.push(`${path3.relative(cwd, file)}: ${name}`);
|
|
7627
7687
|
}
|
|
7628
7688
|
}
|
|
7629
7689
|
}
|
|
@@ -7635,9 +7695,44 @@ function assertNoAppLayerImporters(cwd, excluded) {
|
|
|
7635
7695
|
);
|
|
7636
7696
|
}
|
|
7637
7697
|
}
|
|
7698
|
+
var TSCONFIG_PATH = "apps/web/tsconfig.json";
|
|
7699
|
+
var BUILD_TSCONFIG_PATH = "apps/web/tsconfig.build.gen.json";
|
|
7700
|
+
var BUILD_TSCONFIG_HEADER = "// Generated by stardeck-compose \u2014 the tsconfig `next build` type-checks. Do not edit or commit.";
|
|
7701
|
+
function writeBuildTsconfig(cwd, excluded) {
|
|
7702
|
+
const baseFile = path3.join(cwd, TSCONFIG_PATH);
|
|
7703
|
+
if (!fs3.existsSync(path3.join(cwd, MODULES_DIR2)) || !fs3.existsSync(baseFile)) return;
|
|
7704
|
+
const base = ts6.readConfigFile(baseFile, ts6.sys.readFile);
|
|
7705
|
+
if (base.error) {
|
|
7706
|
+
throw new Error(
|
|
7707
|
+
`[compose] cannot read ${TSCONFIG_PATH}: ${ts6.flattenDiagnosticMessageText(base.error.messageText, "\n")}`
|
|
7708
|
+
);
|
|
7709
|
+
}
|
|
7710
|
+
const baseExclude = base.config?.exclude ?? ["node_modules"];
|
|
7711
|
+
const modulesDir = path3.posix.relative("apps/web", MODULES_DIR2);
|
|
7712
|
+
const config = {
|
|
7713
|
+
extends: "./tsconfig.json",
|
|
7714
|
+
exclude: [
|
|
7715
|
+
...baseExclude,
|
|
7716
|
+
...excluded.flatMap((name) => [
|
|
7717
|
+
`${modulesDir}/${name}`,
|
|
7718
|
+
`${modulesDir}/*/enhancements/${name}`
|
|
7719
|
+
])
|
|
7720
|
+
]
|
|
7721
|
+
};
|
|
7722
|
+
const content = `${BUILD_TSCONFIG_HEADER}
|
|
7723
|
+
${JSON.stringify(config, null, 2)}
|
|
7724
|
+
`;
|
|
7725
|
+
const file = path3.join(cwd, BUILD_TSCONFIG_PATH);
|
|
7726
|
+
if (!fs3.existsSync(file) || fs3.readFileSync(file, "utf8") !== content) {
|
|
7727
|
+
fs3.writeFileSync(file, content);
|
|
7728
|
+
}
|
|
7729
|
+
console.log(
|
|
7730
|
+
`[compose] ${BUILD_TSCONFIG_PATH} excludes ${excluded.length} Module(s): ${excluded.join(",") || "none"}`
|
|
7731
|
+
);
|
|
7732
|
+
}
|
|
7638
7733
|
function removeDir(absolute) {
|
|
7639
|
-
if (!
|
|
7640
|
-
|
|
7734
|
+
if (!fs3.existsSync(absolute)) return false;
|
|
7735
|
+
fs3.rmSync(absolute, { recursive: true, force: true });
|
|
7641
7736
|
return true;
|
|
7642
7737
|
}
|
|
7643
7738
|
async function compose(options) {
|
|
@@ -7664,6 +7759,16 @@ async function compose(options) {
|
|
|
7664
7759
|
dryRun: true
|
|
7665
7760
|
});
|
|
7666
7761
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
7762
|
+
const violations = findModuleBoundaryViolations(
|
|
7763
|
+
path3.join(options.cwd, MODULES_DIR2),
|
|
7764
|
+
Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
|
|
7765
|
+
{ pruning: true }
|
|
7766
|
+
);
|
|
7767
|
+
if (violations.length > 0) {
|
|
7768
|
+
throw new Error(
|
|
7769
|
+
`[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
|
|
7770
|
+
);
|
|
7771
|
+
}
|
|
7667
7772
|
}
|
|
7668
7773
|
const result = await reconcileCompositionArtifacts(context, {
|
|
7669
7774
|
enabledModulesRaw: options.enabled,
|
|
@@ -7672,14 +7777,15 @@ async function compose(options) {
|
|
|
7672
7777
|
skipComposeInputs: true
|
|
7673
7778
|
});
|
|
7674
7779
|
const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
|
|
7780
|
+
writeBuildTsconfig(options.cwd, result.excludedModules);
|
|
7675
7781
|
let pruned = 0;
|
|
7676
7782
|
if (options.prune) {
|
|
7677
7783
|
for (const name of result.excludedModules) {
|
|
7678
|
-
if (removeDir(
|
|
7784
|
+
if (removeDir(path3.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7679
7785
|
}
|
|
7680
7786
|
for (const owner of result.composedModules) {
|
|
7681
7787
|
for (const peer of result.excludedModules) {
|
|
7682
|
-
if (removeDir(
|
|
7788
|
+
if (removeDir(path3.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7683
7789
|
}
|
|
7684
7790
|
}
|
|
7685
7791
|
}
|
|
@@ -7693,9 +7799,13 @@ async function compose(options) {
|
|
|
7693
7799
|
};
|
|
7694
7800
|
}
|
|
7695
7801
|
export {
|
|
7802
|
+
BUILD_TSCONFIG_PATH,
|
|
7696
7803
|
COMPOSE_INPUTS_PATH,
|
|
7697
7804
|
assertSupportedTypescript,
|
|
7698
7805
|
compose,
|
|
7699
7806
|
escapeGitignore,
|
|
7807
|
+
findModuleBoundaryViolations,
|
|
7808
|
+
findOptionalPeerViolations,
|
|
7809
|
+
formatBoundaryViolations,
|
|
7700
7810
|
readComposeInputs
|
|
7701
7811
|
};
|