@stardeck-customer-apps/compose 0.7.0 → 0.9.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 +35 -1
- package/dist/cli.js +446 -240
- package/dist/index.d.mts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +435 -237
- package/dist/index.mjs +432 -237
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2031,12 +2031,15 @@ __export(index_exports, {
|
|
|
2031
2031
|
assertSupportedTypescript: () => assertSupportedTypescript,
|
|
2032
2032
|
compose: () => compose,
|
|
2033
2033
|
escapeGitignore: () => escapeGitignore,
|
|
2034
|
+
findModuleBoundaryViolations: () => findModuleBoundaryViolations,
|
|
2035
|
+
findOptionalPeerViolations: () => findOptionalPeerViolations,
|
|
2036
|
+
formatBoundaryViolations: () => formatBoundaryViolations,
|
|
2034
2037
|
readComposeInputs: () => readComposeInputs
|
|
2035
2038
|
});
|
|
2036
2039
|
module.exports = __toCommonJS(index_exports);
|
|
2037
2040
|
var import_node_child_process2 = require("child_process");
|
|
2038
|
-
var
|
|
2039
|
-
var
|
|
2041
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
2042
|
+
var import_node_path3 = __toESM(require("path"));
|
|
2040
2043
|
|
|
2041
2044
|
// ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
|
|
2042
2045
|
var import_node_child_process = require("child_process");
|
|
@@ -2141,23 +2144,23 @@ function isValidModuleName(value) {
|
|
|
2141
2144
|
var projectSlugSchema = import_zod.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
2142
2145
|
var moduleKindSchema = import_zod.z.enum(["feature", "library", "contract"]);
|
|
2143
2146
|
var endpointMethodSchema = import_zod.z.enum(ENDPOINT_METHODS);
|
|
2144
|
-
function endpointPathValidationError(
|
|
2145
|
-
if (!
|
|
2147
|
+
function endpointPathValidationError(path4) {
|
|
2148
|
+
if (!path4.startsWith("/api/")) {
|
|
2146
2149
|
return 'must begin with "/api/"';
|
|
2147
2150
|
}
|
|
2148
|
-
if (
|
|
2151
|
+
if (path4.endsWith("/")) {
|
|
2149
2152
|
return "must not end with a trailing slash";
|
|
2150
2153
|
}
|
|
2151
|
-
if (
|
|
2154
|
+
if (path4.includes("?") || path4.includes("#")) {
|
|
2152
2155
|
return "must not include query or hash";
|
|
2153
2156
|
}
|
|
2154
|
-
if (
|
|
2157
|
+
if (path4.includes("\\")) {
|
|
2155
2158
|
return "must not include backslashes";
|
|
2156
2159
|
}
|
|
2157
|
-
if (
|
|
2160
|
+
if (path4.includes("%")) {
|
|
2158
2161
|
return "must not include percent-encoded segments";
|
|
2159
2162
|
}
|
|
2160
|
-
const parts =
|
|
2163
|
+
const parts = path4.split("/");
|
|
2161
2164
|
if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
|
|
2162
2165
|
return 'must begin with "/api/"';
|
|
2163
2166
|
}
|
|
@@ -2207,12 +2210,12 @@ function endpointPathValidationError(path3) {
|
|
|
2207
2210
|
}
|
|
2208
2211
|
return null;
|
|
2209
2212
|
}
|
|
2210
|
-
function normalizeEndpointPathForCollision(
|
|
2211
|
-
const error = endpointPathValidationError(
|
|
2213
|
+
function normalizeEndpointPathForCollision(path4) {
|
|
2214
|
+
const error = endpointPathValidationError(path4);
|
|
2212
2215
|
if (error != null) {
|
|
2213
2216
|
throw new Error(`invalid endpoint path: ${error}`);
|
|
2214
2217
|
}
|
|
2215
|
-
return
|
|
2218
|
+
return path4.split("/").map((segment, index) => {
|
|
2216
2219
|
if (index < 2) {
|
|
2217
2220
|
return segment;
|
|
2218
2221
|
}
|
|
@@ -2225,8 +2228,8 @@ function normalizeEndpointPathForCollision(path3) {
|
|
|
2225
2228
|
return segment;
|
|
2226
2229
|
}).join("/");
|
|
2227
2230
|
}
|
|
2228
|
-
var endpointPathSchema = import_zod.z.string().superRefine((
|
|
2229
|
-
const error = endpointPathValidationError(
|
|
2231
|
+
var endpointPathSchema = import_zod.z.string().superRefine((path4, ctx) => {
|
|
2232
|
+
const error = endpointPathValidationError(path4);
|
|
2230
2233
|
if (error != null) {
|
|
2231
2234
|
ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
|
|
2232
2235
|
}
|
|
@@ -2473,16 +2476,16 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2473
2476
|
}
|
|
2474
2477
|
}
|
|
2475
2478
|
const seenWorkflows = /* @__PURE__ */ new Map();
|
|
2476
|
-
const noteWorkflow = (name,
|
|
2479
|
+
const noteWorkflow = (name, path4) => {
|
|
2477
2480
|
const existing = seenWorkflows.get(name);
|
|
2478
2481
|
if (existing != null) {
|
|
2479
2482
|
ctx.addIssue({
|
|
2480
2483
|
code: import_zod.z.ZodIssueCode.custom,
|
|
2481
2484
|
message: `duplicate workflow name "${name}" (also at ${existing})`,
|
|
2482
|
-
path:
|
|
2485
|
+
path: path4
|
|
2483
2486
|
});
|
|
2484
2487
|
} else {
|
|
2485
|
-
seenWorkflows.set(name,
|
|
2488
|
+
seenWorkflows.set(name, path4.join("."));
|
|
2486
2489
|
}
|
|
2487
2490
|
};
|
|
2488
2491
|
for (let i = 0; i < manifest.workflows.length; i += 1) {
|
|
@@ -2494,7 +2497,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2494
2497
|
}
|
|
2495
2498
|
}
|
|
2496
2499
|
const seenEndpointKeys = /* @__PURE__ */ new Map();
|
|
2497
|
-
const noteEndpoint = (claim,
|
|
2500
|
+
const noteEndpoint = (claim, path4) => {
|
|
2498
2501
|
let key;
|
|
2499
2502
|
try {
|
|
2500
2503
|
key = normalizeEndpointPathForCollision(claim.path);
|
|
@@ -2506,10 +2509,10 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2506
2509
|
ctx.addIssue({
|
|
2507
2510
|
code: import_zod.z.ZodIssueCode.custom,
|
|
2508
2511
|
message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
|
|
2509
|
-
path:
|
|
2512
|
+
path: path4
|
|
2510
2513
|
});
|
|
2511
2514
|
} else {
|
|
2512
|
-
seenEndpointKeys.set(key,
|
|
2515
|
+
seenEndpointKeys.set(key, path4.join("."));
|
|
2513
2516
|
}
|
|
2514
2517
|
};
|
|
2515
2518
|
for (let i = 0; i < manifest.endpoints.length; i += 1) {
|
|
@@ -2610,8 +2613,8 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2610
2613
|
}).transform(({ version: _legacyVersion, ...manifest }) => manifest);
|
|
2611
2614
|
function formatZodErrors(error) {
|
|
2612
2615
|
return error.issues.map((issue) => {
|
|
2613
|
-
const
|
|
2614
|
-
return `${
|
|
2616
|
+
const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2617
|
+
return `${path4}: ${issue.message}`;
|
|
2615
2618
|
});
|
|
2616
2619
|
}
|
|
2617
2620
|
function parseModuleManifest(raw) {
|
|
@@ -3011,16 +3014,16 @@ var collectedE2eFactsSchema = import_zod3.z.object({
|
|
|
3011
3014
|
function validateRouteExportEvidenceCongruence(input) {
|
|
3012
3015
|
const entrySet = new Set(input.routeEntries);
|
|
3013
3016
|
const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
|
|
3014
|
-
for (const
|
|
3015
|
-
if (!entrySet.has(
|
|
3016
|
-
input.onIssue(`routePageExports path "${
|
|
3017
|
+
for (const path4 of exportPaths) {
|
|
3018
|
+
if (!entrySet.has(path4)) {
|
|
3019
|
+
input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
|
|
3017
3020
|
}
|
|
3018
3021
|
}
|
|
3019
3022
|
if (input.routeCollectionErrors.length === 0) {
|
|
3020
3023
|
const exportSet = new Set(exportPaths);
|
|
3021
|
-
for (const
|
|
3022
|
-
if (!exportSet.has(
|
|
3023
|
-
input.onIssue(`routeEntries path "${
|
|
3024
|
+
for (const path4 of input.routeEntries) {
|
|
3025
|
+
if (!exportSet.has(path4)) {
|
|
3026
|
+
input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
|
|
3024
3027
|
}
|
|
3025
3028
|
}
|
|
3026
3029
|
}
|
|
@@ -4134,6 +4137,7 @@ var MODULE_CONTRIBUTIONS_GEN_PATH = `${APP_WEB_PREFIX}src/module-contributions.g
|
|
|
4134
4137
|
var MODULE_DATASTORES_GEN_PATH = `${APP_WEB_PREFIX}src/module-datastores.gen.ts`;
|
|
4135
4138
|
var MODULE_I18N_GEN_PATH = `${APP_WEB_PREFIX}src/module-i18n.gen.ts`;
|
|
4136
4139
|
var MODULE_INIT_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-init.server.gen.ts`;
|
|
4140
|
+
var MODULE_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-server.gen.ts`;
|
|
4137
4141
|
var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
|
|
4138
4142
|
var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
|
|
4139
4143
|
|
|
@@ -4150,7 +4154,8 @@ var COMPOSITION_RUNTIME_GEN_PATHS = [
|
|
|
4150
4154
|
MODULE_CONTRIBUTIONS_GEN_PATH,
|
|
4151
4155
|
MODULE_I18N_GEN_PATH,
|
|
4152
4156
|
MODULE_INIT_SERVER_GEN_PATH,
|
|
4153
|
-
MODULE_DATASTORES_GEN_PATH
|
|
4157
|
+
MODULE_DATASTORES_GEN_PATH,
|
|
4158
|
+
MODULE_SERVER_GEN_PATH
|
|
4154
4159
|
];
|
|
4155
4160
|
function compareNames(a, b) {
|
|
4156
4161
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
@@ -4171,6 +4176,9 @@ function formatI18nMarker() {
|
|
|
4171
4176
|
function formatInitMarker() {
|
|
4172
4177
|
return `// ${MODULE_STUB_MARKER} scope=init`;
|
|
4173
4178
|
}
|
|
4179
|
+
function formatServerMarker() {
|
|
4180
|
+
return `// ${MODULE_STUB_MARKER} scope=server`;
|
|
4181
|
+
}
|
|
4174
4182
|
function formatDatastoresMarker() {
|
|
4175
4183
|
return `// ${MODULE_STUB_MARKER} scope=datastores`;
|
|
4176
4184
|
}
|
|
@@ -4266,13 +4274,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4266
4274
|
function createMessageTree() {
|
|
4267
4275
|
return /* @__PURE__ */ Object.create(null);
|
|
4268
4276
|
}
|
|
4269
|
-
function assertMessageTree(value,
|
|
4277
|
+
function assertMessageTree(value, path4) {
|
|
4270
4278
|
if (typeof value === "string") return;
|
|
4271
4279
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4272
|
-
throw new Error(`Invalid message at ${
|
|
4280
|
+
throw new Error(`Invalid message at ${path4}: expected string or nested object`);
|
|
4273
4281
|
}
|
|
4274
4282
|
for (const [key, child] of Object.entries(value)) {
|
|
4275
|
-
assertMessageTree(child, `${
|
|
4283
|
+
assertMessageTree(child, `${path4}.${key}`);
|
|
4276
4284
|
}
|
|
4277
4285
|
}
|
|
4278
4286
|
function cloneTree(value) {
|
|
@@ -4282,17 +4290,17 @@ function cloneTree(value) {
|
|
|
4282
4290
|
}
|
|
4283
4291
|
return clone;
|
|
4284
4292
|
}
|
|
4285
|
-
function collectLeafPaths(value,
|
|
4293
|
+
function collectLeafPaths(value, path4, out) {
|
|
4286
4294
|
if (typeof value === "string") {
|
|
4287
|
-
out.push(
|
|
4295
|
+
out.push(path4);
|
|
4288
4296
|
return;
|
|
4289
4297
|
}
|
|
4290
|
-
assertMessageTree(value,
|
|
4291
|
-
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${
|
|
4298
|
+
assertMessageTree(value, path4);
|
|
4299
|
+
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
|
|
4292
4300
|
}
|
|
4293
|
-
function overrideInto(target, source, owner,
|
|
4301
|
+
function overrideInto(target, source, owner, path4, missing) {
|
|
4294
4302
|
for (const [key, value] of Object.entries(source)) {
|
|
4295
|
-
const nextPath =
|
|
4303
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4296
4304
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4297
4305
|
collectLeafPaths(value, nextPath, missing);
|
|
4298
4306
|
continue;
|
|
@@ -4319,9 +4327,9 @@ function overrideInto(target, source, owner, path3, missing) {
|
|
|
4319
4327
|
function composeLocaleMessagesForPlan(layers) {
|
|
4320
4328
|
const result = createMessageTree();
|
|
4321
4329
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4322
|
-
function mergeInto(target, source, owner,
|
|
4330
|
+
function mergeInto(target, source, owner, path4) {
|
|
4323
4331
|
for (const [key, value] of Object.entries(source)) {
|
|
4324
|
-
const nextPath =
|
|
4332
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4325
4333
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4326
4334
|
const existing = hasExisting ? target[key] : void 0;
|
|
4327
4335
|
if (typeof value === "string") {
|
|
@@ -4814,6 +4822,42 @@ function emitModuleInitServerGenTs(input) {
|
|
|
4814
4822
|
""
|
|
4815
4823
|
].join("\n");
|
|
4816
4824
|
}
|
|
4825
|
+
function emitModuleServerGenTs(input) {
|
|
4826
|
+
const installed = new Set(input.registry.modules.map((m) => m.name));
|
|
4827
|
+
const moduleNames = input.facts.filter((f) => f.hasServerRegistrations && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames);
|
|
4828
|
+
for (const name of moduleNames) {
|
|
4829
|
+
if (!isValidModuleName(name)) {
|
|
4830
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
const importLines = moduleNames.map(
|
|
4834
|
+
(name, index) => [
|
|
4835
|
+
`@/modules/${name}/server/registrations`,
|
|
4836
|
+
`import * as serverRegistrations${index} from ${JSON.stringify(`@/modules/${name}/server/registrations`)};`
|
|
4837
|
+
]
|
|
4838
|
+
).sort((a, b) => compareImportSpecifiers(a[0], b[0])).map(([, line]) => line);
|
|
4839
|
+
const entryLines = moduleNames.map(
|
|
4840
|
+
(name, index) => ` { module: ${JSON.stringify(name)}, registrations: serverRegistrations${index} },`
|
|
4841
|
+
);
|
|
4842
|
+
return [
|
|
4843
|
+
formatServerMarker(),
|
|
4844
|
+
"/** DO NOT EDIT \u2014 owned by the module install rail. */",
|
|
4845
|
+
"",
|
|
4846
|
+
...importLines,
|
|
4847
|
+
...importLines.length > 0 ? [""] : [],
|
|
4848
|
+
"export type ModuleServerRegistration = {",
|
|
4849
|
+
" module: string;",
|
|
4850
|
+
" registrations: Readonly<Record<string, unknown>>;",
|
|
4851
|
+
"};",
|
|
4852
|
+
"",
|
|
4853
|
+
...entryLines.length === 0 ? ["export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [];"] : [
|
|
4854
|
+
"export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [",
|
|
4855
|
+
...entryLines,
|
|
4856
|
+
"];"
|
|
4857
|
+
],
|
|
4858
|
+
""
|
|
4859
|
+
].join("\n");
|
|
4860
|
+
}
|
|
4817
4861
|
|
|
4818
4862
|
// ../../packages/lib/src/server/module-rail/composition-artifacts.ts
|
|
4819
4863
|
var COMPOSITION_ARTIFACTS_GENERATED_BY = "module-rail";
|
|
@@ -4829,8 +4873,8 @@ var METHOD_ORDER = {
|
|
|
4829
4873
|
function compareNames2(a, b) {
|
|
4830
4874
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4831
4875
|
}
|
|
4832
|
-
function endpointPairKey(method,
|
|
4833
|
-
return `${method} ${
|
|
4876
|
+
function endpointPairKey(method, path4) {
|
|
4877
|
+
return `${method} ${path4}`;
|
|
4834
4878
|
}
|
|
4835
4879
|
function apiPathToRouteFile(apiPath) {
|
|
4836
4880
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4844,11 +4888,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4844
4888
|
}
|
|
4845
4889
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4846
4890
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4847
|
-
function appRouteFileToNextRelativePath(
|
|
4848
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
4891
|
+
function appRouteFileToNextRelativePath(path4) {
|
|
4892
|
+
const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
4849
4893
|
if (pagePath != null) return pagePath;
|
|
4850
|
-
if (!
|
|
4851
|
-
const relative =
|
|
4894
|
+
if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
4895
|
+
const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4852
4896
|
if (!relative.startsWith("api/")) return null;
|
|
4853
4897
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4854
4898
|
if (!suffix) return null;
|
|
@@ -4876,6 +4920,14 @@ function formatRouteMarker(owner, nextRelativePath) {
|
|
|
4876
4920
|
function formatRegistryMarker() {
|
|
4877
4921
|
return `// ${MODULE_STUB_MARKER} scope=registry`;
|
|
4878
4922
|
}
|
|
4923
|
+
var OWNERLESS_MARKER_KINDS = [
|
|
4924
|
+
"registry",
|
|
4925
|
+
"contributions",
|
|
4926
|
+
"datastores",
|
|
4927
|
+
"i18n",
|
|
4928
|
+
"init",
|
|
4929
|
+
"server"
|
|
4930
|
+
];
|
|
4879
4931
|
function parseCompositionArtifactMarker(source) {
|
|
4880
4932
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4881
4933
|
const match = firstLine.match(
|
|
@@ -4886,26 +4938,8 @@ function parseCompositionArtifactMarker(source) {
|
|
|
4886
4938
|
if (!match) return null;
|
|
4887
4939
|
const owner = match[1];
|
|
4888
4940
|
const scope = match[2];
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
return { kind: "registry" };
|
|
4892
|
-
}
|
|
4893
|
-
if (scope === "contributions") {
|
|
4894
|
-
if (owner) return null;
|
|
4895
|
-
return { kind: "contributions" };
|
|
4896
|
-
}
|
|
4897
|
-
if (scope === "datastores") {
|
|
4898
|
-
if (owner) return null;
|
|
4899
|
-
return { kind: "datastores" };
|
|
4900
|
-
}
|
|
4901
|
-
if (scope === "i18n") {
|
|
4902
|
-
if (owner) return null;
|
|
4903
|
-
return { kind: "i18n" };
|
|
4904
|
-
}
|
|
4905
|
-
if (scope === "init") {
|
|
4906
|
-
if (owner) return null;
|
|
4907
|
-
return { kind: "init" };
|
|
4908
|
-
}
|
|
4941
|
+
const ownerless = OWNERLESS_MARKER_KINDS.find((kind) => kind === scope);
|
|
4942
|
+
if (ownerless) return owner ? null : { kind: ownerless };
|
|
4909
4943
|
if (!owner || !isValidModuleName(owner)) return null;
|
|
4910
4944
|
if (scope === "route") {
|
|
4911
4945
|
const nextRelativePath = match[3]?.trim();
|
|
@@ -4950,9 +4984,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4950
4984
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4951
4985
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4952
4986
|
}
|
|
4953
|
-
function parseLegacyApiAdapter(
|
|
4954
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4955
|
-
if (expectedMarkerKindForPath(
|
|
4987
|
+
function parseLegacyApiAdapter(path4, source) {
|
|
4988
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
4989
|
+
if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
|
|
4956
4990
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4957
4991
|
const match = firstLine.match(
|
|
4958
4992
|
new RegExp(
|
|
@@ -4962,7 +4996,7 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4962
4996
|
if (!match || match[2] !== physicalPath) return null;
|
|
4963
4997
|
const owner = match[1];
|
|
4964
4998
|
if (!isValidModuleName(owner)) return null;
|
|
4965
|
-
const file = import_typescript4.default.createSourceFile(
|
|
4999
|
+
const file = import_typescript4.default.createSourceFile(path4, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
|
|
4966
5000
|
if (file.parseDiagnostics.length > 0) {
|
|
4967
5001
|
return null;
|
|
4968
5002
|
}
|
|
@@ -5004,9 +5038,9 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
5004
5038
|
}
|
|
5005
5039
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
5006
5040
|
}
|
|
5007
|
-
function parseLegacyRouteAdapter(
|
|
5008
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
5009
|
-
if (expectedMarkerKindForPath(
|
|
5041
|
+
function parseLegacyRouteAdapter(path4, source) {
|
|
5042
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
5043
|
+
if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
|
|
5010
5044
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
5011
5045
|
const match = normalized.match(
|
|
5012
5046
|
new RegExp(
|
|
@@ -5018,29 +5052,30 @@ function parseLegacyRouteAdapter(path3, source) {
|
|
|
5018
5052
|
const nextRelativePath = match[2];
|
|
5019
5053
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
5020
5054
|
}
|
|
5021
|
-
function legacyRouteAdapterMatchesStub(
|
|
5022
|
-
const legacy = parseLegacyRouteAdapter(
|
|
5055
|
+
function legacyRouteAdapterMatchesStub(path4, source, stub) {
|
|
5056
|
+
const legacy = parseLegacyRouteAdapter(path4, source);
|
|
5023
5057
|
return legacy != null && legacy.owner === stub.owner;
|
|
5024
5058
|
}
|
|
5025
5059
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
5026
5060
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
5027
5061
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
5028
|
-
function expectedMarkerKindForPath(
|
|
5029
|
-
if (
|
|
5030
|
-
if (
|
|
5031
|
-
if (
|
|
5032
|
-
if (
|
|
5033
|
-
if (
|
|
5034
|
-
if (
|
|
5035
|
-
if (
|
|
5062
|
+
function expectedMarkerKindForPath(path4) {
|
|
5063
|
+
if (path4 === MODULES_GEN_PATH) return "registry";
|
|
5064
|
+
if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
|
|
5065
|
+
if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
|
|
5066
|
+
if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
|
|
5067
|
+
if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
|
|
5068
|
+
if (path4 === MODULE_SERVER_GEN_PATH) return "server";
|
|
5069
|
+
if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
|
|
5070
|
+
if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
|
|
5036
5071
|
return "route";
|
|
5037
5072
|
}
|
|
5038
5073
|
return null;
|
|
5039
5074
|
}
|
|
5040
|
-
function markerCongruentWithPath(
|
|
5041
|
-
if (expectedMarkerKindForPath(
|
|
5075
|
+
function markerCongruentWithPath(path4, marker) {
|
|
5076
|
+
if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
|
|
5042
5077
|
if (marker.kind === "route") {
|
|
5043
|
-
const derived = appPageFileToNextRelativePath(
|
|
5078
|
+
const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
5044
5079
|
return derived != null && derived === marker.nextRelativePath;
|
|
5045
5080
|
}
|
|
5046
5081
|
return true;
|
|
@@ -5311,8 +5346,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5311
5346
|
function activeEnhancementPeers(registryModule) {
|
|
5312
5347
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5313
5348
|
}
|
|
5314
|
-
function endpointBucketKey(
|
|
5315
|
-
return `${
|
|
5349
|
+
function endpointBucketKey(path4, owner, scope) {
|
|
5350
|
+
return `${path4}\0${owner}\0${scopeLabel(scope)}`;
|
|
5316
5351
|
}
|
|
5317
5352
|
function planRouteStubs(input) {
|
|
5318
5353
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5436,7 +5471,7 @@ function planRouteStubs(input) {
|
|
|
5436
5471
|
for (const entry of entries) {
|
|
5437
5472
|
const nextRelativePath = entry.nextRelativePath;
|
|
5438
5473
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5439
|
-
const
|
|
5474
|
+
const path4 = routeEntryToAppPageFile(nextRelativePath);
|
|
5440
5475
|
const existing = routeDestinationOwners.get(destination);
|
|
5441
5476
|
if (existing) {
|
|
5442
5477
|
throw new Error(
|
|
@@ -5446,13 +5481,13 @@ function planRouteStubs(input) {
|
|
|
5446
5481
|
routeDestinationOwners.set(destination, {
|
|
5447
5482
|
owner: regMod.name,
|
|
5448
5483
|
nextRelativePath,
|
|
5449
|
-
path:
|
|
5484
|
+
path: path4
|
|
5450
5485
|
});
|
|
5451
5486
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5452
5487
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5453
5488
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5454
5489
|
routeStubs.push({
|
|
5455
|
-
path:
|
|
5490
|
+
path: path4,
|
|
5456
5491
|
owner: regMod.name,
|
|
5457
5492
|
nextRelativePath,
|
|
5458
5493
|
importModule,
|
|
@@ -5513,16 +5548,16 @@ function planCompositionArtifacts(input) {
|
|
|
5513
5548
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5514
5549
|
]);
|
|
5515
5550
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5516
|
-
const isProvenStaleDeletionCandidate = (
|
|
5517
|
-
if (
|
|
5518
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5519
|
-
if (desiredPaths.has(
|
|
5520
|
-
if (!existingGeneratedPathSet.has(
|
|
5521
|
-
const legacy = parseLegacyApiAdapter(
|
|
5551
|
+
const isProvenStaleDeletionCandidate = (path4, content) => {
|
|
5552
|
+
if (path4 === MODULES_GEN_PATH) return false;
|
|
5553
|
+
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
|
|
5554
|
+
if (desiredPaths.has(path4)) return false;
|
|
5555
|
+
if (!existingGeneratedPathSet.has(path4)) return false;
|
|
5556
|
+
const legacy = parseLegacyApiAdapter(path4, content);
|
|
5522
5557
|
if (legacy) return true;
|
|
5523
|
-
if (parseLegacyRouteAdapter(
|
|
5558
|
+
if (parseLegacyRouteAdapter(path4, content)) return true;
|
|
5524
5559
|
const marker = parseCompositionArtifactMarker(content);
|
|
5525
|
-
return marker != null && markerCongruentWithPath(
|
|
5560
|
+
return marker != null && markerCongruentWithPath(path4, marker);
|
|
5526
5561
|
};
|
|
5527
5562
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5528
5563
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5630,6 +5665,7 @@ function planCompositionArtifacts(input) {
|
|
|
5630
5665
|
let datastoresContent;
|
|
5631
5666
|
let i18nContent;
|
|
5632
5667
|
let initContent;
|
|
5668
|
+
let serverContent;
|
|
5633
5669
|
try {
|
|
5634
5670
|
contributionsContent = emitModuleContributionsGenTs({
|
|
5635
5671
|
registry: input.registry,
|
|
@@ -5654,6 +5690,10 @@ function planCompositionArtifacts(input) {
|
|
|
5654
5690
|
registry: input.registry,
|
|
5655
5691
|
facts: compositionEntryFacts
|
|
5656
5692
|
});
|
|
5693
|
+
serverContent = emitModuleServerGenTs({
|
|
5694
|
+
registry: input.registry,
|
|
5695
|
+
facts: compositionEntryFacts
|
|
5696
|
+
});
|
|
5657
5697
|
} catch (error) {
|
|
5658
5698
|
return {
|
|
5659
5699
|
ok: false,
|
|
@@ -5666,17 +5706,18 @@ function planCompositionArtifacts(input) {
|
|
|
5666
5706
|
desired.set(MODULE_DATASTORES_GEN_PATH, datastoresContent);
|
|
5667
5707
|
desired.set(MODULE_I18N_GEN_PATH, i18nContent);
|
|
5668
5708
|
desired.set(MODULE_INIT_SERVER_GEN_PATH, initContent);
|
|
5709
|
+
desired.set(MODULE_SERVER_GEN_PATH, serverContent);
|
|
5669
5710
|
for (const stub of stubs.endpointStubs) desired.set(stub.path, stub.content);
|
|
5670
5711
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5671
5712
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5672
5713
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5673
|
-
for (const [
|
|
5674
|
-
const existing = input.existingFiles[
|
|
5714
|
+
for (const [path4] of desired) {
|
|
5715
|
+
const existing = input.existingFiles[path4];
|
|
5675
5716
|
if (existing == null) continue;
|
|
5676
5717
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5677
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5678
|
-
const plannedRoute = routeStubByPath.get(
|
|
5679
|
-
const legacy = parseLegacyApiAdapter(
|
|
5718
|
+
const plannedEndpoint = endpointStubByPath.get(path4);
|
|
5719
|
+
const plannedRoute = routeStubByPath.get(path4);
|
|
5720
|
+
const legacy = parseLegacyApiAdapter(path4, existing);
|
|
5680
5721
|
if (legacy && plannedEndpoint) {
|
|
5681
5722
|
marker = {
|
|
5682
5723
|
kind: "endpoint",
|
|
@@ -5684,66 +5725,66 @@ function planCompositionArtifacts(input) {
|
|
|
5684
5725
|
scope: plannedEndpoint.scope
|
|
5685
5726
|
};
|
|
5686
5727
|
}
|
|
5687
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5728
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
|
|
5688
5729
|
marker = {
|
|
5689
5730
|
kind: "route",
|
|
5690
5731
|
owner: plannedRoute.owner,
|
|
5691
5732
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5692
5733
|
};
|
|
5693
5734
|
}
|
|
5694
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5735
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
|
|
5695
5736
|
if (!marker) {
|
|
5696
5737
|
return {
|
|
5697
5738
|
ok: false,
|
|
5698
|
-
error:
|
|
5699
|
-
path:
|
|
5739
|
+
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}`,
|
|
5740
|
+
path: path4
|
|
5700
5741
|
};
|
|
5701
5742
|
}
|
|
5702
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5743
|
+
const expectedKind = expectedMarkerKindForPath(path4);
|
|
5703
5744
|
if (marker.kind !== expectedKind) {
|
|
5704
5745
|
return {
|
|
5705
5746
|
ok: false,
|
|
5706
|
-
error:
|
|
5707
|
-
path:
|
|
5747
|
+
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)`,
|
|
5748
|
+
path: path4
|
|
5708
5749
|
};
|
|
5709
5750
|
}
|
|
5710
5751
|
if (marker.kind === "endpoint") {
|
|
5711
|
-
const stub = endpointStubByPath.get(
|
|
5752
|
+
const stub = endpointStubByPath.get(path4);
|
|
5712
5753
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5713
5754
|
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5714
5755
|
if (!ownerMatches || !scopeMatches) {
|
|
5715
5756
|
return {
|
|
5716
5757
|
ok: false,
|
|
5717
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5718
|
-
path:
|
|
5758
|
+
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) : "?"})`,
|
|
5759
|
+
path: path4,
|
|
5719
5760
|
owner: stub?.owner
|
|
5720
5761
|
};
|
|
5721
5762
|
}
|
|
5722
5763
|
} else if (marker.kind === "route") {
|
|
5723
|
-
const stub = routeStubByPath.get(
|
|
5764
|
+
const stub = routeStubByPath.get(path4);
|
|
5724
5765
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5725
5766
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5726
5767
|
if (!ownerMatches || !pathMatches) {
|
|
5727
5768
|
return {
|
|
5728
5769
|
ok: false,
|
|
5729
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5730
|
-
path:
|
|
5770
|
+
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 ?? "?"})`,
|
|
5771
|
+
path: path4,
|
|
5731
5772
|
owner: stub?.owner
|
|
5732
5773
|
};
|
|
5733
5774
|
}
|
|
5734
5775
|
}
|
|
5735
5776
|
}
|
|
5736
5777
|
const deletes = [];
|
|
5737
|
-
for (const
|
|
5738
|
-
const existing = input.existingFiles[
|
|
5778
|
+
for (const path4 of input.existingGeneratedPaths) {
|
|
5779
|
+
const existing = input.existingFiles[path4];
|
|
5739
5780
|
if (existing == null) continue;
|
|
5740
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5781
|
+
if (!isProvenStaleDeletionCandidate(path4, existing)) {
|
|
5741
5782
|
continue;
|
|
5742
5783
|
}
|
|
5743
|
-
deletes.push(
|
|
5784
|
+
deletes.push(path4);
|
|
5744
5785
|
}
|
|
5745
5786
|
deletes.sort(compareNames2);
|
|
5746
|
-
const writes = [...desired.entries()].filter(([
|
|
5787
|
+
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));
|
|
5747
5788
|
return {
|
|
5748
5789
|
ok: true,
|
|
5749
5790
|
plan: {
|
|
@@ -5772,8 +5813,16 @@ async function checkoutParsesTypedStores(sandbox) {
|
|
|
5772
5813
|
try {
|
|
5773
5814
|
if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
|
|
5774
5815
|
const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
|
|
5775
|
-
|
|
5776
|
-
|
|
5816
|
+
return composeRangeParsesTypedStores(
|
|
5817
|
+
pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
|
|
5818
|
+
);
|
|
5819
|
+
} catch {
|
|
5820
|
+
return false;
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
function composeRangeParsesTypedStores(range) {
|
|
5824
|
+
if (!range) return false;
|
|
5825
|
+
try {
|
|
5777
5826
|
const lowest = import_semver.default.minVersion(range);
|
|
5778
5827
|
return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
|
|
5779
5828
|
} catch {
|
|
@@ -6218,11 +6267,11 @@ function countedCompositionContext(context) {
|
|
|
6218
6267
|
counts.sandboxExecs += 1;
|
|
6219
6268
|
return context.sandbox.exec(...args);
|
|
6220
6269
|
},
|
|
6221
|
-
readFile: (
|
|
6270
|
+
readFile: (path4) => {
|
|
6222
6271
|
counts.sandboxReads += 1;
|
|
6223
|
-
return context.sandbox.readFile(
|
|
6272
|
+
return context.sandbox.readFile(path4);
|
|
6224
6273
|
},
|
|
6225
|
-
fileExists: (
|
|
6274
|
+
fileExists: (path4) => context.sandbox.fileExists(path4),
|
|
6226
6275
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6227
6276
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6228
6277
|
}
|
|
@@ -6434,14 +6483,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6434
6483
|
}
|
|
6435
6484
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6436
6485
|
}
|
|
6437
|
-
async function writeSandboxFileAtomic(target,
|
|
6438
|
-
const dir = dirnamePosix(
|
|
6439
|
-
const tmp = `${
|
|
6486
|
+
async function writeSandboxFileAtomic(target, path4, content) {
|
|
6487
|
+
const dir = dirnamePosix(path4);
|
|
6488
|
+
const tmp = `${path4}.tmp.${target.runId}`;
|
|
6440
6489
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6441
6490
|
const cmd = [
|
|
6442
6491
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6443
6492
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6444
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6493
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
|
|
6445
6494
|
].join(" && ");
|
|
6446
6495
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6447
6496
|
if (result.exitCode !== 0) {
|
|
@@ -6456,14 +6505,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
|
|
|
6456
6505
|
}
|
|
6457
6506
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6458
6507
|
throw new Error(
|
|
6459
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6508
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6460
6509
|
);
|
|
6461
6510
|
}
|
|
6462
6511
|
}
|
|
6463
6512
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6464
6513
|
const absent = /* @__PURE__ */ new Set();
|
|
6465
|
-
for (const
|
|
6466
|
-
let dir = dirnamePosix(
|
|
6514
|
+
for (const path4 of writePaths) {
|
|
6515
|
+
let dir = dirnamePosix(path4);
|
|
6467
6516
|
while (dir !== "." && dir !== "") {
|
|
6468
6517
|
if (absent.has(dir)) {
|
|
6469
6518
|
dir = dirnamePosix(dir);
|
|
@@ -6500,12 +6549,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6500
6549
|
);
|
|
6501
6550
|
}
|
|
6502
6551
|
}
|
|
6503
|
-
async function deleteSandboxFile(context,
|
|
6504
|
-
const result = await context.sandbox.exec(`rm -f ${shellQuote(
|
|
6552
|
+
async function deleteSandboxFile(context, path4) {
|
|
6553
|
+
const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
|
|
6505
6554
|
if (result.exitCode !== 0) {
|
|
6506
6555
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6507
6556
|
throw new Error(
|
|
6508
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6557
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6509
6558
|
);
|
|
6510
6559
|
}
|
|
6511
6560
|
}
|
|
@@ -6572,37 +6621,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6572
6621
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6573
6622
|
appliedWrites.push({ path: write.path, previous });
|
|
6574
6623
|
}
|
|
6575
|
-
for (const
|
|
6576
|
-
const previous = existingFiles[
|
|
6624
|
+
for (const path4 of plan.deletes) {
|
|
6625
|
+
const previous = existingFiles[path4];
|
|
6577
6626
|
if (previous == null) continue;
|
|
6578
|
-
await deleteSandboxFile(context,
|
|
6579
|
-
appliedDeletes.push({ path:
|
|
6627
|
+
await deleteSandboxFile(context, path4);
|
|
6628
|
+
appliedDeletes.push({ path: path4, previous });
|
|
6580
6629
|
}
|
|
6581
6630
|
} catch (error) {
|
|
6582
6631
|
console.log(
|
|
6583
6632
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6584
6633
|
);
|
|
6585
|
-
for (const { path:
|
|
6634
|
+
for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
|
|
6586
6635
|
try {
|
|
6587
|
-
await writeSandboxFileAtomic(context,
|
|
6636
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6588
6637
|
} catch (rollbackError) {
|
|
6589
6638
|
console.log(
|
|
6590
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(
|
|
6639
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6591
6640
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6592
6641
|
)}`
|
|
6593
6642
|
);
|
|
6594
6643
|
}
|
|
6595
6644
|
}
|
|
6596
|
-
for (const { path:
|
|
6645
|
+
for (const { path: path4, previous } of [...appliedWrites].reverse()) {
|
|
6597
6646
|
try {
|
|
6598
6647
|
if (previous == null) {
|
|
6599
|
-
await deleteSandboxFile(context,
|
|
6648
|
+
await deleteSandboxFile(context, path4);
|
|
6600
6649
|
} else {
|
|
6601
|
-
await writeSandboxFileAtomic(context,
|
|
6650
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6602
6651
|
}
|
|
6603
6652
|
} catch (rollbackError) {
|
|
6604
6653
|
console.log(
|
|
6605
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(
|
|
6654
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6606
6655
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6607
6656
|
)}`
|
|
6608
6657
|
);
|
|
@@ -6636,39 +6685,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6636
6685
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6637
6686
|
);
|
|
6638
6687
|
const presentPaths = [];
|
|
6639
|
-
for (const
|
|
6688
|
+
for (const path4 of candidates) {
|
|
6640
6689
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6641
|
-
|
|
6642
|
-
kinds.get(
|
|
6690
|
+
path4,
|
|
6691
|
+
kinds.get(path4) ?? "unreadable"
|
|
6643
6692
|
);
|
|
6644
|
-
if (presence === "file") presentPaths.push(
|
|
6693
|
+
if (presence === "file") presentPaths.push(path4);
|
|
6645
6694
|
}
|
|
6646
6695
|
let contents;
|
|
6647
6696
|
try {
|
|
6648
6697
|
contents = await readFilesMany(context, presentPaths);
|
|
6649
6698
|
} catch (error) {
|
|
6650
|
-
const
|
|
6699
|
+
const path4 = readFailurePath(error);
|
|
6651
6700
|
throw new Error(
|
|
6652
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6701
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
|
|
6653
6702
|
error instanceof Error ? error.message : String(error)
|
|
6654
6703
|
)}`
|
|
6655
6704
|
);
|
|
6656
6705
|
}
|
|
6657
6706
|
const owned = [];
|
|
6658
|
-
for (const
|
|
6659
|
-
const content = contents.get(
|
|
6707
|
+
for (const path4 of presentPaths) {
|
|
6708
|
+
const content = contents.get(path4);
|
|
6660
6709
|
const marker = parseCompositionArtifactMarker(content);
|
|
6661
|
-
if (parseLegacyApiAdapter(
|
|
6662
|
-
owned.push(
|
|
6710
|
+
if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
|
|
6711
|
+
owned.push(path4);
|
|
6663
6712
|
continue;
|
|
6664
6713
|
}
|
|
6665
6714
|
if (marker == null) continue;
|
|
6666
|
-
if (!markerCongruentWithPath(
|
|
6715
|
+
if (!markerCongruentWithPath(path4, marker)) {
|
|
6667
6716
|
throw new Error(
|
|
6668
|
-
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(
|
|
6717
|
+
`[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)`
|
|
6669
6718
|
);
|
|
6670
6719
|
}
|
|
6671
|
-
owned.push(
|
|
6720
|
+
owned.push(path4);
|
|
6672
6721
|
}
|
|
6673
6722
|
return owned;
|
|
6674
6723
|
}
|
|
@@ -6801,7 +6850,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6801
6850
|
{ raiseOnError: false }
|
|
6802
6851
|
);
|
|
6803
6852
|
if (result.exitCode !== 0) {
|
|
6804
|
-
for (const
|
|
6853
|
+
for (const path4 of chunk) kinds.set(path4, "unreadable");
|
|
6805
6854
|
continue;
|
|
6806
6855
|
}
|
|
6807
6856
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6812,31 +6861,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6812
6861
|
}
|
|
6813
6862
|
} else {
|
|
6814
6863
|
for (const line of lines) {
|
|
6815
|
-
const [rawKind,
|
|
6864
|
+
const [rawKind, path4] = line.split(" ", 2);
|
|
6816
6865
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6817
|
-
if (
|
|
6818
|
-
kinds.set(
|
|
6866
|
+
if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
6867
|
+
kinds.set(path4, kind);
|
|
6819
6868
|
}
|
|
6820
6869
|
}
|
|
6821
6870
|
}
|
|
6822
|
-
for (const
|
|
6823
|
-
if (!kinds.has(
|
|
6871
|
+
for (const path4 of chunk) {
|
|
6872
|
+
if (!kinds.has(path4)) kinds.set(path4, "unreadable");
|
|
6824
6873
|
}
|
|
6825
6874
|
}
|
|
6826
6875
|
return kinds;
|
|
6827
6876
|
}
|
|
6828
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6829
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
6877
|
+
async function classifySandboxPathPresenceKind(context, path4, probeToken) {
|
|
6878
|
+
return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
|
|
6830
6879
|
}
|
|
6831
6880
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6832
|
-
function assertRealLocaleJsonFilePresence(
|
|
6881
|
+
function assertRealLocaleJsonFilePresence(path4, presence) {
|
|
6833
6882
|
if (presence === "file") return;
|
|
6834
6883
|
if (presence === "absent") {
|
|
6835
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
6884
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
|
|
6836
6885
|
}
|
|
6837
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6886
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6838
6887
|
throw new Error(
|
|
6839
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6888
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
|
|
6840
6889
|
);
|
|
6841
6890
|
}
|
|
6842
6891
|
function splitTaggedLine(line) {
|
|
@@ -6911,11 +6960,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6911
6960
|
}
|
|
6912
6961
|
async function readFilesMany(context, paths) {
|
|
6913
6962
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6914
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
6963
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
|
|
6915
6964
|
try {
|
|
6916
|
-
return { path:
|
|
6965
|
+
return { path: path4, value: await context.sandbox.readFile(path4) };
|
|
6917
6966
|
} catch (error) {
|
|
6918
|
-
return { path:
|
|
6967
|
+
return { path: path4, error };
|
|
6919
6968
|
}
|
|
6920
6969
|
});
|
|
6921
6970
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6936,55 +6985,60 @@ function readFailurePath(error) {
|
|
|
6936
6985
|
return typeof error === "object" && error !== null && "path" in error ? String(error.path) : "unknown";
|
|
6937
6986
|
}
|
|
6938
6987
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6988
|
+
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6939
6989
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6940
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6941
|
-
return classifySandboxPathPresenceKind(context,
|
|
6990
|
+
async function classifyCompositionArtifactPathKind(context, path4) {
|
|
6991
|
+
return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6942
6992
|
}
|
|
6943
|
-
function assertRealCompositionArtifactFilePresence(
|
|
6993
|
+
function assertRealCompositionArtifactFilePresence(path4, presence) {
|
|
6944
6994
|
if (presence === "file") return "file";
|
|
6945
6995
|
if (presence === "absent") return "absent";
|
|
6946
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6996
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6947
6997
|
throw new Error(
|
|
6948
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6998
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
|
|
6949
6999
|
);
|
|
6950
7000
|
}
|
|
6951
|
-
async function readConventionalEntryIfRealFile(
|
|
6952
|
-
const presence = presences.get(
|
|
6953
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
7001
|
+
async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
7002
|
+
const presence = presences.get(path4) ?? "unreadable";
|
|
7003
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6954
7004
|
if (presenceError) {
|
|
6955
7005
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6956
7006
|
}
|
|
6957
7007
|
if (presence === "absent") {
|
|
6958
7008
|
return null;
|
|
6959
7009
|
}
|
|
6960
|
-
const source = files.get(
|
|
7010
|
+
const source = files.get(path4);
|
|
6961
7011
|
switch (kind) {
|
|
6962
7012
|
case "contributions":
|
|
6963
|
-
assertContributionsExport(source,
|
|
6964
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7013
|
+
assertContributionsExport(source, path4);
|
|
7014
|
+
assertClientSafeConventionalEntryImports(source, path4, "contributions");
|
|
6965
7015
|
break;
|
|
6966
7016
|
case "slotCatalogs":
|
|
6967
|
-
assertSlotCatalogsExport(source,
|
|
6968
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7017
|
+
assertSlotCatalogsExport(source, path4);
|
|
7018
|
+
assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
|
|
6969
7019
|
break;
|
|
6970
7020
|
case "initializeEnhancement":
|
|
6971
|
-
assertInitializeEnhancementExport(source,
|
|
7021
|
+
assertInitializeEnhancementExport(source, path4);
|
|
6972
7022
|
break;
|
|
6973
7023
|
}
|
|
6974
7024
|
return source;
|
|
6975
7025
|
}
|
|
6976
|
-
|
|
6977
|
-
const
|
|
7026
|
+
function hasRealServerRegistrations(modulePath, presences) {
|
|
7027
|
+
const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
7028
|
+
return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
|
|
7029
|
+
}
|
|
7030
|
+
async function readJsonObjectFile(path4, files) {
|
|
7031
|
+
const raw = files.get(path4);
|
|
6978
7032
|
let parsed;
|
|
6979
7033
|
try {
|
|
6980
7034
|
parsed = JSON.parse(raw);
|
|
6981
7035
|
} catch (error) {
|
|
6982
7036
|
throw new Error(
|
|
6983
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
7037
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
|
|
6984
7038
|
);
|
|
6985
7039
|
}
|
|
6986
7040
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
6987
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
7041
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
|
|
6988
7042
|
}
|
|
6989
7043
|
return parsed;
|
|
6990
7044
|
}
|
|
@@ -6996,6 +7050,9 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
6996
7050
|
const overridesDir = `${APP_PACKAGE_DIR}/src/lib/i18n/overrides`;
|
|
6997
7051
|
const localeDirs = [substrateDir, overridesDir];
|
|
6998
7052
|
const conventionalPaths = [];
|
|
7053
|
+
const serverRegistrationPaths = registry.modules.map(
|
|
7054
|
+
(mod) => `${MODULES_SANDBOX_DIR}/${mod.name}/${SERVER_REGISTRATIONS_FILE}`
|
|
7055
|
+
);
|
|
6999
7056
|
for (const mod of registry.modules) {
|
|
7000
7057
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
7001
7058
|
localeDirs.push(`${modulePath}/i18n`);
|
|
@@ -7024,17 +7081,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7024
7081
|
const [conventionalPresences, localePresences] = await Promise.all([
|
|
7025
7082
|
classifySandboxPathPresenceKindMany(
|
|
7026
7083
|
context,
|
|
7027
|
-
conventionalPaths,
|
|
7084
|
+
[...conventionalPaths, ...serverRegistrationPaths],
|
|
7028
7085
|
CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN
|
|
7029
7086
|
),
|
|
7030
7087
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7031
7088
|
]);
|
|
7032
7089
|
const presentConventionalPaths = [];
|
|
7033
|
-
for (const
|
|
7034
|
-
const presence = conventionalPresences.get(
|
|
7035
|
-
if (presence === "file") presentConventionalPaths.push(
|
|
7090
|
+
for (const path4 of conventionalPaths) {
|
|
7091
|
+
const presence = conventionalPresences.get(path4) ?? "unreadable";
|
|
7092
|
+
if (presence === "file") presentConventionalPaths.push(path4);
|
|
7036
7093
|
}
|
|
7037
|
-
const presentLocalePaths = localePaths.filter((
|
|
7094
|
+
const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
|
|
7038
7095
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7039
7096
|
const substrateLocales = [];
|
|
7040
7097
|
for (const locale of substrateNames) {
|
|
@@ -7086,6 +7143,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7086
7143
|
);
|
|
7087
7144
|
}
|
|
7088
7145
|
}
|
|
7146
|
+
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7089
7147
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7090
7148
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7091
7149
|
throw new Error(
|
|
@@ -7125,13 +7183,14 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7125
7183
|
moduleName: mod.name,
|
|
7126
7184
|
hasRootContributions,
|
|
7127
7185
|
hasRootSlots,
|
|
7186
|
+
hasServerRegistrations,
|
|
7128
7187
|
rootLocales,
|
|
7129
7188
|
enhancements
|
|
7130
7189
|
});
|
|
7131
7190
|
if (rootLocales.length > 0) {
|
|
7132
7191
|
for (const locale of rootLocales) {
|
|
7133
|
-
const
|
|
7134
|
-
assertRealLocaleJsonFilePresence(
|
|
7192
|
+
const path4 = `${modulePath}/i18n/${locale}.json`;
|
|
7193
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7135
7194
|
}
|
|
7136
7195
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7137
7196
|
const localeMessages = { en: enMessages };
|
|
@@ -7155,8 +7214,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7155
7214
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7156
7215
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7157
7216
|
for (const locale of enh.locales) {
|
|
7158
|
-
const
|
|
7159
|
-
assertRealLocaleJsonFilePresence(
|
|
7217
|
+
const path4 = `${peerDir}/i18n/${locale}.json`;
|
|
7218
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7160
7219
|
}
|
|
7161
7220
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7162
7221
|
const localeMessages = { en: enMessages };
|
|
@@ -7328,6 +7387,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7328
7387
|
MODULE_DATASTORES_GEN_PATH,
|
|
7329
7388
|
MODULE_I18N_GEN_PATH,
|
|
7330
7389
|
MODULE_INIT_SERVER_GEN_PATH,
|
|
7390
|
+
MODULE_SERVER_GEN_PATH,
|
|
7331
7391
|
...existingGeneratedPaths,
|
|
7332
7392
|
...Object.keys(existingAppPages),
|
|
7333
7393
|
...Object.keys(existingAppRoutes)
|
|
@@ -7356,16 +7416,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7356
7416
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7357
7417
|
const existingFiles = {};
|
|
7358
7418
|
const remainingCandidatePaths = [];
|
|
7359
|
-
for (const
|
|
7360
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7361
|
-
existingFiles[
|
|
7419
|
+
for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
|
|
7420
|
+
if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
|
|
7421
|
+
existingFiles[path4] = existingAppPages[path4];
|
|
7362
7422
|
continue;
|
|
7363
7423
|
}
|
|
7364
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7365
|
-
existingFiles[
|
|
7424
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
|
|
7425
|
+
existingFiles[path4] = existingAppRoutes[path4];
|
|
7366
7426
|
continue;
|
|
7367
7427
|
}
|
|
7368
|
-
remainingCandidatePaths.push(
|
|
7428
|
+
remainingCandidatePaths.push(path4);
|
|
7369
7429
|
}
|
|
7370
7430
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7371
7431
|
context,
|
|
@@ -7373,12 +7433,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7373
7433
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7374
7434
|
);
|
|
7375
7435
|
const presentCandidatePaths = [];
|
|
7376
|
-
for (const
|
|
7436
|
+
for (const path4 of remainingCandidatePaths) {
|
|
7377
7437
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7378
|
-
|
|
7379
|
-
candidateKinds.get(
|
|
7438
|
+
path4,
|
|
7439
|
+
candidateKinds.get(path4) ?? "unreadable"
|
|
7380
7440
|
);
|
|
7381
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7441
|
+
if (presence === "file") presentCandidatePaths.push(path4);
|
|
7382
7442
|
}
|
|
7383
7443
|
try {
|
|
7384
7444
|
Object.assign(
|
|
@@ -7386,9 +7446,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7386
7446
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7387
7447
|
);
|
|
7388
7448
|
} catch (error) {
|
|
7389
|
-
const
|
|
7449
|
+
const path4 = readFailurePath(error);
|
|
7390
7450
|
throw new Error(
|
|
7391
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7451
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
|
|
7392
7452
|
error instanceof Error ? error.message : String(error)
|
|
7393
7453
|
)}`
|
|
7394
7454
|
);
|
|
@@ -7457,14 +7517,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7457
7517
|
var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
7458
7518
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
7459
7519
|
|
|
7520
|
+
// src/module-boundaries.ts
|
|
7521
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
7522
|
+
var import_node_path2 = __toESM(require("path"));
|
|
7523
|
+
var import_typescript5 = __toESM(require("typescript"));
|
|
7524
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
7525
|
+
var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
7526
|
+
var DATA_FILE = /\.(?:sql|json)$/;
|
|
7527
|
+
function dataPathsIn(fileName, source) {
|
|
7528
|
+
if (!/\.(?:sql|json)["'`]/.test(source)) return [];
|
|
7529
|
+
const paths = [];
|
|
7530
|
+
const visit = (node) => {
|
|
7531
|
+
if ((import_typescript5.default.isStringLiteralLike(node) || import_typescript5.default.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
|
|
7532
|
+
paths.push(node.text);
|
|
7533
|
+
}
|
|
7534
|
+
import_typescript5.default.forEachChild(node, visit);
|
|
7535
|
+
};
|
|
7536
|
+
visit(import_typescript5.default.createSourceFile(fileName, source, import_typescript5.default.ScriptTarget.Latest, false));
|
|
7537
|
+
return paths;
|
|
7538
|
+
}
|
|
7539
|
+
function scanModule(modulesDir, module2) {
|
|
7540
|
+
const scanned = /* @__PURE__ */ new Map();
|
|
7541
|
+
const moduleDir = import_node_path2.default.join(modulesDir, module2);
|
|
7542
|
+
if (!import_node_fs2.default.existsSync(moduleDir)) return scanned;
|
|
7543
|
+
const files = import_node_fs2.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
|
|
7544
|
+
for (const relative of files) {
|
|
7545
|
+
const absolute = import_node_path2.default.join(moduleDir, relative);
|
|
7546
|
+
if (!import_node_fs2.default.statSync(absolute).isFile()) continue;
|
|
7547
|
+
const source = import_node_fs2.default.readFileSync(absolute, "utf8");
|
|
7548
|
+
const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
|
|
7549
|
+
const dataPaths = dataPathsIn(absolute, source);
|
|
7550
|
+
const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
|
|
7551
|
+
if (spec.startsWith("@/modules/"))
|
|
7552
|
+
return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
|
|
7553
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
7554
|
+
return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
|
|
7555
|
+
}
|
|
7556
|
+
return [];
|
|
7557
|
+
});
|
|
7558
|
+
scanned.set(absolute, {
|
|
7559
|
+
module: module2,
|
|
7560
|
+
relative: relative.split(import_node_path2.default.sep).join("/"),
|
|
7561
|
+
targets,
|
|
7562
|
+
dataPaths
|
|
7563
|
+
});
|
|
7564
|
+
}
|
|
7565
|
+
return scanned;
|
|
7566
|
+
}
|
|
7567
|
+
function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
|
|
7568
|
+
const files = /* @__PURE__ */ new Map();
|
|
7569
|
+
for (const [module2, peers] of Object.entries(peersByModule)) {
|
|
7570
|
+
if (peers.length === 0) continue;
|
|
7571
|
+
for (const [absolute, file] of scanModule(modulesDir, module2)) files.set(absolute, file);
|
|
7572
|
+
}
|
|
7573
|
+
const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
|
|
7574
|
+
(q) => file.relative.startsWith(`enhancements/${q}/`)
|
|
7575
|
+
);
|
|
7576
|
+
const goneWith = (target, peer) => {
|
|
7577
|
+
const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
|
|
7578
|
+
return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
|
|
7579
|
+
};
|
|
7580
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
7581
|
+
const queue = [];
|
|
7582
|
+
const taint = (absolute, peer) => {
|
|
7583
|
+
const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
|
|
7584
|
+
if (peers.has(peer)) return;
|
|
7585
|
+
peers.add(peer);
|
|
7586
|
+
tainted.set(absolute, peers);
|
|
7587
|
+
queue.push([absolute, peer]);
|
|
7588
|
+
};
|
|
7589
|
+
const importers = /* @__PURE__ */ new Map();
|
|
7590
|
+
for (const [absolute, file] of files) {
|
|
7591
|
+
for (const target of file.targets) {
|
|
7592
|
+
const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
|
|
7593
|
+
if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
|
|
7594
|
+
}
|
|
7595
|
+
for (const peer of peersByModule[file.module] ?? []) {
|
|
7596
|
+
if (exempt(file, peer)) continue;
|
|
7597
|
+
if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
|
|
7598
|
+
(p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
|
|
7599
|
+
)) {
|
|
7600
|
+
taint(absolute, peer);
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
7605
|
+
const [target, peer] = next;
|
|
7606
|
+
for (const importer of importers.get(target) ?? []) {
|
|
7607
|
+
const file = files.get(importer);
|
|
7608
|
+
if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
|
|
7609
|
+
taint(importer, peer);
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
}
|
|
7613
|
+
const violations = [];
|
|
7614
|
+
for (const [absolute, peers] of tainted) {
|
|
7615
|
+
const file = files.get(absolute);
|
|
7616
|
+
for (const peer of peers) {
|
|
7617
|
+
violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
|
|
7621
|
+
}
|
|
7622
|
+
function findOptionalPeerViolations(modulesDir) {
|
|
7623
|
+
if (!import_node_fs2.default.existsSync(modulesDir)) return [];
|
|
7624
|
+
const manifests = {};
|
|
7625
|
+
for (const name of import_node_fs2.default.readdirSync(modulesDir)) {
|
|
7626
|
+
const manifestPath = import_node_path2.default.join(modulesDir, name, "module.json");
|
|
7627
|
+
if (!import_node_fs2.default.existsSync(manifestPath)) continue;
|
|
7628
|
+
manifests[name] = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf8"));
|
|
7629
|
+
}
|
|
7630
|
+
const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
|
|
7631
|
+
const peersByModule = {};
|
|
7632
|
+
for (const [name, manifest] of Object.entries(manifests)) {
|
|
7633
|
+
const uses = new Set(manifest.uses ?? []);
|
|
7634
|
+
peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
|
|
7635
|
+
}
|
|
7636
|
+
return findModuleBoundaryViolations(modulesDir, peersByModule);
|
|
7637
|
+
}
|
|
7638
|
+
function formatBoundaryViolations(modulesDirLabel, violations) {
|
|
7639
|
+
const files = new Set(violations.map((v) => v.file)).size;
|
|
7640
|
+
return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
|
|
7641
|
+
\`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).
|
|
7642
|
+
` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
|
|
7643
|
+
}
|
|
7644
|
+
|
|
7460
7645
|
// src/index.ts
|
|
7461
7646
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7462
7647
|
function readComposeInputs(cwd) {
|
|
7463
|
-
const file =
|
|
7464
|
-
if (!
|
|
7648
|
+
const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7649
|
+
if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7465
7650
|
let raw;
|
|
7466
7651
|
try {
|
|
7467
|
-
raw = JSON.parse(
|
|
7652
|
+
raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
|
|
7468
7653
|
} catch (error) {
|
|
7469
7654
|
throw new Error(
|
|
7470
7655
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7506,18 +7691,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7506
7691
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7507
7692
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7508
7693
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7509
|
-
const file =
|
|
7510
|
-
const existing =
|
|
7694
|
+
const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7695
|
+
const existing = import_node_fs3.default.existsSync(file) ? import_node_fs3.default.readFileSync(file, "utf8") : null;
|
|
7511
7696
|
if (existing !== content) {
|
|
7512
|
-
|
|
7513
|
-
|
|
7697
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
|
|
7698
|
+
import_node_fs3.default.writeFileSync(file, content);
|
|
7514
7699
|
}
|
|
7515
7700
|
return entries.length;
|
|
7516
7701
|
}
|
|
7517
7702
|
function assertStubGitignoreOwned(cwd) {
|
|
7518
|
-
const file =
|
|
7519
|
-
if (!
|
|
7520
|
-
const firstLine =
|
|
7703
|
+
const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7704
|
+
if (!import_node_fs3.default.existsSync(file)) return;
|
|
7705
|
+
const firstLine = import_node_fs3.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
|
|
7521
7706
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7522
7707
|
throw new Error(
|
|
7523
7708
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7525,8 +7710,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7525
7710
|
}
|
|
7526
7711
|
}
|
|
7527
7712
|
function assertAppRoot(cwd) {
|
|
7528
|
-
const appPath =
|
|
7529
|
-
if (!
|
|
7713
|
+
const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
|
|
7714
|
+
if (!import_node_fs3.default.existsSync(appPath)) {
|
|
7530
7715
|
throw new Error(
|
|
7531
7716
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7532
7717
|
);
|
|
@@ -7572,9 +7757,9 @@ ${porcelain.trim()}`
|
|
|
7572
7757
|
}
|
|
7573
7758
|
var APP_SRC_DIR = "apps/web/src";
|
|
7574
7759
|
function walkSources(dir, skip, out) {
|
|
7575
|
-
if (!
|
|
7576
|
-
for (const entry of
|
|
7577
|
-
const full =
|
|
7760
|
+
if (!import_node_fs3.default.existsSync(dir)) return;
|
|
7761
|
+
for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
|
|
7762
|
+
const full = import_node_path3.default.join(dir, entry.name);
|
|
7578
7763
|
if (full === skip) continue;
|
|
7579
7764
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7580
7765
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7583,13 +7768,13 @@ function walkSources(dir, skip, out) {
|
|
|
7583
7768
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7584
7769
|
if (excluded.length === 0) return;
|
|
7585
7770
|
const files = [];
|
|
7586
|
-
walkSources(
|
|
7771
|
+
walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
|
|
7587
7772
|
const offenders = [];
|
|
7588
7773
|
for (const file of files) {
|
|
7589
|
-
const source =
|
|
7774
|
+
const source = import_node_fs3.default.readFileSync(file, "utf8");
|
|
7590
7775
|
for (const name of excluded) {
|
|
7591
7776
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7592
|
-
offenders.push(`${
|
|
7777
|
+
offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
|
|
7593
7778
|
}
|
|
7594
7779
|
}
|
|
7595
7780
|
}
|
|
@@ -7602,8 +7787,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
|
|
|
7602
7787
|
}
|
|
7603
7788
|
}
|
|
7604
7789
|
function removeDir(absolute) {
|
|
7605
|
-
if (!
|
|
7606
|
-
|
|
7790
|
+
if (!import_node_fs3.default.existsSync(absolute)) return false;
|
|
7791
|
+
import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
|
|
7607
7792
|
return true;
|
|
7608
7793
|
}
|
|
7609
7794
|
async function compose(options) {
|
|
@@ -7630,6 +7815,16 @@ async function compose(options) {
|
|
|
7630
7815
|
dryRun: true
|
|
7631
7816
|
});
|
|
7632
7817
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
7818
|
+
const violations = findModuleBoundaryViolations(
|
|
7819
|
+
import_node_path3.default.join(options.cwd, MODULES_DIR2),
|
|
7820
|
+
Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
|
|
7821
|
+
{ pruning: true }
|
|
7822
|
+
);
|
|
7823
|
+
if (violations.length > 0) {
|
|
7824
|
+
throw new Error(
|
|
7825
|
+
`[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
|
|
7826
|
+
);
|
|
7827
|
+
}
|
|
7633
7828
|
}
|
|
7634
7829
|
const result = await reconcileCompositionArtifacts(context, {
|
|
7635
7830
|
enabledModulesRaw: options.enabled,
|
|
@@ -7641,11 +7836,11 @@ async function compose(options) {
|
|
|
7641
7836
|
let pruned = 0;
|
|
7642
7837
|
if (options.prune) {
|
|
7643
7838
|
for (const name of result.excludedModules) {
|
|
7644
|
-
if (removeDir(
|
|
7839
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7645
7840
|
}
|
|
7646
7841
|
for (const owner of result.composedModules) {
|
|
7647
7842
|
for (const peer of result.excludedModules) {
|
|
7648
|
-
if (removeDir(
|
|
7843
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7649
7844
|
}
|
|
7650
7845
|
}
|
|
7651
7846
|
}
|
|
@@ -7664,5 +7859,8 @@ async function compose(options) {
|
|
|
7664
7859
|
assertSupportedTypescript,
|
|
7665
7860
|
compose,
|
|
7666
7861
|
escapeGitignore,
|
|
7862
|
+
findModuleBoundaryViolations,
|
|
7863
|
+
findOptionalPeerViolations,
|
|
7864
|
+
formatBoundaryViolations,
|
|
7667
7865
|
readComposeInputs
|
|
7668
7866
|
});
|