@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.mjs
CHANGED
|
@@ -2020,8 +2020,8 @@ var require_semver2 = __commonJS({
|
|
|
2020
2020
|
|
|
2021
2021
|
// src/index.ts
|
|
2022
2022
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2023
|
-
import
|
|
2024
|
-
import
|
|
2023
|
+
import fs3 from "fs";
|
|
2024
|
+
import path3 from "path";
|
|
2025
2025
|
|
|
2026
2026
|
// ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
|
|
2027
2027
|
import { spawnSync } from "child_process";
|
|
@@ -2126,23 +2126,23 @@ function isValidModuleName(value) {
|
|
|
2126
2126
|
var projectSlugSchema = z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
2127
2127
|
var moduleKindSchema = z.enum(["feature", "library", "contract"]);
|
|
2128
2128
|
var endpointMethodSchema = z.enum(ENDPOINT_METHODS);
|
|
2129
|
-
function endpointPathValidationError(
|
|
2130
|
-
if (!
|
|
2129
|
+
function endpointPathValidationError(path4) {
|
|
2130
|
+
if (!path4.startsWith("/api/")) {
|
|
2131
2131
|
return 'must begin with "/api/"';
|
|
2132
2132
|
}
|
|
2133
|
-
if (
|
|
2133
|
+
if (path4.endsWith("/")) {
|
|
2134
2134
|
return "must not end with a trailing slash";
|
|
2135
2135
|
}
|
|
2136
|
-
if (
|
|
2136
|
+
if (path4.includes("?") || path4.includes("#")) {
|
|
2137
2137
|
return "must not include query or hash";
|
|
2138
2138
|
}
|
|
2139
|
-
if (
|
|
2139
|
+
if (path4.includes("\\")) {
|
|
2140
2140
|
return "must not include backslashes";
|
|
2141
2141
|
}
|
|
2142
|
-
if (
|
|
2142
|
+
if (path4.includes("%")) {
|
|
2143
2143
|
return "must not include percent-encoded segments";
|
|
2144
2144
|
}
|
|
2145
|
-
const parts =
|
|
2145
|
+
const parts = path4.split("/");
|
|
2146
2146
|
if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
|
|
2147
2147
|
return 'must begin with "/api/"';
|
|
2148
2148
|
}
|
|
@@ -2192,12 +2192,12 @@ function endpointPathValidationError(path3) {
|
|
|
2192
2192
|
}
|
|
2193
2193
|
return null;
|
|
2194
2194
|
}
|
|
2195
|
-
function normalizeEndpointPathForCollision(
|
|
2196
|
-
const error = endpointPathValidationError(
|
|
2195
|
+
function normalizeEndpointPathForCollision(path4) {
|
|
2196
|
+
const error = endpointPathValidationError(path4);
|
|
2197
2197
|
if (error != null) {
|
|
2198
2198
|
throw new Error(`invalid endpoint path: ${error}`);
|
|
2199
2199
|
}
|
|
2200
|
-
return
|
|
2200
|
+
return path4.split("/").map((segment, index) => {
|
|
2201
2201
|
if (index < 2) {
|
|
2202
2202
|
return segment;
|
|
2203
2203
|
}
|
|
@@ -2210,8 +2210,8 @@ function normalizeEndpointPathForCollision(path3) {
|
|
|
2210
2210
|
return segment;
|
|
2211
2211
|
}).join("/");
|
|
2212
2212
|
}
|
|
2213
|
-
var endpointPathSchema = z.string().superRefine((
|
|
2214
|
-
const error = endpointPathValidationError(
|
|
2213
|
+
var endpointPathSchema = z.string().superRefine((path4, ctx) => {
|
|
2214
|
+
const error = endpointPathValidationError(path4);
|
|
2215
2215
|
if (error != null) {
|
|
2216
2216
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
|
|
2217
2217
|
}
|
|
@@ -2458,16 +2458,16 @@ var moduleManifestSchema = z.object({
|
|
|
2458
2458
|
}
|
|
2459
2459
|
}
|
|
2460
2460
|
const seenWorkflows = /* @__PURE__ */ new Map();
|
|
2461
|
-
const noteWorkflow = (name,
|
|
2461
|
+
const noteWorkflow = (name, path4) => {
|
|
2462
2462
|
const existing = seenWorkflows.get(name);
|
|
2463
2463
|
if (existing != null) {
|
|
2464
2464
|
ctx.addIssue({
|
|
2465
2465
|
code: z.ZodIssueCode.custom,
|
|
2466
2466
|
message: `duplicate workflow name "${name}" (also at ${existing})`,
|
|
2467
|
-
path:
|
|
2467
|
+
path: path4
|
|
2468
2468
|
});
|
|
2469
2469
|
} else {
|
|
2470
|
-
seenWorkflows.set(name,
|
|
2470
|
+
seenWorkflows.set(name, path4.join("."));
|
|
2471
2471
|
}
|
|
2472
2472
|
};
|
|
2473
2473
|
for (let i = 0; i < manifest.workflows.length; i += 1) {
|
|
@@ -2479,7 +2479,7 @@ var moduleManifestSchema = z.object({
|
|
|
2479
2479
|
}
|
|
2480
2480
|
}
|
|
2481
2481
|
const seenEndpointKeys = /* @__PURE__ */ new Map();
|
|
2482
|
-
const noteEndpoint = (claim,
|
|
2482
|
+
const noteEndpoint = (claim, path4) => {
|
|
2483
2483
|
let key;
|
|
2484
2484
|
try {
|
|
2485
2485
|
key = normalizeEndpointPathForCollision(claim.path);
|
|
@@ -2491,10 +2491,10 @@ var moduleManifestSchema = z.object({
|
|
|
2491
2491
|
ctx.addIssue({
|
|
2492
2492
|
code: z.ZodIssueCode.custom,
|
|
2493
2493
|
message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
|
|
2494
|
-
path:
|
|
2494
|
+
path: path4
|
|
2495
2495
|
});
|
|
2496
2496
|
} else {
|
|
2497
|
-
seenEndpointKeys.set(key,
|
|
2497
|
+
seenEndpointKeys.set(key, path4.join("."));
|
|
2498
2498
|
}
|
|
2499
2499
|
};
|
|
2500
2500
|
for (let i = 0; i < manifest.endpoints.length; i += 1) {
|
|
@@ -2595,8 +2595,8 @@ var moduleManifestSchema = z.object({
|
|
|
2595
2595
|
}).transform(({ version: _legacyVersion, ...manifest }) => manifest);
|
|
2596
2596
|
function formatZodErrors(error) {
|
|
2597
2597
|
return error.issues.map((issue) => {
|
|
2598
|
-
const
|
|
2599
|
-
return `${
|
|
2598
|
+
const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2599
|
+
return `${path4}: ${issue.message}`;
|
|
2600
2600
|
});
|
|
2601
2601
|
}
|
|
2602
2602
|
function parseModuleManifest(raw) {
|
|
@@ -2996,16 +2996,16 @@ var collectedE2eFactsSchema = z3.object({
|
|
|
2996
2996
|
function validateRouteExportEvidenceCongruence(input) {
|
|
2997
2997
|
const entrySet = new Set(input.routeEntries);
|
|
2998
2998
|
const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
|
|
2999
|
-
for (const
|
|
3000
|
-
if (!entrySet.has(
|
|
3001
|
-
input.onIssue(`routePageExports path "${
|
|
2999
|
+
for (const path4 of exportPaths) {
|
|
3000
|
+
if (!entrySet.has(path4)) {
|
|
3001
|
+
input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
|
|
3002
3002
|
}
|
|
3003
3003
|
}
|
|
3004
3004
|
if (input.routeCollectionErrors.length === 0) {
|
|
3005
3005
|
const exportSet = new Set(exportPaths);
|
|
3006
|
-
for (const
|
|
3007
|
-
if (!exportSet.has(
|
|
3008
|
-
input.onIssue(`routeEntries path "${
|
|
3006
|
+
for (const path4 of input.routeEntries) {
|
|
3007
|
+
if (!exportSet.has(path4)) {
|
|
3008
|
+
input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
|
|
3009
3009
|
}
|
|
3010
3010
|
}
|
|
3011
3011
|
}
|
|
@@ -4119,6 +4119,7 @@ var MODULE_CONTRIBUTIONS_GEN_PATH = `${APP_WEB_PREFIX}src/module-contributions.g
|
|
|
4119
4119
|
var MODULE_DATASTORES_GEN_PATH = `${APP_WEB_PREFIX}src/module-datastores.gen.ts`;
|
|
4120
4120
|
var MODULE_I18N_GEN_PATH = `${APP_WEB_PREFIX}src/module-i18n.gen.ts`;
|
|
4121
4121
|
var MODULE_INIT_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-init.server.gen.ts`;
|
|
4122
|
+
var MODULE_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-server.gen.ts`;
|
|
4122
4123
|
var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
|
|
4123
4124
|
var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
|
|
4124
4125
|
|
|
@@ -4135,7 +4136,8 @@ var COMPOSITION_RUNTIME_GEN_PATHS = [
|
|
|
4135
4136
|
MODULE_CONTRIBUTIONS_GEN_PATH,
|
|
4136
4137
|
MODULE_I18N_GEN_PATH,
|
|
4137
4138
|
MODULE_INIT_SERVER_GEN_PATH,
|
|
4138
|
-
MODULE_DATASTORES_GEN_PATH
|
|
4139
|
+
MODULE_DATASTORES_GEN_PATH,
|
|
4140
|
+
MODULE_SERVER_GEN_PATH
|
|
4139
4141
|
];
|
|
4140
4142
|
function compareNames(a, b) {
|
|
4141
4143
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
@@ -4156,6 +4158,9 @@ function formatI18nMarker() {
|
|
|
4156
4158
|
function formatInitMarker() {
|
|
4157
4159
|
return `// ${MODULE_STUB_MARKER} scope=init`;
|
|
4158
4160
|
}
|
|
4161
|
+
function formatServerMarker() {
|
|
4162
|
+
return `// ${MODULE_STUB_MARKER} scope=server`;
|
|
4163
|
+
}
|
|
4159
4164
|
function formatDatastoresMarker() {
|
|
4160
4165
|
return `// ${MODULE_STUB_MARKER} scope=datastores`;
|
|
4161
4166
|
}
|
|
@@ -4251,13 +4256,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4251
4256
|
function createMessageTree() {
|
|
4252
4257
|
return /* @__PURE__ */ Object.create(null);
|
|
4253
4258
|
}
|
|
4254
|
-
function assertMessageTree(value,
|
|
4259
|
+
function assertMessageTree(value, path4) {
|
|
4255
4260
|
if (typeof value === "string") return;
|
|
4256
4261
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4257
|
-
throw new Error(`Invalid message at ${
|
|
4262
|
+
throw new Error(`Invalid message at ${path4}: expected string or nested object`);
|
|
4258
4263
|
}
|
|
4259
4264
|
for (const [key, child] of Object.entries(value)) {
|
|
4260
|
-
assertMessageTree(child, `${
|
|
4265
|
+
assertMessageTree(child, `${path4}.${key}`);
|
|
4261
4266
|
}
|
|
4262
4267
|
}
|
|
4263
4268
|
function cloneTree(value) {
|
|
@@ -4267,17 +4272,17 @@ function cloneTree(value) {
|
|
|
4267
4272
|
}
|
|
4268
4273
|
return clone;
|
|
4269
4274
|
}
|
|
4270
|
-
function collectLeafPaths(value,
|
|
4275
|
+
function collectLeafPaths(value, path4, out) {
|
|
4271
4276
|
if (typeof value === "string") {
|
|
4272
|
-
out.push(
|
|
4277
|
+
out.push(path4);
|
|
4273
4278
|
return;
|
|
4274
4279
|
}
|
|
4275
|
-
assertMessageTree(value,
|
|
4276
|
-
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${
|
|
4280
|
+
assertMessageTree(value, path4);
|
|
4281
|
+
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
|
|
4277
4282
|
}
|
|
4278
|
-
function overrideInto(target, source, owner,
|
|
4283
|
+
function overrideInto(target, source, owner, path4, missing) {
|
|
4279
4284
|
for (const [key, value] of Object.entries(source)) {
|
|
4280
|
-
const nextPath =
|
|
4285
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4281
4286
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4282
4287
|
collectLeafPaths(value, nextPath, missing);
|
|
4283
4288
|
continue;
|
|
@@ -4304,9 +4309,9 @@ function overrideInto(target, source, owner, path3, missing) {
|
|
|
4304
4309
|
function composeLocaleMessagesForPlan(layers) {
|
|
4305
4310
|
const result = createMessageTree();
|
|
4306
4311
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4307
|
-
function mergeInto(target, source, owner,
|
|
4312
|
+
function mergeInto(target, source, owner, path4) {
|
|
4308
4313
|
for (const [key, value] of Object.entries(source)) {
|
|
4309
|
-
const nextPath =
|
|
4314
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4310
4315
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4311
4316
|
const existing = hasExisting ? target[key] : void 0;
|
|
4312
4317
|
if (typeof value === "string") {
|
|
@@ -4799,6 +4804,42 @@ function emitModuleInitServerGenTs(input) {
|
|
|
4799
4804
|
""
|
|
4800
4805
|
].join("\n");
|
|
4801
4806
|
}
|
|
4807
|
+
function emitModuleServerGenTs(input) {
|
|
4808
|
+
const installed = new Set(input.registry.modules.map((m) => m.name));
|
|
4809
|
+
const moduleNames = input.facts.filter((f) => f.hasServerRegistrations && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames);
|
|
4810
|
+
for (const name of moduleNames) {
|
|
4811
|
+
if (!isValidModuleName(name)) {
|
|
4812
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
const importLines = moduleNames.map(
|
|
4816
|
+
(name, index) => [
|
|
4817
|
+
`@/modules/${name}/server/registrations`,
|
|
4818
|
+
`import * as serverRegistrations${index} from ${JSON.stringify(`@/modules/${name}/server/registrations`)};`
|
|
4819
|
+
]
|
|
4820
|
+
).sort((a, b) => compareImportSpecifiers(a[0], b[0])).map(([, line]) => line);
|
|
4821
|
+
const entryLines = moduleNames.map(
|
|
4822
|
+
(name, index) => ` { module: ${JSON.stringify(name)}, registrations: serverRegistrations${index} },`
|
|
4823
|
+
);
|
|
4824
|
+
return [
|
|
4825
|
+
formatServerMarker(),
|
|
4826
|
+
"/** DO NOT EDIT \u2014 owned by the module install rail. */",
|
|
4827
|
+
"",
|
|
4828
|
+
...importLines,
|
|
4829
|
+
...importLines.length > 0 ? [""] : [],
|
|
4830
|
+
"export type ModuleServerRegistration = {",
|
|
4831
|
+
" module: string;",
|
|
4832
|
+
" registrations: Readonly<Record<string, unknown>>;",
|
|
4833
|
+
"};",
|
|
4834
|
+
"",
|
|
4835
|
+
...entryLines.length === 0 ? ["export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [];"] : [
|
|
4836
|
+
"export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [",
|
|
4837
|
+
...entryLines,
|
|
4838
|
+
"];"
|
|
4839
|
+
],
|
|
4840
|
+
""
|
|
4841
|
+
].join("\n");
|
|
4842
|
+
}
|
|
4802
4843
|
|
|
4803
4844
|
// ../../packages/lib/src/server/module-rail/composition-artifacts.ts
|
|
4804
4845
|
var COMPOSITION_ARTIFACTS_GENERATED_BY = "module-rail";
|
|
@@ -4814,8 +4855,8 @@ var METHOD_ORDER = {
|
|
|
4814
4855
|
function compareNames2(a, b) {
|
|
4815
4856
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4816
4857
|
}
|
|
4817
|
-
function endpointPairKey(method,
|
|
4818
|
-
return `${method} ${
|
|
4858
|
+
function endpointPairKey(method, path4) {
|
|
4859
|
+
return `${method} ${path4}`;
|
|
4819
4860
|
}
|
|
4820
4861
|
function apiPathToRouteFile(apiPath) {
|
|
4821
4862
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4829,11 +4870,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4829
4870
|
}
|
|
4830
4871
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4831
4872
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4832
|
-
function appRouteFileToNextRelativePath(
|
|
4833
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
4873
|
+
function appRouteFileToNextRelativePath(path4) {
|
|
4874
|
+
const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
4834
4875
|
if (pagePath != null) return pagePath;
|
|
4835
|
-
if (!
|
|
4836
|
-
const relative =
|
|
4876
|
+
if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
4877
|
+
const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4837
4878
|
if (!relative.startsWith("api/")) return null;
|
|
4838
4879
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4839
4880
|
if (!suffix) return null;
|
|
@@ -4861,6 +4902,14 @@ function formatRouteMarker(owner, nextRelativePath) {
|
|
|
4861
4902
|
function formatRegistryMarker() {
|
|
4862
4903
|
return `// ${MODULE_STUB_MARKER} scope=registry`;
|
|
4863
4904
|
}
|
|
4905
|
+
var OWNERLESS_MARKER_KINDS = [
|
|
4906
|
+
"registry",
|
|
4907
|
+
"contributions",
|
|
4908
|
+
"datastores",
|
|
4909
|
+
"i18n",
|
|
4910
|
+
"init",
|
|
4911
|
+
"server"
|
|
4912
|
+
];
|
|
4864
4913
|
function parseCompositionArtifactMarker(source) {
|
|
4865
4914
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4866
4915
|
const match = firstLine.match(
|
|
@@ -4871,26 +4920,8 @@ function parseCompositionArtifactMarker(source) {
|
|
|
4871
4920
|
if (!match) return null;
|
|
4872
4921
|
const owner = match[1];
|
|
4873
4922
|
const scope = match[2];
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
return { kind: "registry" };
|
|
4877
|
-
}
|
|
4878
|
-
if (scope === "contributions") {
|
|
4879
|
-
if (owner) return null;
|
|
4880
|
-
return { kind: "contributions" };
|
|
4881
|
-
}
|
|
4882
|
-
if (scope === "datastores") {
|
|
4883
|
-
if (owner) return null;
|
|
4884
|
-
return { kind: "datastores" };
|
|
4885
|
-
}
|
|
4886
|
-
if (scope === "i18n") {
|
|
4887
|
-
if (owner) return null;
|
|
4888
|
-
return { kind: "i18n" };
|
|
4889
|
-
}
|
|
4890
|
-
if (scope === "init") {
|
|
4891
|
-
if (owner) return null;
|
|
4892
|
-
return { kind: "init" };
|
|
4893
|
-
}
|
|
4923
|
+
const ownerless = OWNERLESS_MARKER_KINDS.find((kind) => kind === scope);
|
|
4924
|
+
if (ownerless) return owner ? null : { kind: ownerless };
|
|
4894
4925
|
if (!owner || !isValidModuleName(owner)) return null;
|
|
4895
4926
|
if (scope === "route") {
|
|
4896
4927
|
const nextRelativePath = match[3]?.trim();
|
|
@@ -4935,9 +4966,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4935
4966
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4936
4967
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4937
4968
|
}
|
|
4938
|
-
function parseLegacyApiAdapter(
|
|
4939
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4940
|
-
if (expectedMarkerKindForPath(
|
|
4969
|
+
function parseLegacyApiAdapter(path4, source) {
|
|
4970
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
4971
|
+
if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
|
|
4941
4972
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4942
4973
|
const match = firstLine.match(
|
|
4943
4974
|
new RegExp(
|
|
@@ -4947,7 +4978,7 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4947
4978
|
if (!match || match[2] !== physicalPath) return null;
|
|
4948
4979
|
const owner = match[1];
|
|
4949
4980
|
if (!isValidModuleName(owner)) return null;
|
|
4950
|
-
const file = ts4.createSourceFile(
|
|
4981
|
+
const file = ts4.createSourceFile(path4, source, ts4.ScriptTarget.Latest, false, ts4.ScriptKind.TS);
|
|
4951
4982
|
if (file.parseDiagnostics.length > 0) {
|
|
4952
4983
|
return null;
|
|
4953
4984
|
}
|
|
@@ -4989,9 +5020,9 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4989
5020
|
}
|
|
4990
5021
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
4991
5022
|
}
|
|
4992
|
-
function parseLegacyRouteAdapter(
|
|
4993
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4994
|
-
if (expectedMarkerKindForPath(
|
|
5023
|
+
function parseLegacyRouteAdapter(path4, source) {
|
|
5024
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
5025
|
+
if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
|
|
4995
5026
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
4996
5027
|
const match = normalized.match(
|
|
4997
5028
|
new RegExp(
|
|
@@ -5003,29 +5034,30 @@ function parseLegacyRouteAdapter(path3, source) {
|
|
|
5003
5034
|
const nextRelativePath = match[2];
|
|
5004
5035
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
5005
5036
|
}
|
|
5006
|
-
function legacyRouteAdapterMatchesStub(
|
|
5007
|
-
const legacy = parseLegacyRouteAdapter(
|
|
5037
|
+
function legacyRouteAdapterMatchesStub(path4, source, stub) {
|
|
5038
|
+
const legacy = parseLegacyRouteAdapter(path4, source);
|
|
5008
5039
|
return legacy != null && legacy.owner === stub.owner;
|
|
5009
5040
|
}
|
|
5010
5041
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
5011
5042
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
5012
5043
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
5013
|
-
function expectedMarkerKindForPath(
|
|
5014
|
-
if (
|
|
5015
|
-
if (
|
|
5016
|
-
if (
|
|
5017
|
-
if (
|
|
5018
|
-
if (
|
|
5019
|
-
if (
|
|
5020
|
-
if (
|
|
5044
|
+
function expectedMarkerKindForPath(path4) {
|
|
5045
|
+
if (path4 === MODULES_GEN_PATH) return "registry";
|
|
5046
|
+
if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
|
|
5047
|
+
if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
|
|
5048
|
+
if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
|
|
5049
|
+
if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
|
|
5050
|
+
if (path4 === MODULE_SERVER_GEN_PATH) return "server";
|
|
5051
|
+
if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
|
|
5052
|
+
if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
|
|
5021
5053
|
return "route";
|
|
5022
5054
|
}
|
|
5023
5055
|
return null;
|
|
5024
5056
|
}
|
|
5025
|
-
function markerCongruentWithPath(
|
|
5026
|
-
if (expectedMarkerKindForPath(
|
|
5057
|
+
function markerCongruentWithPath(path4, marker) {
|
|
5058
|
+
if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
|
|
5027
5059
|
if (marker.kind === "route") {
|
|
5028
|
-
const derived = appPageFileToNextRelativePath(
|
|
5060
|
+
const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
5029
5061
|
return derived != null && derived === marker.nextRelativePath;
|
|
5030
5062
|
}
|
|
5031
5063
|
return true;
|
|
@@ -5296,8 +5328,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5296
5328
|
function activeEnhancementPeers(registryModule) {
|
|
5297
5329
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5298
5330
|
}
|
|
5299
|
-
function endpointBucketKey(
|
|
5300
|
-
return `${
|
|
5331
|
+
function endpointBucketKey(path4, owner, scope) {
|
|
5332
|
+
return `${path4}\0${owner}\0${scopeLabel(scope)}`;
|
|
5301
5333
|
}
|
|
5302
5334
|
function planRouteStubs(input) {
|
|
5303
5335
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5421,7 +5453,7 @@ function planRouteStubs(input) {
|
|
|
5421
5453
|
for (const entry of entries) {
|
|
5422
5454
|
const nextRelativePath = entry.nextRelativePath;
|
|
5423
5455
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5424
|
-
const
|
|
5456
|
+
const path4 = routeEntryToAppPageFile(nextRelativePath);
|
|
5425
5457
|
const existing = routeDestinationOwners.get(destination);
|
|
5426
5458
|
if (existing) {
|
|
5427
5459
|
throw new Error(
|
|
@@ -5431,13 +5463,13 @@ function planRouteStubs(input) {
|
|
|
5431
5463
|
routeDestinationOwners.set(destination, {
|
|
5432
5464
|
owner: regMod.name,
|
|
5433
5465
|
nextRelativePath,
|
|
5434
|
-
path:
|
|
5466
|
+
path: path4
|
|
5435
5467
|
});
|
|
5436
5468
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5437
5469
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5438
5470
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5439
5471
|
routeStubs.push({
|
|
5440
|
-
path:
|
|
5472
|
+
path: path4,
|
|
5441
5473
|
owner: regMod.name,
|
|
5442
5474
|
nextRelativePath,
|
|
5443
5475
|
importModule,
|
|
@@ -5498,16 +5530,16 @@ function planCompositionArtifacts(input) {
|
|
|
5498
5530
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5499
5531
|
]);
|
|
5500
5532
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5501
|
-
const isProvenStaleDeletionCandidate = (
|
|
5502
|
-
if (
|
|
5503
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5504
|
-
if (desiredPaths.has(
|
|
5505
|
-
if (!existingGeneratedPathSet.has(
|
|
5506
|
-
const legacy = parseLegacyApiAdapter(
|
|
5533
|
+
const isProvenStaleDeletionCandidate = (path4, content) => {
|
|
5534
|
+
if (path4 === MODULES_GEN_PATH) return false;
|
|
5535
|
+
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
|
|
5536
|
+
if (desiredPaths.has(path4)) return false;
|
|
5537
|
+
if (!existingGeneratedPathSet.has(path4)) return false;
|
|
5538
|
+
const legacy = parseLegacyApiAdapter(path4, content);
|
|
5507
5539
|
if (legacy) return true;
|
|
5508
|
-
if (parseLegacyRouteAdapter(
|
|
5540
|
+
if (parseLegacyRouteAdapter(path4, content)) return true;
|
|
5509
5541
|
const marker = parseCompositionArtifactMarker(content);
|
|
5510
|
-
return marker != null && markerCongruentWithPath(
|
|
5542
|
+
return marker != null && markerCongruentWithPath(path4, marker);
|
|
5511
5543
|
};
|
|
5512
5544
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5513
5545
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5615,6 +5647,7 @@ function planCompositionArtifacts(input) {
|
|
|
5615
5647
|
let datastoresContent;
|
|
5616
5648
|
let i18nContent;
|
|
5617
5649
|
let initContent;
|
|
5650
|
+
let serverContent;
|
|
5618
5651
|
try {
|
|
5619
5652
|
contributionsContent = emitModuleContributionsGenTs({
|
|
5620
5653
|
registry: input.registry,
|
|
@@ -5639,6 +5672,10 @@ function planCompositionArtifacts(input) {
|
|
|
5639
5672
|
registry: input.registry,
|
|
5640
5673
|
facts: compositionEntryFacts
|
|
5641
5674
|
});
|
|
5675
|
+
serverContent = emitModuleServerGenTs({
|
|
5676
|
+
registry: input.registry,
|
|
5677
|
+
facts: compositionEntryFacts
|
|
5678
|
+
});
|
|
5642
5679
|
} catch (error) {
|
|
5643
5680
|
return {
|
|
5644
5681
|
ok: false,
|
|
@@ -5651,17 +5688,18 @@ function planCompositionArtifacts(input) {
|
|
|
5651
5688
|
desired.set(MODULE_DATASTORES_GEN_PATH, datastoresContent);
|
|
5652
5689
|
desired.set(MODULE_I18N_GEN_PATH, i18nContent);
|
|
5653
5690
|
desired.set(MODULE_INIT_SERVER_GEN_PATH, initContent);
|
|
5691
|
+
desired.set(MODULE_SERVER_GEN_PATH, serverContent);
|
|
5654
5692
|
for (const stub of stubs.endpointStubs) desired.set(stub.path, stub.content);
|
|
5655
5693
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5656
5694
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5657
5695
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5658
|
-
for (const [
|
|
5659
|
-
const existing = input.existingFiles[
|
|
5696
|
+
for (const [path4] of desired) {
|
|
5697
|
+
const existing = input.existingFiles[path4];
|
|
5660
5698
|
if (existing == null) continue;
|
|
5661
5699
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5662
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5663
|
-
const plannedRoute = routeStubByPath.get(
|
|
5664
|
-
const legacy = parseLegacyApiAdapter(
|
|
5700
|
+
const plannedEndpoint = endpointStubByPath.get(path4);
|
|
5701
|
+
const plannedRoute = routeStubByPath.get(path4);
|
|
5702
|
+
const legacy = parseLegacyApiAdapter(path4, existing);
|
|
5665
5703
|
if (legacy && plannedEndpoint) {
|
|
5666
5704
|
marker = {
|
|
5667
5705
|
kind: "endpoint",
|
|
@@ -5669,66 +5707,66 @@ function planCompositionArtifacts(input) {
|
|
|
5669
5707
|
scope: plannedEndpoint.scope
|
|
5670
5708
|
};
|
|
5671
5709
|
}
|
|
5672
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5710
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
|
|
5673
5711
|
marker = {
|
|
5674
5712
|
kind: "route",
|
|
5675
5713
|
owner: plannedRoute.owner,
|
|
5676
5714
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5677
5715
|
};
|
|
5678
5716
|
}
|
|
5679
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5717
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
|
|
5680
5718
|
if (!marker) {
|
|
5681
5719
|
return {
|
|
5682
5720
|
ok: false,
|
|
5683
|
-
error:
|
|
5684
|
-
path:
|
|
5721
|
+
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}`,
|
|
5722
|
+
path: path4
|
|
5685
5723
|
};
|
|
5686
5724
|
}
|
|
5687
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5725
|
+
const expectedKind = expectedMarkerKindForPath(path4);
|
|
5688
5726
|
if (marker.kind !== expectedKind) {
|
|
5689
5727
|
return {
|
|
5690
5728
|
ok: false,
|
|
5691
|
-
error:
|
|
5692
|
-
path:
|
|
5729
|
+
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)`,
|
|
5730
|
+
path: path4
|
|
5693
5731
|
};
|
|
5694
5732
|
}
|
|
5695
5733
|
if (marker.kind === "endpoint") {
|
|
5696
|
-
const stub = endpointStubByPath.get(
|
|
5734
|
+
const stub = endpointStubByPath.get(path4);
|
|
5697
5735
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5698
5736
|
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5699
5737
|
if (!ownerMatches || !scopeMatches) {
|
|
5700
5738
|
return {
|
|
5701
5739
|
ok: false,
|
|
5702
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5703
|
-
path:
|
|
5740
|
+
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) : "?"})`,
|
|
5741
|
+
path: path4,
|
|
5704
5742
|
owner: stub?.owner
|
|
5705
5743
|
};
|
|
5706
5744
|
}
|
|
5707
5745
|
} else if (marker.kind === "route") {
|
|
5708
|
-
const stub = routeStubByPath.get(
|
|
5746
|
+
const stub = routeStubByPath.get(path4);
|
|
5709
5747
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5710
5748
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5711
5749
|
if (!ownerMatches || !pathMatches) {
|
|
5712
5750
|
return {
|
|
5713
5751
|
ok: false,
|
|
5714
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5715
|
-
path:
|
|
5752
|
+
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 ?? "?"})`,
|
|
5753
|
+
path: path4,
|
|
5716
5754
|
owner: stub?.owner
|
|
5717
5755
|
};
|
|
5718
5756
|
}
|
|
5719
5757
|
}
|
|
5720
5758
|
}
|
|
5721
5759
|
const deletes = [];
|
|
5722
|
-
for (const
|
|
5723
|
-
const existing = input.existingFiles[
|
|
5760
|
+
for (const path4 of input.existingGeneratedPaths) {
|
|
5761
|
+
const existing = input.existingFiles[path4];
|
|
5724
5762
|
if (existing == null) continue;
|
|
5725
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5763
|
+
if (!isProvenStaleDeletionCandidate(path4, existing)) {
|
|
5726
5764
|
continue;
|
|
5727
5765
|
}
|
|
5728
|
-
deletes.push(
|
|
5766
|
+
deletes.push(path4);
|
|
5729
5767
|
}
|
|
5730
5768
|
deletes.sort(compareNames2);
|
|
5731
|
-
const writes = [...desired.entries()].filter(([
|
|
5769
|
+
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));
|
|
5732
5770
|
return {
|
|
5733
5771
|
ok: true,
|
|
5734
5772
|
plan: {
|
|
@@ -5757,8 +5795,16 @@ async function checkoutParsesTypedStores(sandbox) {
|
|
|
5757
5795
|
try {
|
|
5758
5796
|
if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
|
|
5759
5797
|
const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
|
|
5760
|
-
|
|
5761
|
-
|
|
5798
|
+
return composeRangeParsesTypedStores(
|
|
5799
|
+
pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
|
|
5800
|
+
);
|
|
5801
|
+
} catch {
|
|
5802
|
+
return false;
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
function composeRangeParsesTypedStores(range) {
|
|
5806
|
+
if (!range) return false;
|
|
5807
|
+
try {
|
|
5762
5808
|
const lowest = import_semver.default.minVersion(range);
|
|
5763
5809
|
return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
|
|
5764
5810
|
} catch {
|
|
@@ -6203,11 +6249,11 @@ function countedCompositionContext(context) {
|
|
|
6203
6249
|
counts.sandboxExecs += 1;
|
|
6204
6250
|
return context.sandbox.exec(...args);
|
|
6205
6251
|
},
|
|
6206
|
-
readFile: (
|
|
6252
|
+
readFile: (path4) => {
|
|
6207
6253
|
counts.sandboxReads += 1;
|
|
6208
|
-
return context.sandbox.readFile(
|
|
6254
|
+
return context.sandbox.readFile(path4);
|
|
6209
6255
|
},
|
|
6210
|
-
fileExists: (
|
|
6256
|
+
fileExists: (path4) => context.sandbox.fileExists(path4),
|
|
6211
6257
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6212
6258
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6213
6259
|
}
|
|
@@ -6419,14 +6465,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6419
6465
|
}
|
|
6420
6466
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6421
6467
|
}
|
|
6422
|
-
async function writeSandboxFileAtomic(target,
|
|
6423
|
-
const dir = dirnamePosix(
|
|
6424
|
-
const tmp = `${
|
|
6468
|
+
async function writeSandboxFileAtomic(target, path4, content) {
|
|
6469
|
+
const dir = dirnamePosix(path4);
|
|
6470
|
+
const tmp = `${path4}.tmp.${target.runId}`;
|
|
6425
6471
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6426
6472
|
const cmd = [
|
|
6427
6473
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6428
6474
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6429
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6475
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
|
|
6430
6476
|
].join(" && ");
|
|
6431
6477
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6432
6478
|
if (result.exitCode !== 0) {
|
|
@@ -6441,14 +6487,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
|
|
|
6441
6487
|
}
|
|
6442
6488
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6443
6489
|
throw new Error(
|
|
6444
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6490
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6445
6491
|
);
|
|
6446
6492
|
}
|
|
6447
6493
|
}
|
|
6448
6494
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6449
6495
|
const absent = /* @__PURE__ */ new Set();
|
|
6450
|
-
for (const
|
|
6451
|
-
let dir = dirnamePosix(
|
|
6496
|
+
for (const path4 of writePaths) {
|
|
6497
|
+
let dir = dirnamePosix(path4);
|
|
6452
6498
|
while (dir !== "." && dir !== "") {
|
|
6453
6499
|
if (absent.has(dir)) {
|
|
6454
6500
|
dir = dirnamePosix(dir);
|
|
@@ -6485,12 +6531,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6485
6531
|
);
|
|
6486
6532
|
}
|
|
6487
6533
|
}
|
|
6488
|
-
async function deleteSandboxFile(context,
|
|
6489
|
-
const result = await context.sandbox.exec(`rm -f ${shellQuote(
|
|
6534
|
+
async function deleteSandboxFile(context, path4) {
|
|
6535
|
+
const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
|
|
6490
6536
|
if (result.exitCode !== 0) {
|
|
6491
6537
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6492
6538
|
throw new Error(
|
|
6493
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6539
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6494
6540
|
);
|
|
6495
6541
|
}
|
|
6496
6542
|
}
|
|
@@ -6557,37 +6603,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6557
6603
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6558
6604
|
appliedWrites.push({ path: write.path, previous });
|
|
6559
6605
|
}
|
|
6560
|
-
for (const
|
|
6561
|
-
const previous = existingFiles[
|
|
6606
|
+
for (const path4 of plan.deletes) {
|
|
6607
|
+
const previous = existingFiles[path4];
|
|
6562
6608
|
if (previous == null) continue;
|
|
6563
|
-
await deleteSandboxFile(context,
|
|
6564
|
-
appliedDeletes.push({ path:
|
|
6609
|
+
await deleteSandboxFile(context, path4);
|
|
6610
|
+
appliedDeletes.push({ path: path4, previous });
|
|
6565
6611
|
}
|
|
6566
6612
|
} catch (error) {
|
|
6567
6613
|
console.log(
|
|
6568
6614
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6569
6615
|
);
|
|
6570
|
-
for (const { path:
|
|
6616
|
+
for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
|
|
6571
6617
|
try {
|
|
6572
|
-
await writeSandboxFileAtomic(context,
|
|
6618
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6573
6619
|
} catch (rollbackError) {
|
|
6574
6620
|
console.log(
|
|
6575
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(
|
|
6621
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6576
6622
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6577
6623
|
)}`
|
|
6578
6624
|
);
|
|
6579
6625
|
}
|
|
6580
6626
|
}
|
|
6581
|
-
for (const { path:
|
|
6627
|
+
for (const { path: path4, previous } of [...appliedWrites].reverse()) {
|
|
6582
6628
|
try {
|
|
6583
6629
|
if (previous == null) {
|
|
6584
|
-
await deleteSandboxFile(context,
|
|
6630
|
+
await deleteSandboxFile(context, path4);
|
|
6585
6631
|
} else {
|
|
6586
|
-
await writeSandboxFileAtomic(context,
|
|
6632
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6587
6633
|
}
|
|
6588
6634
|
} catch (rollbackError) {
|
|
6589
6635
|
console.log(
|
|
6590
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(
|
|
6636
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
|
|
6591
6637
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6592
6638
|
)}`
|
|
6593
6639
|
);
|
|
@@ -6621,39 +6667,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6621
6667
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6622
6668
|
);
|
|
6623
6669
|
const presentPaths = [];
|
|
6624
|
-
for (const
|
|
6670
|
+
for (const path4 of candidates) {
|
|
6625
6671
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6626
|
-
|
|
6627
|
-
kinds.get(
|
|
6672
|
+
path4,
|
|
6673
|
+
kinds.get(path4) ?? "unreadable"
|
|
6628
6674
|
);
|
|
6629
|
-
if (presence === "file") presentPaths.push(
|
|
6675
|
+
if (presence === "file") presentPaths.push(path4);
|
|
6630
6676
|
}
|
|
6631
6677
|
let contents;
|
|
6632
6678
|
try {
|
|
6633
6679
|
contents = await readFilesMany(context, presentPaths);
|
|
6634
6680
|
} catch (error) {
|
|
6635
|
-
const
|
|
6681
|
+
const path4 = readFailurePath(error);
|
|
6636
6682
|
throw new Error(
|
|
6637
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6683
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
|
|
6638
6684
|
error instanceof Error ? error.message : String(error)
|
|
6639
6685
|
)}`
|
|
6640
6686
|
);
|
|
6641
6687
|
}
|
|
6642
6688
|
const owned = [];
|
|
6643
|
-
for (const
|
|
6644
|
-
const content = contents.get(
|
|
6689
|
+
for (const path4 of presentPaths) {
|
|
6690
|
+
const content = contents.get(path4);
|
|
6645
6691
|
const marker = parseCompositionArtifactMarker(content);
|
|
6646
|
-
if (parseLegacyApiAdapter(
|
|
6647
|
-
owned.push(
|
|
6692
|
+
if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
|
|
6693
|
+
owned.push(path4);
|
|
6648
6694
|
continue;
|
|
6649
6695
|
}
|
|
6650
6696
|
if (marker == null) continue;
|
|
6651
|
-
if (!markerCongruentWithPath(
|
|
6697
|
+
if (!markerCongruentWithPath(path4, marker)) {
|
|
6652
6698
|
throw new Error(
|
|
6653
|
-
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(
|
|
6699
|
+
`[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)`
|
|
6654
6700
|
);
|
|
6655
6701
|
}
|
|
6656
|
-
owned.push(
|
|
6702
|
+
owned.push(path4);
|
|
6657
6703
|
}
|
|
6658
6704
|
return owned;
|
|
6659
6705
|
}
|
|
@@ -6786,7 +6832,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6786
6832
|
{ raiseOnError: false }
|
|
6787
6833
|
);
|
|
6788
6834
|
if (result.exitCode !== 0) {
|
|
6789
|
-
for (const
|
|
6835
|
+
for (const path4 of chunk) kinds.set(path4, "unreadable");
|
|
6790
6836
|
continue;
|
|
6791
6837
|
}
|
|
6792
6838
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6797,31 +6843,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6797
6843
|
}
|
|
6798
6844
|
} else {
|
|
6799
6845
|
for (const line of lines) {
|
|
6800
|
-
const [rawKind,
|
|
6846
|
+
const [rawKind, path4] = line.split(" ", 2);
|
|
6801
6847
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6802
|
-
if (
|
|
6803
|
-
kinds.set(
|
|
6848
|
+
if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
6849
|
+
kinds.set(path4, kind);
|
|
6804
6850
|
}
|
|
6805
6851
|
}
|
|
6806
6852
|
}
|
|
6807
|
-
for (const
|
|
6808
|
-
if (!kinds.has(
|
|
6853
|
+
for (const path4 of chunk) {
|
|
6854
|
+
if (!kinds.has(path4)) kinds.set(path4, "unreadable");
|
|
6809
6855
|
}
|
|
6810
6856
|
}
|
|
6811
6857
|
return kinds;
|
|
6812
6858
|
}
|
|
6813
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6814
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
6859
|
+
async function classifySandboxPathPresenceKind(context, path4, probeToken) {
|
|
6860
|
+
return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
|
|
6815
6861
|
}
|
|
6816
6862
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6817
|
-
function assertRealLocaleJsonFilePresence(
|
|
6863
|
+
function assertRealLocaleJsonFilePresence(path4, presence) {
|
|
6818
6864
|
if (presence === "file") return;
|
|
6819
6865
|
if (presence === "absent") {
|
|
6820
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
6866
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
|
|
6821
6867
|
}
|
|
6822
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6868
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6823
6869
|
throw new Error(
|
|
6824
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6870
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
|
|
6825
6871
|
);
|
|
6826
6872
|
}
|
|
6827
6873
|
function splitTaggedLine(line) {
|
|
@@ -6896,11 +6942,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6896
6942
|
}
|
|
6897
6943
|
async function readFilesMany(context, paths) {
|
|
6898
6944
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6899
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
6945
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
|
|
6900
6946
|
try {
|
|
6901
|
-
return { path:
|
|
6947
|
+
return { path: path4, value: await context.sandbox.readFile(path4) };
|
|
6902
6948
|
} catch (error) {
|
|
6903
|
-
return { path:
|
|
6949
|
+
return { path: path4, error };
|
|
6904
6950
|
}
|
|
6905
6951
|
});
|
|
6906
6952
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6921,55 +6967,60 @@ function readFailurePath(error) {
|
|
|
6921
6967
|
return typeof error === "object" && error !== null && "path" in error ? String(error.path) : "unknown";
|
|
6922
6968
|
}
|
|
6923
6969
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6970
|
+
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6924
6971
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6925
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6926
|
-
return classifySandboxPathPresenceKind(context,
|
|
6972
|
+
async function classifyCompositionArtifactPathKind(context, path4) {
|
|
6973
|
+
return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6927
6974
|
}
|
|
6928
|
-
function assertRealCompositionArtifactFilePresence(
|
|
6975
|
+
function assertRealCompositionArtifactFilePresence(path4, presence) {
|
|
6929
6976
|
if (presence === "file") return "file";
|
|
6930
6977
|
if (presence === "absent") return "absent";
|
|
6931
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6978
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6932
6979
|
throw new Error(
|
|
6933
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6980
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
|
|
6934
6981
|
);
|
|
6935
6982
|
}
|
|
6936
|
-
async function readConventionalEntryIfRealFile(
|
|
6937
|
-
const presence = presences.get(
|
|
6938
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6983
|
+
async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
6984
|
+
const presence = presences.get(path4) ?? "unreadable";
|
|
6985
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6939
6986
|
if (presenceError) {
|
|
6940
6987
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6941
6988
|
}
|
|
6942
6989
|
if (presence === "absent") {
|
|
6943
6990
|
return null;
|
|
6944
6991
|
}
|
|
6945
|
-
const source = files.get(
|
|
6992
|
+
const source = files.get(path4);
|
|
6946
6993
|
switch (kind) {
|
|
6947
6994
|
case "contributions":
|
|
6948
|
-
assertContributionsExport(source,
|
|
6949
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6995
|
+
assertContributionsExport(source, path4);
|
|
6996
|
+
assertClientSafeConventionalEntryImports(source, path4, "contributions");
|
|
6950
6997
|
break;
|
|
6951
6998
|
case "slotCatalogs":
|
|
6952
|
-
assertSlotCatalogsExport(source,
|
|
6953
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6999
|
+
assertSlotCatalogsExport(source, path4);
|
|
7000
|
+
assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
|
|
6954
7001
|
break;
|
|
6955
7002
|
case "initializeEnhancement":
|
|
6956
|
-
assertInitializeEnhancementExport(source,
|
|
7003
|
+
assertInitializeEnhancementExport(source, path4);
|
|
6957
7004
|
break;
|
|
6958
7005
|
}
|
|
6959
7006
|
return source;
|
|
6960
7007
|
}
|
|
6961
|
-
|
|
6962
|
-
const
|
|
7008
|
+
function hasRealServerRegistrations(modulePath, presences) {
|
|
7009
|
+
const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
7010
|
+
return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
|
|
7011
|
+
}
|
|
7012
|
+
async function readJsonObjectFile(path4, files) {
|
|
7013
|
+
const raw = files.get(path4);
|
|
6963
7014
|
let parsed;
|
|
6964
7015
|
try {
|
|
6965
7016
|
parsed = JSON.parse(raw);
|
|
6966
7017
|
} catch (error) {
|
|
6967
7018
|
throw new Error(
|
|
6968
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
7019
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
|
|
6969
7020
|
);
|
|
6970
7021
|
}
|
|
6971
7022
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
6972
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
7023
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
|
|
6973
7024
|
}
|
|
6974
7025
|
return parsed;
|
|
6975
7026
|
}
|
|
@@ -6981,6 +7032,9 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
6981
7032
|
const overridesDir = `${APP_PACKAGE_DIR}/src/lib/i18n/overrides`;
|
|
6982
7033
|
const localeDirs = [substrateDir, overridesDir];
|
|
6983
7034
|
const conventionalPaths = [];
|
|
7035
|
+
const serverRegistrationPaths = registry.modules.map(
|
|
7036
|
+
(mod) => `${MODULES_SANDBOX_DIR}/${mod.name}/${SERVER_REGISTRATIONS_FILE}`
|
|
7037
|
+
);
|
|
6984
7038
|
for (const mod of registry.modules) {
|
|
6985
7039
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
6986
7040
|
localeDirs.push(`${modulePath}/i18n`);
|
|
@@ -7009,17 +7063,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7009
7063
|
const [conventionalPresences, localePresences] = await Promise.all([
|
|
7010
7064
|
classifySandboxPathPresenceKindMany(
|
|
7011
7065
|
context,
|
|
7012
|
-
conventionalPaths,
|
|
7066
|
+
[...conventionalPaths, ...serverRegistrationPaths],
|
|
7013
7067
|
CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN
|
|
7014
7068
|
),
|
|
7015
7069
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7016
7070
|
]);
|
|
7017
7071
|
const presentConventionalPaths = [];
|
|
7018
|
-
for (const
|
|
7019
|
-
const presence = conventionalPresences.get(
|
|
7020
|
-
if (presence === "file") presentConventionalPaths.push(
|
|
7072
|
+
for (const path4 of conventionalPaths) {
|
|
7073
|
+
const presence = conventionalPresences.get(path4) ?? "unreadable";
|
|
7074
|
+
if (presence === "file") presentConventionalPaths.push(path4);
|
|
7021
7075
|
}
|
|
7022
|
-
const presentLocalePaths = localePaths.filter((
|
|
7076
|
+
const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
|
|
7023
7077
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7024
7078
|
const substrateLocales = [];
|
|
7025
7079
|
for (const locale of substrateNames) {
|
|
@@ -7071,6 +7125,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7071
7125
|
);
|
|
7072
7126
|
}
|
|
7073
7127
|
}
|
|
7128
|
+
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7074
7129
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7075
7130
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7076
7131
|
throw new Error(
|
|
@@ -7110,13 +7165,14 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7110
7165
|
moduleName: mod.name,
|
|
7111
7166
|
hasRootContributions,
|
|
7112
7167
|
hasRootSlots,
|
|
7168
|
+
hasServerRegistrations,
|
|
7113
7169
|
rootLocales,
|
|
7114
7170
|
enhancements
|
|
7115
7171
|
});
|
|
7116
7172
|
if (rootLocales.length > 0) {
|
|
7117
7173
|
for (const locale of rootLocales) {
|
|
7118
|
-
const
|
|
7119
|
-
assertRealLocaleJsonFilePresence(
|
|
7174
|
+
const path4 = `${modulePath}/i18n/${locale}.json`;
|
|
7175
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7120
7176
|
}
|
|
7121
7177
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7122
7178
|
const localeMessages = { en: enMessages };
|
|
@@ -7140,8 +7196,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7140
7196
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7141
7197
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7142
7198
|
for (const locale of enh.locales) {
|
|
7143
|
-
const
|
|
7144
|
-
assertRealLocaleJsonFilePresence(
|
|
7199
|
+
const path4 = `${peerDir}/i18n/${locale}.json`;
|
|
7200
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7145
7201
|
}
|
|
7146
7202
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7147
7203
|
const localeMessages = { en: enMessages };
|
|
@@ -7313,6 +7369,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7313
7369
|
MODULE_DATASTORES_GEN_PATH,
|
|
7314
7370
|
MODULE_I18N_GEN_PATH,
|
|
7315
7371
|
MODULE_INIT_SERVER_GEN_PATH,
|
|
7372
|
+
MODULE_SERVER_GEN_PATH,
|
|
7316
7373
|
...existingGeneratedPaths,
|
|
7317
7374
|
...Object.keys(existingAppPages),
|
|
7318
7375
|
...Object.keys(existingAppRoutes)
|
|
@@ -7341,16 +7398,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7341
7398
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7342
7399
|
const existingFiles = {};
|
|
7343
7400
|
const remainingCandidatePaths = [];
|
|
7344
|
-
for (const
|
|
7345
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7346
|
-
existingFiles[
|
|
7401
|
+
for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
|
|
7402
|
+
if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
|
|
7403
|
+
existingFiles[path4] = existingAppPages[path4];
|
|
7347
7404
|
continue;
|
|
7348
7405
|
}
|
|
7349
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7350
|
-
existingFiles[
|
|
7406
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
|
|
7407
|
+
existingFiles[path4] = existingAppRoutes[path4];
|
|
7351
7408
|
continue;
|
|
7352
7409
|
}
|
|
7353
|
-
remainingCandidatePaths.push(
|
|
7410
|
+
remainingCandidatePaths.push(path4);
|
|
7354
7411
|
}
|
|
7355
7412
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7356
7413
|
context,
|
|
@@ -7358,12 +7415,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7358
7415
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7359
7416
|
);
|
|
7360
7417
|
const presentCandidatePaths = [];
|
|
7361
|
-
for (const
|
|
7418
|
+
for (const path4 of remainingCandidatePaths) {
|
|
7362
7419
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7363
|
-
|
|
7364
|
-
candidateKinds.get(
|
|
7420
|
+
path4,
|
|
7421
|
+
candidateKinds.get(path4) ?? "unreadable"
|
|
7365
7422
|
);
|
|
7366
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7423
|
+
if (presence === "file") presentCandidatePaths.push(path4);
|
|
7367
7424
|
}
|
|
7368
7425
|
try {
|
|
7369
7426
|
Object.assign(
|
|
@@ -7371,9 +7428,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7371
7428
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7372
7429
|
);
|
|
7373
7430
|
} catch (error) {
|
|
7374
|
-
const
|
|
7431
|
+
const path4 = readFailurePath(error);
|
|
7375
7432
|
throw new Error(
|
|
7376
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7433
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
|
|
7377
7434
|
error instanceof Error ? error.message : String(error)
|
|
7378
7435
|
)}`
|
|
7379
7436
|
);
|
|
@@ -7442,14 +7499,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7442
7499
|
var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
7443
7500
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
7444
7501
|
|
|
7502
|
+
// src/module-boundaries.ts
|
|
7503
|
+
import fs2 from "fs";
|
|
7504
|
+
import path2 from "path";
|
|
7505
|
+
import ts5 from "typescript";
|
|
7506
|
+
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
7507
|
+
var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
7508
|
+
var DATA_FILE = /\.(?:sql|json)$/;
|
|
7509
|
+
function dataPathsIn(fileName, source) {
|
|
7510
|
+
if (!/\.(?:sql|json)["'`]/.test(source)) return [];
|
|
7511
|
+
const paths = [];
|
|
7512
|
+
const visit = (node) => {
|
|
7513
|
+
if ((ts5.isStringLiteralLike(node) || ts5.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
|
|
7514
|
+
paths.push(node.text);
|
|
7515
|
+
}
|
|
7516
|
+
ts5.forEachChild(node, visit);
|
|
7517
|
+
};
|
|
7518
|
+
visit(ts5.createSourceFile(fileName, source, ts5.ScriptTarget.Latest, false));
|
|
7519
|
+
return paths;
|
|
7520
|
+
}
|
|
7521
|
+
function scanModule(modulesDir, module) {
|
|
7522
|
+
const scanned = /* @__PURE__ */ new Map();
|
|
7523
|
+
const moduleDir = path2.join(modulesDir, module);
|
|
7524
|
+
if (!fs2.existsSync(moduleDir)) return scanned;
|
|
7525
|
+
const files = fs2.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
|
|
7526
|
+
for (const relative of files) {
|
|
7527
|
+
const absolute = path2.join(moduleDir, relative);
|
|
7528
|
+
if (!fs2.statSync(absolute).isFile()) continue;
|
|
7529
|
+
const source = fs2.readFileSync(absolute, "utf8");
|
|
7530
|
+
const specifiers = ts5.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
|
|
7531
|
+
const dataPaths = dataPathsIn(absolute, source);
|
|
7532
|
+
const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
|
|
7533
|
+
if (spec.startsWith("@/modules/"))
|
|
7534
|
+
return [path2.join(modulesDir, spec.slice("@/modules/".length))];
|
|
7535
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
7536
|
+
return [path2.resolve(path2.dirname(absolute), spec)];
|
|
7537
|
+
}
|
|
7538
|
+
return [];
|
|
7539
|
+
});
|
|
7540
|
+
scanned.set(absolute, {
|
|
7541
|
+
module,
|
|
7542
|
+
relative: relative.split(path2.sep).join("/"),
|
|
7543
|
+
targets,
|
|
7544
|
+
dataPaths
|
|
7545
|
+
});
|
|
7546
|
+
}
|
|
7547
|
+
return scanned;
|
|
7548
|
+
}
|
|
7549
|
+
function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
|
|
7550
|
+
const files = /* @__PURE__ */ new Map();
|
|
7551
|
+
for (const [module, peers] of Object.entries(peersByModule)) {
|
|
7552
|
+
if (peers.length === 0) continue;
|
|
7553
|
+
for (const [absolute, file] of scanModule(modulesDir, module)) files.set(absolute, file);
|
|
7554
|
+
}
|
|
7555
|
+
const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
|
|
7556
|
+
(q) => file.relative.startsWith(`enhancements/${q}/`)
|
|
7557
|
+
);
|
|
7558
|
+
const goneWith = (target, peer) => {
|
|
7559
|
+
const relative = path2.relative(modulesDir, target).split(path2.sep).join("/");
|
|
7560
|
+
return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
|
|
7561
|
+
};
|
|
7562
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
7563
|
+
const queue = [];
|
|
7564
|
+
const taint = (absolute, peer) => {
|
|
7565
|
+
const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
|
|
7566
|
+
if (peers.has(peer)) return;
|
|
7567
|
+
peers.add(peer);
|
|
7568
|
+
tainted.set(absolute, peers);
|
|
7569
|
+
queue.push([absolute, peer]);
|
|
7570
|
+
};
|
|
7571
|
+
const importers = /* @__PURE__ */ new Map();
|
|
7572
|
+
for (const [absolute, file] of files) {
|
|
7573
|
+
for (const target of file.targets) {
|
|
7574
|
+
const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
|
|
7575
|
+
if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
|
|
7576
|
+
}
|
|
7577
|
+
for (const peer of peersByModule[file.module] ?? []) {
|
|
7578
|
+
if (exempt(file, peer)) continue;
|
|
7579
|
+
if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
|
|
7580
|
+
(p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
|
|
7581
|
+
)) {
|
|
7582
|
+
taint(absolute, peer);
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
}
|
|
7586
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
7587
|
+
const [target, peer] = next;
|
|
7588
|
+
for (const importer of importers.get(target) ?? []) {
|
|
7589
|
+
const file = files.get(importer);
|
|
7590
|
+
if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
|
|
7591
|
+
taint(importer, peer);
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
const violations = [];
|
|
7596
|
+
for (const [absolute, peers] of tainted) {
|
|
7597
|
+
const file = files.get(absolute);
|
|
7598
|
+
for (const peer of peers) {
|
|
7599
|
+
violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
|
|
7603
|
+
}
|
|
7604
|
+
function findOptionalPeerViolations(modulesDir) {
|
|
7605
|
+
if (!fs2.existsSync(modulesDir)) return [];
|
|
7606
|
+
const manifests = {};
|
|
7607
|
+
for (const name of fs2.readdirSync(modulesDir)) {
|
|
7608
|
+
const manifestPath = path2.join(modulesDir, name, "module.json");
|
|
7609
|
+
if (!fs2.existsSync(manifestPath)) continue;
|
|
7610
|
+
manifests[name] = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
|
|
7611
|
+
}
|
|
7612
|
+
const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
|
|
7613
|
+
const peersByModule = {};
|
|
7614
|
+
for (const [name, manifest] of Object.entries(manifests)) {
|
|
7615
|
+
const uses = new Set(manifest.uses ?? []);
|
|
7616
|
+
peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
|
|
7617
|
+
}
|
|
7618
|
+
return findModuleBoundaryViolations(modulesDir, peersByModule);
|
|
7619
|
+
}
|
|
7620
|
+
function formatBoundaryViolations(modulesDirLabel, violations) {
|
|
7621
|
+
const files = new Set(violations.map((v) => v.file)).size;
|
|
7622
|
+
return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
|
|
7623
|
+
\`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).
|
|
7624
|
+
` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
|
|
7625
|
+
}
|
|
7626
|
+
|
|
7445
7627
|
// src/index.ts
|
|
7446
7628
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7447
7629
|
function readComposeInputs(cwd) {
|
|
7448
|
-
const file =
|
|
7449
|
-
if (!
|
|
7630
|
+
const file = path3.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7631
|
+
if (!fs3.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7450
7632
|
let raw;
|
|
7451
7633
|
try {
|
|
7452
|
-
raw = JSON.parse(
|
|
7634
|
+
raw = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
7453
7635
|
} catch (error) {
|
|
7454
7636
|
throw new Error(
|
|
7455
7637
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7491,18 +7673,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7491
7673
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7492
7674
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7493
7675
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7494
|
-
const file =
|
|
7495
|
-
const existing =
|
|
7676
|
+
const file = path3.join(cwd, STUB_GITIGNORE_PATH);
|
|
7677
|
+
const existing = fs3.existsSync(file) ? fs3.readFileSync(file, "utf8") : null;
|
|
7496
7678
|
if (existing !== content) {
|
|
7497
|
-
|
|
7498
|
-
|
|
7679
|
+
fs3.mkdirSync(path3.dirname(file), { recursive: true });
|
|
7680
|
+
fs3.writeFileSync(file, content);
|
|
7499
7681
|
}
|
|
7500
7682
|
return entries.length;
|
|
7501
7683
|
}
|
|
7502
7684
|
function assertStubGitignoreOwned(cwd) {
|
|
7503
|
-
const file =
|
|
7504
|
-
if (!
|
|
7505
|
-
const firstLine =
|
|
7685
|
+
const file = path3.join(cwd, STUB_GITIGNORE_PATH);
|
|
7686
|
+
if (!fs3.existsSync(file)) return;
|
|
7687
|
+
const firstLine = fs3.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
|
|
7506
7688
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7507
7689
|
throw new Error(
|
|
7508
7690
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7510,8 +7692,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7510
7692
|
}
|
|
7511
7693
|
}
|
|
7512
7694
|
function assertAppRoot(cwd) {
|
|
7513
|
-
const appPath =
|
|
7514
|
-
if (!
|
|
7695
|
+
const appPath = path3.join(cwd, APP_PACKAGE_JSON);
|
|
7696
|
+
if (!fs3.existsSync(appPath)) {
|
|
7515
7697
|
throw new Error(
|
|
7516
7698
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7517
7699
|
);
|
|
@@ -7557,9 +7739,9 @@ ${porcelain.trim()}`
|
|
|
7557
7739
|
}
|
|
7558
7740
|
var APP_SRC_DIR = "apps/web/src";
|
|
7559
7741
|
function walkSources(dir, skip, out) {
|
|
7560
|
-
if (!
|
|
7561
|
-
for (const entry of
|
|
7562
|
-
const full =
|
|
7742
|
+
if (!fs3.existsSync(dir)) return;
|
|
7743
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
7744
|
+
const full = path3.join(dir, entry.name);
|
|
7563
7745
|
if (full === skip) continue;
|
|
7564
7746
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7565
7747
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7568,13 +7750,13 @@ function walkSources(dir, skip, out) {
|
|
|
7568
7750
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7569
7751
|
if (excluded.length === 0) return;
|
|
7570
7752
|
const files = [];
|
|
7571
|
-
walkSources(
|
|
7753
|
+
walkSources(path3.join(cwd, APP_SRC_DIR), path3.join(cwd, MODULES_DIR2), files);
|
|
7572
7754
|
const offenders = [];
|
|
7573
7755
|
for (const file of files) {
|
|
7574
|
-
const source =
|
|
7756
|
+
const source = fs3.readFileSync(file, "utf8");
|
|
7575
7757
|
for (const name of excluded) {
|
|
7576
7758
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7577
|
-
offenders.push(`${
|
|
7759
|
+
offenders.push(`${path3.relative(cwd, file)}: ${name}`);
|
|
7578
7760
|
}
|
|
7579
7761
|
}
|
|
7580
7762
|
}
|
|
@@ -7587,8 +7769,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
|
|
|
7587
7769
|
}
|
|
7588
7770
|
}
|
|
7589
7771
|
function removeDir(absolute) {
|
|
7590
|
-
if (!
|
|
7591
|
-
|
|
7772
|
+
if (!fs3.existsSync(absolute)) return false;
|
|
7773
|
+
fs3.rmSync(absolute, { recursive: true, force: true });
|
|
7592
7774
|
return true;
|
|
7593
7775
|
}
|
|
7594
7776
|
async function compose(options) {
|
|
@@ -7615,6 +7797,16 @@ async function compose(options) {
|
|
|
7615
7797
|
dryRun: true
|
|
7616
7798
|
});
|
|
7617
7799
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
7800
|
+
const violations = findModuleBoundaryViolations(
|
|
7801
|
+
path3.join(options.cwd, MODULES_DIR2),
|
|
7802
|
+
Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
|
|
7803
|
+
{ pruning: true }
|
|
7804
|
+
);
|
|
7805
|
+
if (violations.length > 0) {
|
|
7806
|
+
throw new Error(
|
|
7807
|
+
`[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
|
|
7808
|
+
);
|
|
7809
|
+
}
|
|
7618
7810
|
}
|
|
7619
7811
|
const result = await reconcileCompositionArtifacts(context, {
|
|
7620
7812
|
enabledModulesRaw: options.enabled,
|
|
@@ -7626,11 +7818,11 @@ async function compose(options) {
|
|
|
7626
7818
|
let pruned = 0;
|
|
7627
7819
|
if (options.prune) {
|
|
7628
7820
|
for (const name of result.excludedModules) {
|
|
7629
|
-
if (removeDir(
|
|
7821
|
+
if (removeDir(path3.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7630
7822
|
}
|
|
7631
7823
|
for (const owner of result.composedModules) {
|
|
7632
7824
|
for (const peer of result.excludedModules) {
|
|
7633
|
-
if (removeDir(
|
|
7825
|
+
if (removeDir(path3.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7634
7826
|
}
|
|
7635
7827
|
}
|
|
7636
7828
|
}
|
|
@@ -7648,5 +7840,8 @@ export {
|
|
|
7648
7840
|
assertSupportedTypescript,
|
|
7649
7841
|
compose,
|
|
7650
7842
|
escapeGitignore,
|
|
7843
|
+
findModuleBoundaryViolations,
|
|
7844
|
+
findOptionalPeerViolations,
|
|
7845
|
+
formatBoundaryViolations,
|
|
7651
7846
|
readComposeInputs
|
|
7652
7847
|
};
|