@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/cli.js
CHANGED
|
@@ -2066,23 +2066,23 @@ function isValidModuleName(value) {
|
|
|
2066
2066
|
var projectSlugSchema = import_zod.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
2067
2067
|
var moduleKindSchema = import_zod.z.enum(["feature", "library", "contract"]);
|
|
2068
2068
|
var endpointMethodSchema = import_zod.z.enum(ENDPOINT_METHODS);
|
|
2069
|
-
function endpointPathValidationError(
|
|
2070
|
-
if (!
|
|
2069
|
+
function endpointPathValidationError(path5) {
|
|
2070
|
+
if (!path5.startsWith("/api/")) {
|
|
2071
2071
|
return 'must begin with "/api/"';
|
|
2072
2072
|
}
|
|
2073
|
-
if (
|
|
2073
|
+
if (path5.endsWith("/")) {
|
|
2074
2074
|
return "must not end with a trailing slash";
|
|
2075
2075
|
}
|
|
2076
|
-
if (
|
|
2076
|
+
if (path5.includes("?") || path5.includes("#")) {
|
|
2077
2077
|
return "must not include query or hash";
|
|
2078
2078
|
}
|
|
2079
|
-
if (
|
|
2079
|
+
if (path5.includes("\\")) {
|
|
2080
2080
|
return "must not include backslashes";
|
|
2081
2081
|
}
|
|
2082
|
-
if (
|
|
2082
|
+
if (path5.includes("%")) {
|
|
2083
2083
|
return "must not include percent-encoded segments";
|
|
2084
2084
|
}
|
|
2085
|
-
const parts =
|
|
2085
|
+
const parts = path5.split("/");
|
|
2086
2086
|
if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
|
|
2087
2087
|
return 'must begin with "/api/"';
|
|
2088
2088
|
}
|
|
@@ -2132,12 +2132,12 @@ function endpointPathValidationError(path3) {
|
|
|
2132
2132
|
}
|
|
2133
2133
|
return null;
|
|
2134
2134
|
}
|
|
2135
|
-
function normalizeEndpointPathForCollision(
|
|
2136
|
-
const error = endpointPathValidationError(
|
|
2135
|
+
function normalizeEndpointPathForCollision(path5) {
|
|
2136
|
+
const error = endpointPathValidationError(path5);
|
|
2137
2137
|
if (error != null) {
|
|
2138
2138
|
throw new Error(`invalid endpoint path: ${error}`);
|
|
2139
2139
|
}
|
|
2140
|
-
return
|
|
2140
|
+
return path5.split("/").map((segment, index) => {
|
|
2141
2141
|
if (index < 2) {
|
|
2142
2142
|
return segment;
|
|
2143
2143
|
}
|
|
@@ -2150,8 +2150,8 @@ function normalizeEndpointPathForCollision(path3) {
|
|
|
2150
2150
|
return segment;
|
|
2151
2151
|
}).join("/");
|
|
2152
2152
|
}
|
|
2153
|
-
var endpointPathSchema = import_zod.z.string().superRefine((
|
|
2154
|
-
const error = endpointPathValidationError(
|
|
2153
|
+
var endpointPathSchema = import_zod.z.string().superRefine((path5, ctx) => {
|
|
2154
|
+
const error = endpointPathValidationError(path5);
|
|
2155
2155
|
if (error != null) {
|
|
2156
2156
|
ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
|
|
2157
2157
|
}
|
|
@@ -2398,16 +2398,16 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2398
2398
|
}
|
|
2399
2399
|
}
|
|
2400
2400
|
const seenWorkflows = /* @__PURE__ */ new Map();
|
|
2401
|
-
const noteWorkflow = (name,
|
|
2401
|
+
const noteWorkflow = (name, path5) => {
|
|
2402
2402
|
const existing = seenWorkflows.get(name);
|
|
2403
2403
|
if (existing != null) {
|
|
2404
2404
|
ctx.addIssue({
|
|
2405
2405
|
code: import_zod.z.ZodIssueCode.custom,
|
|
2406
2406
|
message: `duplicate workflow name "${name}" (also at ${existing})`,
|
|
2407
|
-
path:
|
|
2407
|
+
path: path5
|
|
2408
2408
|
});
|
|
2409
2409
|
} else {
|
|
2410
|
-
seenWorkflows.set(name,
|
|
2410
|
+
seenWorkflows.set(name, path5.join("."));
|
|
2411
2411
|
}
|
|
2412
2412
|
};
|
|
2413
2413
|
for (let i = 0; i < manifest.workflows.length; i += 1) {
|
|
@@ -2419,7 +2419,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2419
2419
|
}
|
|
2420
2420
|
}
|
|
2421
2421
|
const seenEndpointKeys = /* @__PURE__ */ new Map();
|
|
2422
|
-
const noteEndpoint = (claim,
|
|
2422
|
+
const noteEndpoint = (claim, path5) => {
|
|
2423
2423
|
let key;
|
|
2424
2424
|
try {
|
|
2425
2425
|
key = normalizeEndpointPathForCollision(claim.path);
|
|
@@ -2431,10 +2431,10 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2431
2431
|
ctx.addIssue({
|
|
2432
2432
|
code: import_zod.z.ZodIssueCode.custom,
|
|
2433
2433
|
message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
|
|
2434
|
-
path:
|
|
2434
|
+
path: path5
|
|
2435
2435
|
});
|
|
2436
2436
|
} else {
|
|
2437
|
-
seenEndpointKeys.set(key,
|
|
2437
|
+
seenEndpointKeys.set(key, path5.join("."));
|
|
2438
2438
|
}
|
|
2439
2439
|
};
|
|
2440
2440
|
for (let i = 0; i < manifest.endpoints.length; i += 1) {
|
|
@@ -2535,8 +2535,8 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2535
2535
|
}).transform(({ version: _legacyVersion, ...manifest }) => manifest);
|
|
2536
2536
|
function formatZodErrors(error) {
|
|
2537
2537
|
return error.issues.map((issue) => {
|
|
2538
|
-
const
|
|
2539
|
-
return `${
|
|
2538
|
+
const path5 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2539
|
+
return `${path5}: ${issue.message}`;
|
|
2540
2540
|
});
|
|
2541
2541
|
}
|
|
2542
2542
|
function parseModuleManifest(raw) {
|
|
@@ -2936,16 +2936,16 @@ var collectedE2eFactsSchema = import_zod3.z.object({
|
|
|
2936
2936
|
function validateRouteExportEvidenceCongruence(input) {
|
|
2937
2937
|
const entrySet = new Set(input.routeEntries);
|
|
2938
2938
|
const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
|
|
2939
|
-
for (const
|
|
2940
|
-
if (!entrySet.has(
|
|
2941
|
-
input.onIssue(`routePageExports path "${
|
|
2939
|
+
for (const path5 of exportPaths) {
|
|
2940
|
+
if (!entrySet.has(path5)) {
|
|
2941
|
+
input.onIssue(`routePageExports path "${path5}" is not listed in routeEntries`);
|
|
2942
2942
|
}
|
|
2943
2943
|
}
|
|
2944
2944
|
if (input.routeCollectionErrors.length === 0) {
|
|
2945
2945
|
const exportSet = new Set(exportPaths);
|
|
2946
|
-
for (const
|
|
2947
|
-
if (!exportSet.has(
|
|
2948
|
-
input.onIssue(`routeEntries path "${
|
|
2946
|
+
for (const path5 of input.routeEntries) {
|
|
2947
|
+
if (!exportSet.has(path5)) {
|
|
2948
|
+
input.onIssue(`routeEntries path "${path5}" is missing from routePageExports`);
|
|
2949
2949
|
}
|
|
2950
2950
|
}
|
|
2951
2951
|
}
|
|
@@ -3896,16 +3896,18 @@ var MODULE_CONTRIBUTIONS_GEN_PATH = `${APP_WEB_PREFIX}src/module-contributions.g
|
|
|
3896
3896
|
var MODULE_DATASTORES_GEN_PATH = `${APP_WEB_PREFIX}src/module-datastores.gen.ts`;
|
|
3897
3897
|
var MODULE_I18N_GEN_PATH = `${APP_WEB_PREFIX}src/module-i18n.gen.ts`;
|
|
3898
3898
|
var MODULE_INIT_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-init.server.gen.ts`;
|
|
3899
|
+
var MODULE_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-server.gen.ts`;
|
|
3899
3900
|
var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
|
|
3900
3901
|
var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
|
|
3901
3902
|
|
|
3902
3903
|
// src/cli.ts
|
|
3903
|
-
var
|
|
3904
|
+
var import_typescript6 = __toESM(require("typescript"));
|
|
3905
|
+
var import_node_path4 = __toESM(require("path"));
|
|
3904
3906
|
|
|
3905
3907
|
// src/index.ts
|
|
3906
3908
|
var import_node_child_process2 = require("child_process");
|
|
3907
|
-
var
|
|
3908
|
-
var
|
|
3909
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
3910
|
+
var import_node_path3 = __toESM(require("path"));
|
|
3909
3911
|
|
|
3910
3912
|
// ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
|
|
3911
3913
|
var import_node_child_process = require("child_process");
|
|
@@ -4141,7 +4143,8 @@ var COMPOSITION_RUNTIME_GEN_PATHS = [
|
|
|
4141
4143
|
MODULE_CONTRIBUTIONS_GEN_PATH,
|
|
4142
4144
|
MODULE_I18N_GEN_PATH,
|
|
4143
4145
|
MODULE_INIT_SERVER_GEN_PATH,
|
|
4144
|
-
MODULE_DATASTORES_GEN_PATH
|
|
4146
|
+
MODULE_DATASTORES_GEN_PATH,
|
|
4147
|
+
MODULE_SERVER_GEN_PATH
|
|
4145
4148
|
];
|
|
4146
4149
|
function compareNames(a, b) {
|
|
4147
4150
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
@@ -4162,6 +4165,9 @@ function formatI18nMarker() {
|
|
|
4162
4165
|
function formatInitMarker() {
|
|
4163
4166
|
return `// ${MODULE_STUB_MARKER} scope=init`;
|
|
4164
4167
|
}
|
|
4168
|
+
function formatServerMarker() {
|
|
4169
|
+
return `// ${MODULE_STUB_MARKER} scope=server`;
|
|
4170
|
+
}
|
|
4165
4171
|
function formatDatastoresMarker() {
|
|
4166
4172
|
return `// ${MODULE_STUB_MARKER} scope=datastores`;
|
|
4167
4173
|
}
|
|
@@ -4257,13 +4263,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4257
4263
|
function createMessageTree() {
|
|
4258
4264
|
return /* @__PURE__ */ Object.create(null);
|
|
4259
4265
|
}
|
|
4260
|
-
function assertMessageTree(value,
|
|
4266
|
+
function assertMessageTree(value, path5) {
|
|
4261
4267
|
if (typeof value === "string") return;
|
|
4262
4268
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4263
|
-
throw new Error(`Invalid message at ${
|
|
4269
|
+
throw new Error(`Invalid message at ${path5}: expected string or nested object`);
|
|
4264
4270
|
}
|
|
4265
4271
|
for (const [key, child] of Object.entries(value)) {
|
|
4266
|
-
assertMessageTree(child, `${
|
|
4272
|
+
assertMessageTree(child, `${path5}.${key}`);
|
|
4267
4273
|
}
|
|
4268
4274
|
}
|
|
4269
4275
|
function cloneTree(value) {
|
|
@@ -4273,17 +4279,17 @@ function cloneTree(value) {
|
|
|
4273
4279
|
}
|
|
4274
4280
|
return clone;
|
|
4275
4281
|
}
|
|
4276
|
-
function collectLeafPaths(value,
|
|
4282
|
+
function collectLeafPaths(value, path5, out) {
|
|
4277
4283
|
if (typeof value === "string") {
|
|
4278
|
-
out.push(
|
|
4284
|
+
out.push(path5);
|
|
4279
4285
|
return;
|
|
4280
4286
|
}
|
|
4281
|
-
assertMessageTree(value,
|
|
4282
|
-
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${
|
|
4287
|
+
assertMessageTree(value, path5);
|
|
4288
|
+
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path5}.${key}`, out);
|
|
4283
4289
|
}
|
|
4284
|
-
function overrideInto(target, source, owner,
|
|
4290
|
+
function overrideInto(target, source, owner, path5, missing) {
|
|
4285
4291
|
for (const [key, value] of Object.entries(source)) {
|
|
4286
|
-
const nextPath =
|
|
4292
|
+
const nextPath = path5 ? `${path5}.${key}` : key;
|
|
4287
4293
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4288
4294
|
collectLeafPaths(value, nextPath, missing);
|
|
4289
4295
|
continue;
|
|
@@ -4310,9 +4316,9 @@ function overrideInto(target, source, owner, path3, missing) {
|
|
|
4310
4316
|
function composeLocaleMessagesForPlan(layers) {
|
|
4311
4317
|
const result = createMessageTree();
|
|
4312
4318
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4313
|
-
function mergeInto(target, source, owner,
|
|
4319
|
+
function mergeInto(target, source, owner, path5) {
|
|
4314
4320
|
for (const [key, value] of Object.entries(source)) {
|
|
4315
|
-
const nextPath =
|
|
4321
|
+
const nextPath = path5 ? `${path5}.${key}` : key;
|
|
4316
4322
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4317
4323
|
const existing = hasExisting ? target[key] : void 0;
|
|
4318
4324
|
if (typeof value === "string") {
|
|
@@ -4805,6 +4811,42 @@ function emitModuleInitServerGenTs(input) {
|
|
|
4805
4811
|
""
|
|
4806
4812
|
].join("\n");
|
|
4807
4813
|
}
|
|
4814
|
+
function emitModuleServerGenTs(input) {
|
|
4815
|
+
const installed = new Set(input.registry.modules.map((m) => m.name));
|
|
4816
|
+
const moduleNames = input.facts.filter((f) => f.hasServerRegistrations && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames);
|
|
4817
|
+
for (const name of moduleNames) {
|
|
4818
|
+
if (!isValidModuleName(name)) {
|
|
4819
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4820
|
+
}
|
|
4821
|
+
}
|
|
4822
|
+
const importLines = moduleNames.map(
|
|
4823
|
+
(name, index) => [
|
|
4824
|
+
`@/modules/${name}/server/registrations`,
|
|
4825
|
+
`import * as serverRegistrations${index} from ${JSON.stringify(`@/modules/${name}/server/registrations`)};`
|
|
4826
|
+
]
|
|
4827
|
+
).sort((a, b) => compareImportSpecifiers(a[0], b[0])).map(([, line]) => line);
|
|
4828
|
+
const entryLines = moduleNames.map(
|
|
4829
|
+
(name, index) => ` { module: ${JSON.stringify(name)}, registrations: serverRegistrations${index} },`
|
|
4830
|
+
);
|
|
4831
|
+
return [
|
|
4832
|
+
formatServerMarker(),
|
|
4833
|
+
"/** DO NOT EDIT \u2014 owned by the module install rail. */",
|
|
4834
|
+
"",
|
|
4835
|
+
...importLines,
|
|
4836
|
+
...importLines.length > 0 ? [""] : [],
|
|
4837
|
+
"export type ModuleServerRegistration = {",
|
|
4838
|
+
" module: string;",
|
|
4839
|
+
" registrations: Readonly<Record<string, unknown>>;",
|
|
4840
|
+
"};",
|
|
4841
|
+
"",
|
|
4842
|
+
...entryLines.length === 0 ? ["export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [];"] : [
|
|
4843
|
+
"export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [",
|
|
4844
|
+
...entryLines,
|
|
4845
|
+
"];"
|
|
4846
|
+
],
|
|
4847
|
+
""
|
|
4848
|
+
].join("\n");
|
|
4849
|
+
}
|
|
4808
4850
|
|
|
4809
4851
|
// ../../packages/lib/src/server/module-rail/composition-artifacts.ts
|
|
4810
4852
|
var COMPOSITION_ARTIFACTS_GENERATED_BY = "module-rail";
|
|
@@ -4820,8 +4862,8 @@ var METHOD_ORDER = {
|
|
|
4820
4862
|
function compareNames2(a, b) {
|
|
4821
4863
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4822
4864
|
}
|
|
4823
|
-
function endpointPairKey(method,
|
|
4824
|
-
return `${method} ${
|
|
4865
|
+
function endpointPairKey(method, path5) {
|
|
4866
|
+
return `${method} ${path5}`;
|
|
4825
4867
|
}
|
|
4826
4868
|
function apiPathToRouteFile(apiPath) {
|
|
4827
4869
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4835,11 +4877,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4835
4877
|
}
|
|
4836
4878
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4837
4879
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4838
|
-
function appRouteFileToNextRelativePath(
|
|
4839
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
4880
|
+
function appRouteFileToNextRelativePath(path5) {
|
|
4881
|
+
const pagePath = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
|
|
4840
4882
|
if (pagePath != null) return pagePath;
|
|
4841
|
-
if (!
|
|
4842
|
-
const relative =
|
|
4883
|
+
if (!path5.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
4884
|
+
const relative = path5.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4843
4885
|
if (!relative.startsWith("api/")) return null;
|
|
4844
4886
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4845
4887
|
if (!suffix) return null;
|
|
@@ -4867,6 +4909,14 @@ function formatRouteMarker(owner, nextRelativePath) {
|
|
|
4867
4909
|
function formatRegistryMarker() {
|
|
4868
4910
|
return `// ${MODULE_STUB_MARKER} scope=registry`;
|
|
4869
4911
|
}
|
|
4912
|
+
var OWNERLESS_MARKER_KINDS = [
|
|
4913
|
+
"registry",
|
|
4914
|
+
"contributions",
|
|
4915
|
+
"datastores",
|
|
4916
|
+
"i18n",
|
|
4917
|
+
"init",
|
|
4918
|
+
"server"
|
|
4919
|
+
];
|
|
4870
4920
|
function parseCompositionArtifactMarker(source) {
|
|
4871
4921
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4872
4922
|
const match = firstLine.match(
|
|
@@ -4877,26 +4927,8 @@ function parseCompositionArtifactMarker(source) {
|
|
|
4877
4927
|
if (!match) return null;
|
|
4878
4928
|
const owner = match[1];
|
|
4879
4929
|
const scope = match[2];
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
return { kind: "registry" };
|
|
4883
|
-
}
|
|
4884
|
-
if (scope === "contributions") {
|
|
4885
|
-
if (owner) return null;
|
|
4886
|
-
return { kind: "contributions" };
|
|
4887
|
-
}
|
|
4888
|
-
if (scope === "datastores") {
|
|
4889
|
-
if (owner) return null;
|
|
4890
|
-
return { kind: "datastores" };
|
|
4891
|
-
}
|
|
4892
|
-
if (scope === "i18n") {
|
|
4893
|
-
if (owner) return null;
|
|
4894
|
-
return { kind: "i18n" };
|
|
4895
|
-
}
|
|
4896
|
-
if (scope === "init") {
|
|
4897
|
-
if (owner) return null;
|
|
4898
|
-
return { kind: "init" };
|
|
4899
|
-
}
|
|
4930
|
+
const ownerless = OWNERLESS_MARKER_KINDS.find((kind) => kind === scope);
|
|
4931
|
+
if (ownerless) return owner ? null : { kind: ownerless };
|
|
4900
4932
|
if (!owner || !isValidModuleName(owner)) return null;
|
|
4901
4933
|
if (scope === "route") {
|
|
4902
4934
|
const nextRelativePath = match[3]?.trim();
|
|
@@ -4941,9 +4973,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4941
4973
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4942
4974
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4943
4975
|
}
|
|
4944
|
-
function parseLegacyApiAdapter(
|
|
4945
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4946
|
-
if (expectedMarkerKindForPath(
|
|
4976
|
+
function parseLegacyApiAdapter(path5, source) {
|
|
4977
|
+
const physicalPath = appRouteFileToNextRelativePath(path5);
|
|
4978
|
+
if (expectedMarkerKindForPath(path5) !== "endpoint" || physicalPath == null) return null;
|
|
4947
4979
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4948
4980
|
const match = firstLine.match(
|
|
4949
4981
|
new RegExp(
|
|
@@ -4953,7 +4985,7 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4953
4985
|
if (!match || match[2] !== physicalPath) return null;
|
|
4954
4986
|
const owner = match[1];
|
|
4955
4987
|
if (!isValidModuleName(owner)) return null;
|
|
4956
|
-
const file = import_typescript4.default.createSourceFile(
|
|
4988
|
+
const file = import_typescript4.default.createSourceFile(path5, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
|
|
4957
4989
|
if (file.parseDiagnostics.length > 0) {
|
|
4958
4990
|
return null;
|
|
4959
4991
|
}
|
|
@@ -4995,9 +5027,9 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4995
5027
|
}
|
|
4996
5028
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
4997
5029
|
}
|
|
4998
|
-
function parseLegacyRouteAdapter(
|
|
4999
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
5000
|
-
if (expectedMarkerKindForPath(
|
|
5030
|
+
function parseLegacyRouteAdapter(path5, source) {
|
|
5031
|
+
const physicalPath = appRouteFileToNextRelativePath(path5);
|
|
5032
|
+
if (expectedMarkerKindForPath(path5) !== "route" || physicalPath == null) return null;
|
|
5001
5033
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
5002
5034
|
const match = normalized.match(
|
|
5003
5035
|
new RegExp(
|
|
@@ -5009,29 +5041,30 @@ function parseLegacyRouteAdapter(path3, source) {
|
|
|
5009
5041
|
const nextRelativePath = match[2];
|
|
5010
5042
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
5011
5043
|
}
|
|
5012
|
-
function legacyRouteAdapterMatchesStub(
|
|
5013
|
-
const legacy = parseLegacyRouteAdapter(
|
|
5044
|
+
function legacyRouteAdapterMatchesStub(path5, source, stub) {
|
|
5045
|
+
const legacy = parseLegacyRouteAdapter(path5, source);
|
|
5014
5046
|
return legacy != null && legacy.owner === stub.owner;
|
|
5015
5047
|
}
|
|
5016
5048
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
5017
5049
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
5018
5050
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
5019
|
-
function expectedMarkerKindForPath(
|
|
5020
|
-
if (
|
|
5021
|
-
if (
|
|
5022
|
-
if (
|
|
5023
|
-
if (
|
|
5024
|
-
if (
|
|
5025
|
-
if (
|
|
5026
|
-
if (
|
|
5051
|
+
function expectedMarkerKindForPath(path5) {
|
|
5052
|
+
if (path5 === MODULES_GEN_PATH) return "registry";
|
|
5053
|
+
if (path5 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
|
|
5054
|
+
if (path5 === MODULE_DATASTORES_GEN_PATH) return "datastores";
|
|
5055
|
+
if (path5 === MODULE_I18N_GEN_PATH) return "i18n";
|
|
5056
|
+
if (path5 === MODULE_INIT_SERVER_GEN_PATH) return "init";
|
|
5057
|
+
if (path5 === MODULE_SERVER_GEN_PATH) return "server";
|
|
5058
|
+
if (path5.startsWith(API_ROUTE_DIR_PREFIX) && path5.endsWith("/route.ts")) return "endpoint";
|
|
5059
|
+
if (path5.startsWith(APP_DIR_PREFIX) && !path5.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path5.endsWith(suffix))) {
|
|
5027
5060
|
return "route";
|
|
5028
5061
|
}
|
|
5029
5062
|
return null;
|
|
5030
5063
|
}
|
|
5031
|
-
function markerCongruentWithPath(
|
|
5032
|
-
if (expectedMarkerKindForPath(
|
|
5064
|
+
function markerCongruentWithPath(path5, marker) {
|
|
5065
|
+
if (expectedMarkerKindForPath(path5) !== marker.kind) return false;
|
|
5033
5066
|
if (marker.kind === "route") {
|
|
5034
|
-
const derived = appPageFileToNextRelativePath(
|
|
5067
|
+
const derived = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
|
|
5035
5068
|
return derived != null && derived === marker.nextRelativePath;
|
|
5036
5069
|
}
|
|
5037
5070
|
return true;
|
|
@@ -5302,8 +5335,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5302
5335
|
function activeEnhancementPeers(registryModule) {
|
|
5303
5336
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5304
5337
|
}
|
|
5305
|
-
function endpointBucketKey(
|
|
5306
|
-
return `${
|
|
5338
|
+
function endpointBucketKey(path5, owner, scope) {
|
|
5339
|
+
return `${path5}\0${owner}\0${scopeLabel(scope)}`;
|
|
5307
5340
|
}
|
|
5308
5341
|
function planRouteStubs(input) {
|
|
5309
5342
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5427,7 +5460,7 @@ function planRouteStubs(input) {
|
|
|
5427
5460
|
for (const entry of entries) {
|
|
5428
5461
|
const nextRelativePath = entry.nextRelativePath;
|
|
5429
5462
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5430
|
-
const
|
|
5463
|
+
const path5 = routeEntryToAppPageFile(nextRelativePath);
|
|
5431
5464
|
const existing = routeDestinationOwners.get(destination);
|
|
5432
5465
|
if (existing) {
|
|
5433
5466
|
throw new Error(
|
|
@@ -5437,13 +5470,13 @@ function planRouteStubs(input) {
|
|
|
5437
5470
|
routeDestinationOwners.set(destination, {
|
|
5438
5471
|
owner: regMod.name,
|
|
5439
5472
|
nextRelativePath,
|
|
5440
|
-
path:
|
|
5473
|
+
path: path5
|
|
5441
5474
|
});
|
|
5442
5475
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5443
5476
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5444
5477
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5445
5478
|
routeStubs.push({
|
|
5446
|
-
path:
|
|
5479
|
+
path: path5,
|
|
5447
5480
|
owner: regMod.name,
|
|
5448
5481
|
nextRelativePath,
|
|
5449
5482
|
importModule,
|
|
@@ -5504,16 +5537,16 @@ function planCompositionArtifacts(input) {
|
|
|
5504
5537
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5505
5538
|
]);
|
|
5506
5539
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5507
|
-
const isProvenStaleDeletionCandidate = (
|
|
5508
|
-
if (
|
|
5509
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5510
|
-
if (desiredPaths.has(
|
|
5511
|
-
if (!existingGeneratedPathSet.has(
|
|
5512
|
-
const legacy = parseLegacyApiAdapter(
|
|
5540
|
+
const isProvenStaleDeletionCandidate = (path5, content) => {
|
|
5541
|
+
if (path5 === MODULES_GEN_PATH) return false;
|
|
5542
|
+
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path5)) return false;
|
|
5543
|
+
if (desiredPaths.has(path5)) return false;
|
|
5544
|
+
if (!existingGeneratedPathSet.has(path5)) return false;
|
|
5545
|
+
const legacy = parseLegacyApiAdapter(path5, content);
|
|
5513
5546
|
if (legacy) return true;
|
|
5514
|
-
if (parseLegacyRouteAdapter(
|
|
5547
|
+
if (parseLegacyRouteAdapter(path5, content)) return true;
|
|
5515
5548
|
const marker = parseCompositionArtifactMarker(content);
|
|
5516
|
-
return marker != null && markerCongruentWithPath(
|
|
5549
|
+
return marker != null && markerCongruentWithPath(path5, marker);
|
|
5517
5550
|
};
|
|
5518
5551
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5519
5552
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5621,6 +5654,7 @@ function planCompositionArtifacts(input) {
|
|
|
5621
5654
|
let datastoresContent;
|
|
5622
5655
|
let i18nContent;
|
|
5623
5656
|
let initContent;
|
|
5657
|
+
let serverContent;
|
|
5624
5658
|
try {
|
|
5625
5659
|
contributionsContent = emitModuleContributionsGenTs({
|
|
5626
5660
|
registry: input.registry,
|
|
@@ -5645,6 +5679,10 @@ function planCompositionArtifacts(input) {
|
|
|
5645
5679
|
registry: input.registry,
|
|
5646
5680
|
facts: compositionEntryFacts
|
|
5647
5681
|
});
|
|
5682
|
+
serverContent = emitModuleServerGenTs({
|
|
5683
|
+
registry: input.registry,
|
|
5684
|
+
facts: compositionEntryFacts
|
|
5685
|
+
});
|
|
5648
5686
|
} catch (error) {
|
|
5649
5687
|
return {
|
|
5650
5688
|
ok: false,
|
|
@@ -5657,17 +5695,18 @@ function planCompositionArtifacts(input) {
|
|
|
5657
5695
|
desired.set(MODULE_DATASTORES_GEN_PATH, datastoresContent);
|
|
5658
5696
|
desired.set(MODULE_I18N_GEN_PATH, i18nContent);
|
|
5659
5697
|
desired.set(MODULE_INIT_SERVER_GEN_PATH, initContent);
|
|
5698
|
+
desired.set(MODULE_SERVER_GEN_PATH, serverContent);
|
|
5660
5699
|
for (const stub of stubs.endpointStubs) desired.set(stub.path, stub.content);
|
|
5661
5700
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5662
5701
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5663
5702
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5664
|
-
for (const [
|
|
5665
|
-
const existing = input.existingFiles[
|
|
5703
|
+
for (const [path5] of desired) {
|
|
5704
|
+
const existing = input.existingFiles[path5];
|
|
5666
5705
|
if (existing == null) continue;
|
|
5667
5706
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5668
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5669
|
-
const plannedRoute = routeStubByPath.get(
|
|
5670
|
-
const legacy = parseLegacyApiAdapter(
|
|
5707
|
+
const plannedEndpoint = endpointStubByPath.get(path5);
|
|
5708
|
+
const plannedRoute = routeStubByPath.get(path5);
|
|
5709
|
+
const legacy = parseLegacyApiAdapter(path5, existing);
|
|
5671
5710
|
if (legacy && plannedEndpoint) {
|
|
5672
5711
|
marker = {
|
|
5673
5712
|
kind: "endpoint",
|
|
@@ -5675,66 +5714,66 @@ function planCompositionArtifacts(input) {
|
|
|
5675
5714
|
scope: plannedEndpoint.scope
|
|
5676
5715
|
};
|
|
5677
5716
|
}
|
|
5678
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5717
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path5, existing, plannedRoute)) {
|
|
5679
5718
|
marker = {
|
|
5680
5719
|
kind: "route",
|
|
5681
5720
|
owner: plannedRoute.owner,
|
|
5682
5721
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5683
5722
|
};
|
|
5684
5723
|
}
|
|
5685
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5724
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path5);
|
|
5686
5725
|
if (!marker) {
|
|
5687
5726
|
return {
|
|
5688
5727
|
ok: false,
|
|
5689
|
-
error:
|
|
5690
|
-
path:
|
|
5728
|
+
error: path5 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path5}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path5}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path5}`,
|
|
5729
|
+
path: path5
|
|
5691
5730
|
};
|
|
5692
5731
|
}
|
|
5693
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5732
|
+
const expectedKind = expectedMarkerKindForPath(path5);
|
|
5694
5733
|
if (marker.kind !== expectedKind) {
|
|
5695
5734
|
return {
|
|
5696
5735
|
ok: false,
|
|
5697
|
-
error:
|
|
5698
|
-
path:
|
|
5736
|
+
error: path5 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path5} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path5} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
|
|
5737
|
+
path: path5
|
|
5699
5738
|
};
|
|
5700
5739
|
}
|
|
5701
5740
|
if (marker.kind === "endpoint") {
|
|
5702
|
-
const stub = endpointStubByPath.get(
|
|
5741
|
+
const stub = endpointStubByPath.get(path5);
|
|
5703
5742
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5704
5743
|
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5705
5744
|
if (!ownerMatches || !scopeMatches) {
|
|
5706
5745
|
return {
|
|
5707
5746
|
ok: false,
|
|
5708
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5709
|
-
path:
|
|
5747
|
+
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
|
|
5748
|
+
path: path5,
|
|
5710
5749
|
owner: stub?.owner
|
|
5711
5750
|
};
|
|
5712
5751
|
}
|
|
5713
5752
|
} else if (marker.kind === "route") {
|
|
5714
|
-
const stub = routeStubByPath.get(
|
|
5753
|
+
const stub = routeStubByPath.get(path5);
|
|
5715
5754
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5716
5755
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5717
5756
|
if (!ownerMatches || !pathMatches) {
|
|
5718
5757
|
return {
|
|
5719
5758
|
ok: false,
|
|
5720
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5721
|
-
path:
|
|
5759
|
+
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
|
|
5760
|
+
path: path5,
|
|
5722
5761
|
owner: stub?.owner
|
|
5723
5762
|
};
|
|
5724
5763
|
}
|
|
5725
5764
|
}
|
|
5726
5765
|
}
|
|
5727
5766
|
const deletes = [];
|
|
5728
|
-
for (const
|
|
5729
|
-
const existing = input.existingFiles[
|
|
5767
|
+
for (const path5 of input.existingGeneratedPaths) {
|
|
5768
|
+
const existing = input.existingFiles[path5];
|
|
5730
5769
|
if (existing == null) continue;
|
|
5731
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5770
|
+
if (!isProvenStaleDeletionCandidate(path5, existing)) {
|
|
5732
5771
|
continue;
|
|
5733
5772
|
}
|
|
5734
|
-
deletes.push(
|
|
5773
|
+
deletes.push(path5);
|
|
5735
5774
|
}
|
|
5736
5775
|
deletes.sort(compareNames2);
|
|
5737
|
-
const writes = [...desired.entries()].filter(([
|
|
5776
|
+
const writes = [...desired.entries()].filter(([path5, content]) => input.existingFiles[path5] !== content).map(([path5, content]) => ({ path: path5, content })).sort((a, b) => compareNames2(a.path, b.path));
|
|
5738
5777
|
return {
|
|
5739
5778
|
ok: true,
|
|
5740
5779
|
plan: {
|
|
@@ -5763,8 +5802,16 @@ async function checkoutParsesTypedStores(sandbox) {
|
|
|
5763
5802
|
try {
|
|
5764
5803
|
if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
|
|
5765
5804
|
const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
|
|
5766
|
-
|
|
5767
|
-
|
|
5805
|
+
return composeRangeParsesTypedStores(
|
|
5806
|
+
pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
|
|
5807
|
+
);
|
|
5808
|
+
} catch {
|
|
5809
|
+
return false;
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
function composeRangeParsesTypedStores(range) {
|
|
5813
|
+
if (!range) return false;
|
|
5814
|
+
try {
|
|
5768
5815
|
const lowest = import_semver.default.minVersion(range);
|
|
5769
5816
|
return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
|
|
5770
5817
|
} catch {
|
|
@@ -6209,11 +6256,11 @@ function countedCompositionContext(context) {
|
|
|
6209
6256
|
counts.sandboxExecs += 1;
|
|
6210
6257
|
return context.sandbox.exec(...args);
|
|
6211
6258
|
},
|
|
6212
|
-
readFile: (
|
|
6259
|
+
readFile: (path5) => {
|
|
6213
6260
|
counts.sandboxReads += 1;
|
|
6214
|
-
return context.sandbox.readFile(
|
|
6261
|
+
return context.sandbox.readFile(path5);
|
|
6215
6262
|
},
|
|
6216
|
-
fileExists: (
|
|
6263
|
+
fileExists: (path5) => context.sandbox.fileExists(path5),
|
|
6217
6264
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6218
6265
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6219
6266
|
}
|
|
@@ -6425,14 +6472,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6425
6472
|
}
|
|
6426
6473
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6427
6474
|
}
|
|
6428
|
-
async function writeSandboxFileAtomic(target,
|
|
6429
|
-
const dir = dirnamePosix(
|
|
6430
|
-
const tmp = `${
|
|
6475
|
+
async function writeSandboxFileAtomic(target, path5, content) {
|
|
6476
|
+
const dir = dirnamePosix(path5);
|
|
6477
|
+
const tmp = `${path5}.tmp.${target.runId}`;
|
|
6431
6478
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6432
6479
|
const cmd = [
|
|
6433
6480
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6434
6481
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6435
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6482
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path5)}`
|
|
6436
6483
|
].join(" && ");
|
|
6437
6484
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6438
6485
|
if (result.exitCode !== 0) {
|
|
@@ -6447,14 +6494,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
|
|
|
6447
6494
|
}
|
|
6448
6495
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6449
6496
|
throw new Error(
|
|
6450
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6497
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path5)}: ${redactSecrets(detail)}`
|
|
6451
6498
|
);
|
|
6452
6499
|
}
|
|
6453
6500
|
}
|
|
6454
6501
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6455
6502
|
const absent = /* @__PURE__ */ new Set();
|
|
6456
|
-
for (const
|
|
6457
|
-
let dir = dirnamePosix(
|
|
6503
|
+
for (const path5 of writePaths) {
|
|
6504
|
+
let dir = dirnamePosix(path5);
|
|
6458
6505
|
while (dir !== "." && dir !== "") {
|
|
6459
6506
|
if (absent.has(dir)) {
|
|
6460
6507
|
dir = dirnamePosix(dir);
|
|
@@ -6491,12 +6538,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6491
6538
|
);
|
|
6492
6539
|
}
|
|
6493
6540
|
}
|
|
6494
|
-
async function deleteSandboxFile(context,
|
|
6495
|
-
const result = await context.sandbox.exec(`rm -f ${shellQuote(
|
|
6541
|
+
async function deleteSandboxFile(context, path5) {
|
|
6542
|
+
const result = await context.sandbox.exec(`rm -f ${shellQuote(path5)}`, { raiseOnError: false });
|
|
6496
6543
|
if (result.exitCode !== 0) {
|
|
6497
6544
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6498
6545
|
throw new Error(
|
|
6499
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6546
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path5)}: ${redactSecrets(detail)}`
|
|
6500
6547
|
);
|
|
6501
6548
|
}
|
|
6502
6549
|
}
|
|
@@ -6563,37 +6610,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6563
6610
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6564
6611
|
appliedWrites.push({ path: write.path, previous });
|
|
6565
6612
|
}
|
|
6566
|
-
for (const
|
|
6567
|
-
const previous = existingFiles[
|
|
6613
|
+
for (const path5 of plan.deletes) {
|
|
6614
|
+
const previous = existingFiles[path5];
|
|
6568
6615
|
if (previous == null) continue;
|
|
6569
|
-
await deleteSandboxFile(context,
|
|
6570
|
-
appliedDeletes.push({ path:
|
|
6616
|
+
await deleteSandboxFile(context, path5);
|
|
6617
|
+
appliedDeletes.push({ path: path5, previous });
|
|
6571
6618
|
}
|
|
6572
6619
|
} catch (error) {
|
|
6573
6620
|
console.log(
|
|
6574
6621
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6575
6622
|
);
|
|
6576
|
-
for (const { path:
|
|
6623
|
+
for (const { path: path5, previous } of [...appliedDeletes].reverse()) {
|
|
6577
6624
|
try {
|
|
6578
|
-
await writeSandboxFileAtomic(context,
|
|
6625
|
+
await writeSandboxFileAtomic(context, path5, previous);
|
|
6579
6626
|
} catch (rollbackError) {
|
|
6580
6627
|
console.log(
|
|
6581
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(
|
|
6628
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path5)}: ${redactSecrets(
|
|
6582
6629
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6583
6630
|
)}`
|
|
6584
6631
|
);
|
|
6585
6632
|
}
|
|
6586
6633
|
}
|
|
6587
|
-
for (const { path:
|
|
6634
|
+
for (const { path: path5, previous } of [...appliedWrites].reverse()) {
|
|
6588
6635
|
try {
|
|
6589
6636
|
if (previous == null) {
|
|
6590
|
-
await deleteSandboxFile(context,
|
|
6637
|
+
await deleteSandboxFile(context, path5);
|
|
6591
6638
|
} else {
|
|
6592
|
-
await writeSandboxFileAtomic(context,
|
|
6639
|
+
await writeSandboxFileAtomic(context, path5, previous);
|
|
6593
6640
|
}
|
|
6594
6641
|
} catch (rollbackError) {
|
|
6595
6642
|
console.log(
|
|
6596
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(
|
|
6643
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path5)}: ${redactSecrets(
|
|
6597
6644
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6598
6645
|
)}`
|
|
6599
6646
|
);
|
|
@@ -6627,39 +6674,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6627
6674
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6628
6675
|
);
|
|
6629
6676
|
const presentPaths = [];
|
|
6630
|
-
for (const
|
|
6677
|
+
for (const path5 of candidates) {
|
|
6631
6678
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6632
|
-
|
|
6633
|
-
kinds.get(
|
|
6679
|
+
path5,
|
|
6680
|
+
kinds.get(path5) ?? "unreadable"
|
|
6634
6681
|
);
|
|
6635
|
-
if (presence === "file") presentPaths.push(
|
|
6682
|
+
if (presence === "file") presentPaths.push(path5);
|
|
6636
6683
|
}
|
|
6637
6684
|
let contents;
|
|
6638
6685
|
try {
|
|
6639
6686
|
contents = await readFilesMany(context, presentPaths);
|
|
6640
6687
|
} catch (error) {
|
|
6641
|
-
const
|
|
6688
|
+
const path5 = readFailurePath(error);
|
|
6642
6689
|
throw new Error(
|
|
6643
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6690
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path5)}: ${redactSecrets(
|
|
6644
6691
|
error instanceof Error ? error.message : String(error)
|
|
6645
6692
|
)}`
|
|
6646
6693
|
);
|
|
6647
6694
|
}
|
|
6648
6695
|
const owned = [];
|
|
6649
|
-
for (const
|
|
6650
|
-
const content = contents.get(
|
|
6696
|
+
for (const path5 of presentPaths) {
|
|
6697
|
+
const content = contents.get(path5);
|
|
6651
6698
|
const marker = parseCompositionArtifactMarker(content);
|
|
6652
|
-
if (parseLegacyApiAdapter(
|
|
6653
|
-
owned.push(
|
|
6699
|
+
if (parseLegacyApiAdapter(path5, content) || parseLegacyRouteAdapter(path5, content)) {
|
|
6700
|
+
owned.push(path5);
|
|
6654
6701
|
continue;
|
|
6655
6702
|
}
|
|
6656
6703
|
if (marker == null) continue;
|
|
6657
|
-
if (!markerCongruentWithPath(
|
|
6704
|
+
if (!markerCongruentWithPath(path5, marker)) {
|
|
6658
6705
|
throw new Error(
|
|
6659
|
-
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(
|
|
6706
|
+
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path5)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path5) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
|
|
6660
6707
|
);
|
|
6661
6708
|
}
|
|
6662
|
-
owned.push(
|
|
6709
|
+
owned.push(path5);
|
|
6663
6710
|
}
|
|
6664
6711
|
return owned;
|
|
6665
6712
|
}
|
|
@@ -6792,7 +6839,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6792
6839
|
{ raiseOnError: false }
|
|
6793
6840
|
);
|
|
6794
6841
|
if (result.exitCode !== 0) {
|
|
6795
|
-
for (const
|
|
6842
|
+
for (const path5 of chunk) kinds.set(path5, "unreadable");
|
|
6796
6843
|
continue;
|
|
6797
6844
|
}
|
|
6798
6845
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6803,31 +6850,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6803
6850
|
}
|
|
6804
6851
|
} else {
|
|
6805
6852
|
for (const line of lines) {
|
|
6806
|
-
const [rawKind,
|
|
6853
|
+
const [rawKind, path5] = line.split(" ", 2);
|
|
6807
6854
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6808
|
-
if (
|
|
6809
|
-
kinds.set(
|
|
6855
|
+
if (path5 && chunk.includes(path5) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
6856
|
+
kinds.set(path5, kind);
|
|
6810
6857
|
}
|
|
6811
6858
|
}
|
|
6812
6859
|
}
|
|
6813
|
-
for (const
|
|
6814
|
-
if (!kinds.has(
|
|
6860
|
+
for (const path5 of chunk) {
|
|
6861
|
+
if (!kinds.has(path5)) kinds.set(path5, "unreadable");
|
|
6815
6862
|
}
|
|
6816
6863
|
}
|
|
6817
6864
|
return kinds;
|
|
6818
6865
|
}
|
|
6819
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6820
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
6866
|
+
async function classifySandboxPathPresenceKind(context, path5, probeToken) {
|
|
6867
|
+
return (await classifySandboxPathPresenceKindMany(context, [path5], probeToken)).get(path5);
|
|
6821
6868
|
}
|
|
6822
6869
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6823
|
-
function assertRealLocaleJsonFilePresence(
|
|
6870
|
+
function assertRealLocaleJsonFilePresence(path5, presence) {
|
|
6824
6871
|
if (presence === "file") return;
|
|
6825
6872
|
if (presence === "absent") {
|
|
6826
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
6873
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path5}`);
|
|
6827
6874
|
}
|
|
6828
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6875
|
+
const presenceError = formatConventionalEntryPresenceError(path5, presence);
|
|
6829
6876
|
throw new Error(
|
|
6830
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6877
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real locale JSON file`}`
|
|
6831
6878
|
);
|
|
6832
6879
|
}
|
|
6833
6880
|
function splitTaggedLine(line) {
|
|
@@ -6902,11 +6949,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6902
6949
|
}
|
|
6903
6950
|
async function readFilesMany(context, paths) {
|
|
6904
6951
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6905
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
6952
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path5) => {
|
|
6906
6953
|
try {
|
|
6907
|
-
return { path:
|
|
6954
|
+
return { path: path5, value: await context.sandbox.readFile(path5) };
|
|
6908
6955
|
} catch (error) {
|
|
6909
|
-
return { path:
|
|
6956
|
+
return { path: path5, error };
|
|
6910
6957
|
}
|
|
6911
6958
|
});
|
|
6912
6959
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6927,55 +6974,60 @@ function readFailurePath(error) {
|
|
|
6927
6974
|
return typeof error === "object" && error !== null && "path" in error ? String(error.path) : "unknown";
|
|
6928
6975
|
}
|
|
6929
6976
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6977
|
+
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6930
6978
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6931
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6932
|
-
return classifySandboxPathPresenceKind(context,
|
|
6979
|
+
async function classifyCompositionArtifactPathKind(context, path5) {
|
|
6980
|
+
return classifySandboxPathPresenceKind(context, path5, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6933
6981
|
}
|
|
6934
|
-
function assertRealCompositionArtifactFilePresence(
|
|
6982
|
+
function assertRealCompositionArtifactFilePresence(path5, presence) {
|
|
6935
6983
|
if (presence === "file") return "file";
|
|
6936
6984
|
if (presence === "absent") return "absent";
|
|
6937
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6985
|
+
const presenceError = formatConventionalEntryPresenceError(path5, presence);
|
|
6938
6986
|
throw new Error(
|
|
6939
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6987
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real composition artifact file`}`
|
|
6940
6988
|
);
|
|
6941
6989
|
}
|
|
6942
|
-
async function readConventionalEntryIfRealFile(
|
|
6943
|
-
const presence = presences.get(
|
|
6944
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6990
|
+
async function readConventionalEntryIfRealFile(path5, kind, presences, files) {
|
|
6991
|
+
const presence = presences.get(path5) ?? "unreadable";
|
|
6992
|
+
const presenceError = formatConventionalEntryPresenceError(path5, presence);
|
|
6945
6993
|
if (presenceError) {
|
|
6946
6994
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6947
6995
|
}
|
|
6948
6996
|
if (presence === "absent") {
|
|
6949
6997
|
return null;
|
|
6950
6998
|
}
|
|
6951
|
-
const source = files.get(
|
|
6999
|
+
const source = files.get(path5);
|
|
6952
7000
|
switch (kind) {
|
|
6953
7001
|
case "contributions":
|
|
6954
|
-
assertContributionsExport(source,
|
|
6955
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7002
|
+
assertContributionsExport(source, path5);
|
|
7003
|
+
assertClientSafeConventionalEntryImports(source, path5, "contributions");
|
|
6956
7004
|
break;
|
|
6957
7005
|
case "slotCatalogs":
|
|
6958
|
-
assertSlotCatalogsExport(source,
|
|
6959
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7006
|
+
assertSlotCatalogsExport(source, path5);
|
|
7007
|
+
assertClientSafeConventionalEntryImports(source, path5, "slotCatalogs");
|
|
6960
7008
|
break;
|
|
6961
7009
|
case "initializeEnhancement":
|
|
6962
|
-
assertInitializeEnhancementExport(source,
|
|
7010
|
+
assertInitializeEnhancementExport(source, path5);
|
|
6963
7011
|
break;
|
|
6964
7012
|
}
|
|
6965
7013
|
return source;
|
|
6966
7014
|
}
|
|
6967
|
-
|
|
6968
|
-
const
|
|
7015
|
+
function hasRealServerRegistrations(modulePath, presences) {
|
|
7016
|
+
const path5 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
7017
|
+
return assertRealCompositionArtifactFilePresence(path5, presences.get(path5) ?? "unreadable") === "file";
|
|
7018
|
+
}
|
|
7019
|
+
async function readJsonObjectFile(path5, files) {
|
|
7020
|
+
const raw = files.get(path5);
|
|
6969
7021
|
let parsed;
|
|
6970
7022
|
try {
|
|
6971
7023
|
parsed = JSON.parse(raw);
|
|
6972
7024
|
} catch (error) {
|
|
6973
7025
|
throw new Error(
|
|
6974
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
7026
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path5}: ${error instanceof Error ? error.message : String(error)}`
|
|
6975
7027
|
);
|
|
6976
7028
|
}
|
|
6977
7029
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
6978
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
7030
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path5} root must be an object`);
|
|
6979
7031
|
}
|
|
6980
7032
|
return parsed;
|
|
6981
7033
|
}
|
|
@@ -6987,6 +7039,9 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
6987
7039
|
const overridesDir = `${APP_PACKAGE_DIR}/src/lib/i18n/overrides`;
|
|
6988
7040
|
const localeDirs = [substrateDir, overridesDir];
|
|
6989
7041
|
const conventionalPaths = [];
|
|
7042
|
+
const serverRegistrationPaths = registry.modules.map(
|
|
7043
|
+
(mod) => `${MODULES_SANDBOX_DIR}/${mod.name}/${SERVER_REGISTRATIONS_FILE}`
|
|
7044
|
+
);
|
|
6990
7045
|
for (const mod of registry.modules) {
|
|
6991
7046
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
6992
7047
|
localeDirs.push(`${modulePath}/i18n`);
|
|
@@ -7015,17 +7070,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7015
7070
|
const [conventionalPresences, localePresences] = await Promise.all([
|
|
7016
7071
|
classifySandboxPathPresenceKindMany(
|
|
7017
7072
|
context,
|
|
7018
|
-
conventionalPaths,
|
|
7073
|
+
[...conventionalPaths, ...serverRegistrationPaths],
|
|
7019
7074
|
CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN
|
|
7020
7075
|
),
|
|
7021
7076
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7022
7077
|
]);
|
|
7023
7078
|
const presentConventionalPaths = [];
|
|
7024
|
-
for (const
|
|
7025
|
-
const presence = conventionalPresences.get(
|
|
7026
|
-
if (presence === "file") presentConventionalPaths.push(
|
|
7079
|
+
for (const path5 of conventionalPaths) {
|
|
7080
|
+
const presence = conventionalPresences.get(path5) ?? "unreadable";
|
|
7081
|
+
if (presence === "file") presentConventionalPaths.push(path5);
|
|
7027
7082
|
}
|
|
7028
|
-
const presentLocalePaths = localePaths.filter((
|
|
7083
|
+
const presentLocalePaths = localePaths.filter((path5) => localePresences.get(path5) === "file");
|
|
7029
7084
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7030
7085
|
const substrateLocales = [];
|
|
7031
7086
|
for (const locale of substrateNames) {
|
|
@@ -7077,6 +7132,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7077
7132
|
);
|
|
7078
7133
|
}
|
|
7079
7134
|
}
|
|
7135
|
+
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7080
7136
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7081
7137
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7082
7138
|
throw new Error(
|
|
@@ -7116,13 +7172,14 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7116
7172
|
moduleName: mod.name,
|
|
7117
7173
|
hasRootContributions,
|
|
7118
7174
|
hasRootSlots,
|
|
7175
|
+
hasServerRegistrations,
|
|
7119
7176
|
rootLocales,
|
|
7120
7177
|
enhancements
|
|
7121
7178
|
});
|
|
7122
7179
|
if (rootLocales.length > 0) {
|
|
7123
7180
|
for (const locale of rootLocales) {
|
|
7124
|
-
const
|
|
7125
|
-
assertRealLocaleJsonFilePresence(
|
|
7181
|
+
const path5 = `${modulePath}/i18n/${locale}.json`;
|
|
7182
|
+
assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
|
|
7126
7183
|
}
|
|
7127
7184
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7128
7185
|
const localeMessages = { en: enMessages };
|
|
@@ -7146,8 +7203,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7146
7203
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7147
7204
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7148
7205
|
for (const locale of enh.locales) {
|
|
7149
|
-
const
|
|
7150
|
-
assertRealLocaleJsonFilePresence(
|
|
7206
|
+
const path5 = `${peerDir}/i18n/${locale}.json`;
|
|
7207
|
+
assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
|
|
7151
7208
|
}
|
|
7152
7209
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7153
7210
|
const localeMessages = { en: enMessages };
|
|
@@ -7319,6 +7376,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7319
7376
|
MODULE_DATASTORES_GEN_PATH,
|
|
7320
7377
|
MODULE_I18N_GEN_PATH,
|
|
7321
7378
|
MODULE_INIT_SERVER_GEN_PATH,
|
|
7379
|
+
MODULE_SERVER_GEN_PATH,
|
|
7322
7380
|
...existingGeneratedPaths,
|
|
7323
7381
|
...Object.keys(existingAppPages),
|
|
7324
7382
|
...Object.keys(existingAppRoutes)
|
|
@@ -7347,16 +7405,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7347
7405
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7348
7406
|
const existingFiles = {};
|
|
7349
7407
|
const remainingCandidatePaths = [];
|
|
7350
|
-
for (const
|
|
7351
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7352
|
-
existingFiles[
|
|
7408
|
+
for (const path5 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
|
|
7409
|
+
if (Object.prototype.hasOwnProperty.call(existingAppPages, path5)) {
|
|
7410
|
+
existingFiles[path5] = existingAppPages[path5];
|
|
7353
7411
|
continue;
|
|
7354
7412
|
}
|
|
7355
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7356
|
-
existingFiles[
|
|
7413
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path5)) {
|
|
7414
|
+
existingFiles[path5] = existingAppRoutes[path5];
|
|
7357
7415
|
continue;
|
|
7358
7416
|
}
|
|
7359
|
-
remainingCandidatePaths.push(
|
|
7417
|
+
remainingCandidatePaths.push(path5);
|
|
7360
7418
|
}
|
|
7361
7419
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7362
7420
|
context,
|
|
@@ -7364,12 +7422,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7364
7422
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7365
7423
|
);
|
|
7366
7424
|
const presentCandidatePaths = [];
|
|
7367
|
-
for (const
|
|
7425
|
+
for (const path5 of remainingCandidatePaths) {
|
|
7368
7426
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7369
|
-
|
|
7370
|
-
candidateKinds.get(
|
|
7427
|
+
path5,
|
|
7428
|
+
candidateKinds.get(path5) ?? "unreadable"
|
|
7371
7429
|
);
|
|
7372
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7430
|
+
if (presence === "file") presentCandidatePaths.push(path5);
|
|
7373
7431
|
}
|
|
7374
7432
|
try {
|
|
7375
7433
|
Object.assign(
|
|
@@ -7377,9 +7435,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7377
7435
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7378
7436
|
);
|
|
7379
7437
|
} catch (error) {
|
|
7380
|
-
const
|
|
7438
|
+
const path5 = readFailurePath(error);
|
|
7381
7439
|
throw new Error(
|
|
7382
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7440
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path5)} during preflight: ${redactSecrets(
|
|
7383
7441
|
error instanceof Error ? error.message : String(error)
|
|
7384
7442
|
)}`
|
|
7385
7443
|
);
|
|
@@ -7448,14 +7506,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7448
7506
|
var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
7449
7507
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
7450
7508
|
|
|
7509
|
+
// src/module-boundaries.ts
|
|
7510
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
7511
|
+
var import_node_path2 = __toESM(require("path"));
|
|
7512
|
+
var import_typescript5 = __toESM(require("typescript"));
|
|
7513
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
7514
|
+
var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
7515
|
+
var DATA_FILE = /\.(?:sql|json)$/;
|
|
7516
|
+
function dataPathsIn(fileName, source) {
|
|
7517
|
+
if (!/\.(?:sql|json)["'`]/.test(source)) return [];
|
|
7518
|
+
const paths = [];
|
|
7519
|
+
const visit = (node) => {
|
|
7520
|
+
if ((import_typescript5.default.isStringLiteralLike(node) || import_typescript5.default.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
|
|
7521
|
+
paths.push(node.text);
|
|
7522
|
+
}
|
|
7523
|
+
import_typescript5.default.forEachChild(node, visit);
|
|
7524
|
+
};
|
|
7525
|
+
visit(import_typescript5.default.createSourceFile(fileName, source, import_typescript5.default.ScriptTarget.Latest, false));
|
|
7526
|
+
return paths;
|
|
7527
|
+
}
|
|
7528
|
+
function scanModule(modulesDir, module2) {
|
|
7529
|
+
const scanned = /* @__PURE__ */ new Map();
|
|
7530
|
+
const moduleDir = import_node_path2.default.join(modulesDir, module2);
|
|
7531
|
+
if (!import_node_fs2.default.existsSync(moduleDir)) return scanned;
|
|
7532
|
+
const files = import_node_fs2.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
|
|
7533
|
+
for (const relative of files) {
|
|
7534
|
+
const absolute = import_node_path2.default.join(moduleDir, relative);
|
|
7535
|
+
if (!import_node_fs2.default.statSync(absolute).isFile()) continue;
|
|
7536
|
+
const source = import_node_fs2.default.readFileSync(absolute, "utf8");
|
|
7537
|
+
const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
|
|
7538
|
+
const dataPaths = dataPathsIn(absolute, source);
|
|
7539
|
+
const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
|
|
7540
|
+
if (spec.startsWith("@/modules/"))
|
|
7541
|
+
return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
|
|
7542
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
7543
|
+
return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
|
|
7544
|
+
}
|
|
7545
|
+
return [];
|
|
7546
|
+
});
|
|
7547
|
+
scanned.set(absolute, {
|
|
7548
|
+
module: module2,
|
|
7549
|
+
relative: relative.split(import_node_path2.default.sep).join("/"),
|
|
7550
|
+
targets,
|
|
7551
|
+
dataPaths
|
|
7552
|
+
});
|
|
7553
|
+
}
|
|
7554
|
+
return scanned;
|
|
7555
|
+
}
|
|
7556
|
+
function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
|
|
7557
|
+
const files = /* @__PURE__ */ new Map();
|
|
7558
|
+
for (const [module2, peers] of Object.entries(peersByModule)) {
|
|
7559
|
+
if (peers.length === 0) continue;
|
|
7560
|
+
for (const [absolute, file] of scanModule(modulesDir, module2)) files.set(absolute, file);
|
|
7561
|
+
}
|
|
7562
|
+
const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
|
|
7563
|
+
(q) => file.relative.startsWith(`enhancements/${q}/`)
|
|
7564
|
+
);
|
|
7565
|
+
const goneWith = (target, peer) => {
|
|
7566
|
+
const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
|
|
7567
|
+
return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
|
|
7568
|
+
};
|
|
7569
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
7570
|
+
const queue = [];
|
|
7571
|
+
const taint = (absolute, peer) => {
|
|
7572
|
+
const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
|
|
7573
|
+
if (peers.has(peer)) return;
|
|
7574
|
+
peers.add(peer);
|
|
7575
|
+
tainted.set(absolute, peers);
|
|
7576
|
+
queue.push([absolute, peer]);
|
|
7577
|
+
};
|
|
7578
|
+
const importers = /* @__PURE__ */ new Map();
|
|
7579
|
+
for (const [absolute, file] of files) {
|
|
7580
|
+
for (const target of file.targets) {
|
|
7581
|
+
const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
|
|
7582
|
+
if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
|
|
7583
|
+
}
|
|
7584
|
+
for (const peer of peersByModule[file.module] ?? []) {
|
|
7585
|
+
if (exempt(file, peer)) continue;
|
|
7586
|
+
if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
|
|
7587
|
+
(p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
|
|
7588
|
+
)) {
|
|
7589
|
+
taint(absolute, peer);
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
7594
|
+
const [target, peer] = next;
|
|
7595
|
+
for (const importer of importers.get(target) ?? []) {
|
|
7596
|
+
const file = files.get(importer);
|
|
7597
|
+
if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
|
|
7598
|
+
taint(importer, peer);
|
|
7599
|
+
}
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
const violations = [];
|
|
7603
|
+
for (const [absolute, peers] of tainted) {
|
|
7604
|
+
const file = files.get(absolute);
|
|
7605
|
+
for (const peer of peers) {
|
|
7606
|
+
violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7609
|
+
return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
|
|
7610
|
+
}
|
|
7611
|
+
function findOptionalPeerViolations(modulesDir) {
|
|
7612
|
+
if (!import_node_fs2.default.existsSync(modulesDir)) return [];
|
|
7613
|
+
const manifests = {};
|
|
7614
|
+
for (const name of import_node_fs2.default.readdirSync(modulesDir)) {
|
|
7615
|
+
const manifestPath = import_node_path2.default.join(modulesDir, name, "module.json");
|
|
7616
|
+
if (!import_node_fs2.default.existsSync(manifestPath)) continue;
|
|
7617
|
+
manifests[name] = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf8"));
|
|
7618
|
+
}
|
|
7619
|
+
const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
|
|
7620
|
+
const peersByModule = {};
|
|
7621
|
+
for (const [name, manifest] of Object.entries(manifests)) {
|
|
7622
|
+
const uses = new Set(manifest.uses ?? []);
|
|
7623
|
+
peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
|
|
7624
|
+
}
|
|
7625
|
+
return findModuleBoundaryViolations(modulesDir, peersByModule);
|
|
7626
|
+
}
|
|
7627
|
+
function formatBoundaryViolations(modulesDirLabel, violations) {
|
|
7628
|
+
const files = new Set(violations.map((v) => v.file)).size;
|
|
7629
|
+
return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
|
|
7630
|
+
\`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).
|
|
7631
|
+
` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
|
|
7632
|
+
}
|
|
7633
|
+
|
|
7451
7634
|
// src/index.ts
|
|
7452
7635
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7453
7636
|
function readComposeInputs(cwd) {
|
|
7454
|
-
const file =
|
|
7455
|
-
if (!
|
|
7637
|
+
const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7638
|
+
if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7456
7639
|
let raw;
|
|
7457
7640
|
try {
|
|
7458
|
-
raw = JSON.parse(
|
|
7641
|
+
raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
|
|
7459
7642
|
} catch (error) {
|
|
7460
7643
|
throw new Error(
|
|
7461
7644
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7497,18 +7680,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7497
7680
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7498
7681
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7499
7682
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7500
|
-
const file =
|
|
7501
|
-
const existing =
|
|
7683
|
+
const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7684
|
+
const existing = import_node_fs3.default.existsSync(file) ? import_node_fs3.default.readFileSync(file, "utf8") : null;
|
|
7502
7685
|
if (existing !== content) {
|
|
7503
|
-
|
|
7504
|
-
|
|
7686
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
|
|
7687
|
+
import_node_fs3.default.writeFileSync(file, content);
|
|
7505
7688
|
}
|
|
7506
7689
|
return entries.length;
|
|
7507
7690
|
}
|
|
7508
7691
|
function assertStubGitignoreOwned(cwd) {
|
|
7509
|
-
const file =
|
|
7510
|
-
if (!
|
|
7511
|
-
const firstLine =
|
|
7692
|
+
const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7693
|
+
if (!import_node_fs3.default.existsSync(file)) return;
|
|
7694
|
+
const firstLine = import_node_fs3.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
|
|
7512
7695
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7513
7696
|
throw new Error(
|
|
7514
7697
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7516,8 +7699,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7516
7699
|
}
|
|
7517
7700
|
}
|
|
7518
7701
|
function assertAppRoot(cwd) {
|
|
7519
|
-
const appPath =
|
|
7520
|
-
if (!
|
|
7702
|
+
const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
|
|
7703
|
+
if (!import_node_fs3.default.existsSync(appPath)) {
|
|
7521
7704
|
throw new Error(
|
|
7522
7705
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7523
7706
|
);
|
|
@@ -7563,9 +7746,9 @@ ${porcelain.trim()}`
|
|
|
7563
7746
|
}
|
|
7564
7747
|
var APP_SRC_DIR = "apps/web/src";
|
|
7565
7748
|
function walkSources(dir, skip, out) {
|
|
7566
|
-
if (!
|
|
7567
|
-
for (const entry of
|
|
7568
|
-
const full =
|
|
7749
|
+
if (!import_node_fs3.default.existsSync(dir)) return;
|
|
7750
|
+
for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
|
|
7751
|
+
const full = import_node_path3.default.join(dir, entry.name);
|
|
7569
7752
|
if (full === skip) continue;
|
|
7570
7753
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7571
7754
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7574,13 +7757,13 @@ function walkSources(dir, skip, out) {
|
|
|
7574
7757
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7575
7758
|
if (excluded.length === 0) return;
|
|
7576
7759
|
const files = [];
|
|
7577
|
-
walkSources(
|
|
7760
|
+
walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
|
|
7578
7761
|
const offenders = [];
|
|
7579
7762
|
for (const file of files) {
|
|
7580
|
-
const source =
|
|
7763
|
+
const source = import_node_fs3.default.readFileSync(file, "utf8");
|
|
7581
7764
|
for (const name of excluded) {
|
|
7582
7765
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7583
|
-
offenders.push(`${
|
|
7766
|
+
offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
|
|
7584
7767
|
}
|
|
7585
7768
|
}
|
|
7586
7769
|
}
|
|
@@ -7593,8 +7776,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
|
|
|
7593
7776
|
}
|
|
7594
7777
|
}
|
|
7595
7778
|
function removeDir(absolute) {
|
|
7596
|
-
if (!
|
|
7597
|
-
|
|
7779
|
+
if (!import_node_fs3.default.existsSync(absolute)) return false;
|
|
7780
|
+
import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
|
|
7598
7781
|
return true;
|
|
7599
7782
|
}
|
|
7600
7783
|
async function compose(options) {
|
|
@@ -7621,6 +7804,16 @@ async function compose(options) {
|
|
|
7621
7804
|
dryRun: true
|
|
7622
7805
|
});
|
|
7623
7806
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
7807
|
+
const violations = findModuleBoundaryViolations(
|
|
7808
|
+
import_node_path3.default.join(options.cwd, MODULES_DIR2),
|
|
7809
|
+
Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
|
|
7810
|
+
{ pruning: true }
|
|
7811
|
+
);
|
|
7812
|
+
if (violations.length > 0) {
|
|
7813
|
+
throw new Error(
|
|
7814
|
+
`[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
|
|
7815
|
+
);
|
|
7816
|
+
}
|
|
7624
7817
|
}
|
|
7625
7818
|
const result = await reconcileCompositionArtifacts(context, {
|
|
7626
7819
|
enabledModulesRaw: options.enabled,
|
|
@@ -7632,11 +7825,11 @@ async function compose(options) {
|
|
|
7632
7825
|
let pruned = 0;
|
|
7633
7826
|
if (options.prune) {
|
|
7634
7827
|
for (const name of result.excludedModules) {
|
|
7635
|
-
if (removeDir(
|
|
7828
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7636
7829
|
}
|
|
7637
7830
|
for (const owner of result.composedModules) {
|
|
7638
7831
|
for (const peer of result.excludedModules) {
|
|
7639
|
-
if (removeDir(
|
|
7832
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7640
7833
|
}
|
|
7641
7834
|
}
|
|
7642
7835
|
}
|
|
@@ -7652,15 +7845,28 @@ async function compose(options) {
|
|
|
7652
7845
|
|
|
7653
7846
|
// src/cli.ts
|
|
7654
7847
|
async function main() {
|
|
7655
|
-
assertSupportedTypescript(
|
|
7848
|
+
assertSupportedTypescript(import_typescript6.default.versionMajorMinor);
|
|
7656
7849
|
const { values } = (0, import_node_util.parseArgs)({
|
|
7657
7850
|
options: {
|
|
7658
7851
|
enabled: { type: "string" },
|
|
7659
7852
|
prune: { type: "boolean", default: false },
|
|
7660
|
-
cwd: { type: "string" }
|
|
7853
|
+
cwd: { type: "string" },
|
|
7854
|
+
"check-boundaries": { type: "boolean", default: false }
|
|
7661
7855
|
},
|
|
7662
7856
|
strict: true
|
|
7663
7857
|
});
|
|
7858
|
+
if (values["check-boundaries"]) {
|
|
7859
|
+
const violations = findOptionalPeerViolations(
|
|
7860
|
+
import_node_path4.default.join(values.cwd ?? process.cwd(), MODULES_SANDBOX_DIR)
|
|
7861
|
+
);
|
|
7862
|
+
if (violations.length > 0) {
|
|
7863
|
+
throw new Error(
|
|
7864
|
+
`[compose] --check-boundaries: ${formatBoundaryViolations(MODULES_SANDBOX_DIR, violations)}`
|
|
7865
|
+
);
|
|
7866
|
+
}
|
|
7867
|
+
console.log("[compose] --check-boundaries: no Module file reaches a peer outside its `uses`");
|
|
7868
|
+
return;
|
|
7869
|
+
}
|
|
7664
7870
|
const enabled = values.enabled ?? process.env[ENABLED_MODULES_ENV];
|
|
7665
7871
|
if (values.prune && !enabled) {
|
|
7666
7872
|
throw new Error(
|