@stardeck-customer-apps/compose 0.8.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 +19 -0
- package/dist/cli.js +378 -221
- package/dist/index.d.mts +42 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.js +367 -218
- package/dist/index.mjs +364 -218
- package/package.json +1 -1
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
|
}
|
|
@@ -3901,12 +3901,13 @@ var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
|
|
|
3901
3901
|
var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
|
|
3902
3902
|
|
|
3903
3903
|
// src/cli.ts
|
|
3904
|
-
var
|
|
3904
|
+
var import_typescript6 = __toESM(require("typescript"));
|
|
3905
|
+
var import_node_path4 = __toESM(require("path"));
|
|
3905
3906
|
|
|
3906
3907
|
// src/index.ts
|
|
3907
3908
|
var import_node_child_process2 = require("child_process");
|
|
3908
|
-
var
|
|
3909
|
-
var
|
|
3909
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
3910
|
+
var import_node_path3 = __toESM(require("path"));
|
|
3910
3911
|
|
|
3911
3912
|
// ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
|
|
3912
3913
|
var import_node_child_process = require("child_process");
|
|
@@ -4262,13 +4263,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4262
4263
|
function createMessageTree() {
|
|
4263
4264
|
return /* @__PURE__ */ Object.create(null);
|
|
4264
4265
|
}
|
|
4265
|
-
function assertMessageTree(value,
|
|
4266
|
+
function assertMessageTree(value, path5) {
|
|
4266
4267
|
if (typeof value === "string") return;
|
|
4267
4268
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4268
|
-
throw new Error(`Invalid message at ${
|
|
4269
|
+
throw new Error(`Invalid message at ${path5}: expected string or nested object`);
|
|
4269
4270
|
}
|
|
4270
4271
|
for (const [key, child] of Object.entries(value)) {
|
|
4271
|
-
assertMessageTree(child, `${
|
|
4272
|
+
assertMessageTree(child, `${path5}.${key}`);
|
|
4272
4273
|
}
|
|
4273
4274
|
}
|
|
4274
4275
|
function cloneTree(value) {
|
|
@@ -4278,17 +4279,17 @@ function cloneTree(value) {
|
|
|
4278
4279
|
}
|
|
4279
4280
|
return clone;
|
|
4280
4281
|
}
|
|
4281
|
-
function collectLeafPaths(value,
|
|
4282
|
+
function collectLeafPaths(value, path5, out) {
|
|
4282
4283
|
if (typeof value === "string") {
|
|
4283
|
-
out.push(
|
|
4284
|
+
out.push(path5);
|
|
4284
4285
|
return;
|
|
4285
4286
|
}
|
|
4286
|
-
assertMessageTree(value,
|
|
4287
|
-
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);
|
|
4288
4289
|
}
|
|
4289
|
-
function overrideInto(target, source, owner,
|
|
4290
|
+
function overrideInto(target, source, owner, path5, missing) {
|
|
4290
4291
|
for (const [key, value] of Object.entries(source)) {
|
|
4291
|
-
const nextPath =
|
|
4292
|
+
const nextPath = path5 ? `${path5}.${key}` : key;
|
|
4292
4293
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4293
4294
|
collectLeafPaths(value, nextPath, missing);
|
|
4294
4295
|
continue;
|
|
@@ -4315,9 +4316,9 @@ function overrideInto(target, source, owner, path3, missing) {
|
|
|
4315
4316
|
function composeLocaleMessagesForPlan(layers) {
|
|
4316
4317
|
const result = createMessageTree();
|
|
4317
4318
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4318
|
-
function mergeInto(target, source, owner,
|
|
4319
|
+
function mergeInto(target, source, owner, path5) {
|
|
4319
4320
|
for (const [key, value] of Object.entries(source)) {
|
|
4320
|
-
const nextPath =
|
|
4321
|
+
const nextPath = path5 ? `${path5}.${key}` : key;
|
|
4321
4322
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4322
4323
|
const existing = hasExisting ? target[key] : void 0;
|
|
4323
4324
|
if (typeof value === "string") {
|
|
@@ -4861,8 +4862,8 @@ var METHOD_ORDER = {
|
|
|
4861
4862
|
function compareNames2(a, b) {
|
|
4862
4863
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4863
4864
|
}
|
|
4864
|
-
function endpointPairKey(method,
|
|
4865
|
-
return `${method} ${
|
|
4865
|
+
function endpointPairKey(method, path5) {
|
|
4866
|
+
return `${method} ${path5}`;
|
|
4866
4867
|
}
|
|
4867
4868
|
function apiPathToRouteFile(apiPath) {
|
|
4868
4869
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4876,11 +4877,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4876
4877
|
}
|
|
4877
4878
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4878
4879
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4879
|
-
function appRouteFileToNextRelativePath(
|
|
4880
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
4880
|
+
function appRouteFileToNextRelativePath(path5) {
|
|
4881
|
+
const pagePath = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
|
|
4881
4882
|
if (pagePath != null) return pagePath;
|
|
4882
|
-
if (!
|
|
4883
|
-
const relative =
|
|
4883
|
+
if (!path5.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
4884
|
+
const relative = path5.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4884
4885
|
if (!relative.startsWith("api/")) return null;
|
|
4885
4886
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4886
4887
|
if (!suffix) return null;
|
|
@@ -4972,9 +4973,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4972
4973
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4973
4974
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4974
4975
|
}
|
|
4975
|
-
function parseLegacyApiAdapter(
|
|
4976
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4977
|
-
if (expectedMarkerKindForPath(
|
|
4976
|
+
function parseLegacyApiAdapter(path5, source) {
|
|
4977
|
+
const physicalPath = appRouteFileToNextRelativePath(path5);
|
|
4978
|
+
if (expectedMarkerKindForPath(path5) !== "endpoint" || physicalPath == null) return null;
|
|
4978
4979
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4979
4980
|
const match = firstLine.match(
|
|
4980
4981
|
new RegExp(
|
|
@@ -4984,7 +4985,7 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4984
4985
|
if (!match || match[2] !== physicalPath) return null;
|
|
4985
4986
|
const owner = match[1];
|
|
4986
4987
|
if (!isValidModuleName(owner)) return null;
|
|
4987
|
-
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);
|
|
4988
4989
|
if (file.parseDiagnostics.length > 0) {
|
|
4989
4990
|
return null;
|
|
4990
4991
|
}
|
|
@@ -5026,9 +5027,9 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
5026
5027
|
}
|
|
5027
5028
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
5028
5029
|
}
|
|
5029
|
-
function parseLegacyRouteAdapter(
|
|
5030
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
5031
|
-
if (expectedMarkerKindForPath(
|
|
5030
|
+
function parseLegacyRouteAdapter(path5, source) {
|
|
5031
|
+
const physicalPath = appRouteFileToNextRelativePath(path5);
|
|
5032
|
+
if (expectedMarkerKindForPath(path5) !== "route" || physicalPath == null) return null;
|
|
5032
5033
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
5033
5034
|
const match = normalized.match(
|
|
5034
5035
|
new RegExp(
|
|
@@ -5040,30 +5041,30 @@ function parseLegacyRouteAdapter(path3, source) {
|
|
|
5040
5041
|
const nextRelativePath = match[2];
|
|
5041
5042
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
5042
5043
|
}
|
|
5043
|
-
function legacyRouteAdapterMatchesStub(
|
|
5044
|
-
const legacy = parseLegacyRouteAdapter(
|
|
5044
|
+
function legacyRouteAdapterMatchesStub(path5, source, stub) {
|
|
5045
|
+
const legacy = parseLegacyRouteAdapter(path5, source);
|
|
5045
5046
|
return legacy != null && legacy.owner === stub.owner;
|
|
5046
5047
|
}
|
|
5047
5048
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
5048
5049
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
5049
5050
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
5050
|
-
function expectedMarkerKindForPath(
|
|
5051
|
-
if (
|
|
5052
|
-
if (
|
|
5053
|
-
if (
|
|
5054
|
-
if (
|
|
5055
|
-
if (
|
|
5056
|
-
if (
|
|
5057
|
-
if (
|
|
5058
|
-
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))) {
|
|
5059
5060
|
return "route";
|
|
5060
5061
|
}
|
|
5061
5062
|
return null;
|
|
5062
5063
|
}
|
|
5063
|
-
function markerCongruentWithPath(
|
|
5064
|
-
if (expectedMarkerKindForPath(
|
|
5064
|
+
function markerCongruentWithPath(path5, marker) {
|
|
5065
|
+
if (expectedMarkerKindForPath(path5) !== marker.kind) return false;
|
|
5065
5066
|
if (marker.kind === "route") {
|
|
5066
|
-
const derived = appPageFileToNextRelativePath(
|
|
5067
|
+
const derived = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
|
|
5067
5068
|
return derived != null && derived === marker.nextRelativePath;
|
|
5068
5069
|
}
|
|
5069
5070
|
return true;
|
|
@@ -5334,8 +5335,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5334
5335
|
function activeEnhancementPeers(registryModule) {
|
|
5335
5336
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5336
5337
|
}
|
|
5337
|
-
function endpointBucketKey(
|
|
5338
|
-
return `${
|
|
5338
|
+
function endpointBucketKey(path5, owner, scope) {
|
|
5339
|
+
return `${path5}\0${owner}\0${scopeLabel(scope)}`;
|
|
5339
5340
|
}
|
|
5340
5341
|
function planRouteStubs(input) {
|
|
5341
5342
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5459,7 +5460,7 @@ function planRouteStubs(input) {
|
|
|
5459
5460
|
for (const entry of entries) {
|
|
5460
5461
|
const nextRelativePath = entry.nextRelativePath;
|
|
5461
5462
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5462
|
-
const
|
|
5463
|
+
const path5 = routeEntryToAppPageFile(nextRelativePath);
|
|
5463
5464
|
const existing = routeDestinationOwners.get(destination);
|
|
5464
5465
|
if (existing) {
|
|
5465
5466
|
throw new Error(
|
|
@@ -5469,13 +5470,13 @@ function planRouteStubs(input) {
|
|
|
5469
5470
|
routeDestinationOwners.set(destination, {
|
|
5470
5471
|
owner: regMod.name,
|
|
5471
5472
|
nextRelativePath,
|
|
5472
|
-
path:
|
|
5473
|
+
path: path5
|
|
5473
5474
|
});
|
|
5474
5475
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5475
5476
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5476
5477
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5477
5478
|
routeStubs.push({
|
|
5478
|
-
path:
|
|
5479
|
+
path: path5,
|
|
5479
5480
|
owner: regMod.name,
|
|
5480
5481
|
nextRelativePath,
|
|
5481
5482
|
importModule,
|
|
@@ -5536,16 +5537,16 @@ function planCompositionArtifacts(input) {
|
|
|
5536
5537
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5537
5538
|
]);
|
|
5538
5539
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5539
|
-
const isProvenStaleDeletionCandidate = (
|
|
5540
|
-
if (
|
|
5541
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5542
|
-
if (desiredPaths.has(
|
|
5543
|
-
if (!existingGeneratedPathSet.has(
|
|
5544
|
-
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);
|
|
5545
5546
|
if (legacy) return true;
|
|
5546
|
-
if (parseLegacyRouteAdapter(
|
|
5547
|
+
if (parseLegacyRouteAdapter(path5, content)) return true;
|
|
5547
5548
|
const marker = parseCompositionArtifactMarker(content);
|
|
5548
|
-
return marker != null && markerCongruentWithPath(
|
|
5549
|
+
return marker != null && markerCongruentWithPath(path5, marker);
|
|
5549
5550
|
};
|
|
5550
5551
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5551
5552
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5699,13 +5700,13 @@ function planCompositionArtifacts(input) {
|
|
|
5699
5700
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5700
5701
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5701
5702
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5702
|
-
for (const [
|
|
5703
|
-
const existing = input.existingFiles[
|
|
5703
|
+
for (const [path5] of desired) {
|
|
5704
|
+
const existing = input.existingFiles[path5];
|
|
5704
5705
|
if (existing == null) continue;
|
|
5705
5706
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5706
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5707
|
-
const plannedRoute = routeStubByPath.get(
|
|
5708
|
-
const legacy = parseLegacyApiAdapter(
|
|
5707
|
+
const plannedEndpoint = endpointStubByPath.get(path5);
|
|
5708
|
+
const plannedRoute = routeStubByPath.get(path5);
|
|
5709
|
+
const legacy = parseLegacyApiAdapter(path5, existing);
|
|
5709
5710
|
if (legacy && plannedEndpoint) {
|
|
5710
5711
|
marker = {
|
|
5711
5712
|
kind: "endpoint",
|
|
@@ -5713,66 +5714,66 @@ function planCompositionArtifacts(input) {
|
|
|
5713
5714
|
scope: plannedEndpoint.scope
|
|
5714
5715
|
};
|
|
5715
5716
|
}
|
|
5716
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5717
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path5, existing, plannedRoute)) {
|
|
5717
5718
|
marker = {
|
|
5718
5719
|
kind: "route",
|
|
5719
5720
|
owner: plannedRoute.owner,
|
|
5720
5721
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5721
5722
|
};
|
|
5722
5723
|
}
|
|
5723
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5724
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path5);
|
|
5724
5725
|
if (!marker) {
|
|
5725
5726
|
return {
|
|
5726
5727
|
ok: false,
|
|
5727
|
-
error:
|
|
5728
|
-
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
|
|
5729
5730
|
};
|
|
5730
5731
|
}
|
|
5731
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5732
|
+
const expectedKind = expectedMarkerKindForPath(path5);
|
|
5732
5733
|
if (marker.kind !== expectedKind) {
|
|
5733
5734
|
return {
|
|
5734
5735
|
ok: false,
|
|
5735
|
-
error:
|
|
5736
|
-
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
|
|
5737
5738
|
};
|
|
5738
5739
|
}
|
|
5739
5740
|
if (marker.kind === "endpoint") {
|
|
5740
|
-
const stub = endpointStubByPath.get(
|
|
5741
|
+
const stub = endpointStubByPath.get(path5);
|
|
5741
5742
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5742
5743
|
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5743
5744
|
if (!ownerMatches || !scopeMatches) {
|
|
5744
5745
|
return {
|
|
5745
5746
|
ok: false,
|
|
5746
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5747
|
-
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,
|
|
5748
5749
|
owner: stub?.owner
|
|
5749
5750
|
};
|
|
5750
5751
|
}
|
|
5751
5752
|
} else if (marker.kind === "route") {
|
|
5752
|
-
const stub = routeStubByPath.get(
|
|
5753
|
+
const stub = routeStubByPath.get(path5);
|
|
5753
5754
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5754
5755
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5755
5756
|
if (!ownerMatches || !pathMatches) {
|
|
5756
5757
|
return {
|
|
5757
5758
|
ok: false,
|
|
5758
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5759
|
-
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,
|
|
5760
5761
|
owner: stub?.owner
|
|
5761
5762
|
};
|
|
5762
5763
|
}
|
|
5763
5764
|
}
|
|
5764
5765
|
}
|
|
5765
5766
|
const deletes = [];
|
|
5766
|
-
for (const
|
|
5767
|
-
const existing = input.existingFiles[
|
|
5767
|
+
for (const path5 of input.existingGeneratedPaths) {
|
|
5768
|
+
const existing = input.existingFiles[path5];
|
|
5768
5769
|
if (existing == null) continue;
|
|
5769
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5770
|
+
if (!isProvenStaleDeletionCandidate(path5, existing)) {
|
|
5770
5771
|
continue;
|
|
5771
5772
|
}
|
|
5772
|
-
deletes.push(
|
|
5773
|
+
deletes.push(path5);
|
|
5773
5774
|
}
|
|
5774
5775
|
deletes.sort(compareNames2);
|
|
5775
|
-
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));
|
|
5776
5777
|
return {
|
|
5777
5778
|
ok: true,
|
|
5778
5779
|
plan: {
|
|
@@ -5801,8 +5802,16 @@ async function checkoutParsesTypedStores(sandbox) {
|
|
|
5801
5802
|
try {
|
|
5802
5803
|
if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
|
|
5803
5804
|
const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
|
|
5804
|
-
|
|
5805
|
-
|
|
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 {
|
|
5806
5815
|
const lowest = import_semver.default.minVersion(range);
|
|
5807
5816
|
return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
|
|
5808
5817
|
} catch {
|
|
@@ -6247,11 +6256,11 @@ function countedCompositionContext(context) {
|
|
|
6247
6256
|
counts.sandboxExecs += 1;
|
|
6248
6257
|
return context.sandbox.exec(...args);
|
|
6249
6258
|
},
|
|
6250
|
-
readFile: (
|
|
6259
|
+
readFile: (path5) => {
|
|
6251
6260
|
counts.sandboxReads += 1;
|
|
6252
|
-
return context.sandbox.readFile(
|
|
6261
|
+
return context.sandbox.readFile(path5);
|
|
6253
6262
|
},
|
|
6254
|
-
fileExists: (
|
|
6263
|
+
fileExists: (path5) => context.sandbox.fileExists(path5),
|
|
6255
6264
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6256
6265
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6257
6266
|
}
|
|
@@ -6463,14 +6472,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6463
6472
|
}
|
|
6464
6473
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6465
6474
|
}
|
|
6466
|
-
async function writeSandboxFileAtomic(target,
|
|
6467
|
-
const dir = dirnamePosix(
|
|
6468
|
-
const tmp = `${
|
|
6475
|
+
async function writeSandboxFileAtomic(target, path5, content) {
|
|
6476
|
+
const dir = dirnamePosix(path5);
|
|
6477
|
+
const tmp = `${path5}.tmp.${target.runId}`;
|
|
6469
6478
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6470
6479
|
const cmd = [
|
|
6471
6480
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6472
6481
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6473
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6482
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path5)}`
|
|
6474
6483
|
].join(" && ");
|
|
6475
6484
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6476
6485
|
if (result.exitCode !== 0) {
|
|
@@ -6485,14 +6494,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
|
|
|
6485
6494
|
}
|
|
6486
6495
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6487
6496
|
throw new Error(
|
|
6488
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6497
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path5)}: ${redactSecrets(detail)}`
|
|
6489
6498
|
);
|
|
6490
6499
|
}
|
|
6491
6500
|
}
|
|
6492
6501
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6493
6502
|
const absent = /* @__PURE__ */ new Set();
|
|
6494
|
-
for (const
|
|
6495
|
-
let dir = dirnamePosix(
|
|
6503
|
+
for (const path5 of writePaths) {
|
|
6504
|
+
let dir = dirnamePosix(path5);
|
|
6496
6505
|
while (dir !== "." && dir !== "") {
|
|
6497
6506
|
if (absent.has(dir)) {
|
|
6498
6507
|
dir = dirnamePosix(dir);
|
|
@@ -6529,12 +6538,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6529
6538
|
);
|
|
6530
6539
|
}
|
|
6531
6540
|
}
|
|
6532
|
-
async function deleteSandboxFile(context,
|
|
6533
|
-
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 });
|
|
6534
6543
|
if (result.exitCode !== 0) {
|
|
6535
6544
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6536
6545
|
throw new Error(
|
|
6537
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6546
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path5)}: ${redactSecrets(detail)}`
|
|
6538
6547
|
);
|
|
6539
6548
|
}
|
|
6540
6549
|
}
|
|
@@ -6601,37 +6610,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6601
6610
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6602
6611
|
appliedWrites.push({ path: write.path, previous });
|
|
6603
6612
|
}
|
|
6604
|
-
for (const
|
|
6605
|
-
const previous = existingFiles[
|
|
6613
|
+
for (const path5 of plan.deletes) {
|
|
6614
|
+
const previous = existingFiles[path5];
|
|
6606
6615
|
if (previous == null) continue;
|
|
6607
|
-
await deleteSandboxFile(context,
|
|
6608
|
-
appliedDeletes.push({ path:
|
|
6616
|
+
await deleteSandboxFile(context, path5);
|
|
6617
|
+
appliedDeletes.push({ path: path5, previous });
|
|
6609
6618
|
}
|
|
6610
6619
|
} catch (error) {
|
|
6611
6620
|
console.log(
|
|
6612
6621
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6613
6622
|
);
|
|
6614
|
-
for (const { path:
|
|
6623
|
+
for (const { path: path5, previous } of [...appliedDeletes].reverse()) {
|
|
6615
6624
|
try {
|
|
6616
|
-
await writeSandboxFileAtomic(context,
|
|
6625
|
+
await writeSandboxFileAtomic(context, path5, previous);
|
|
6617
6626
|
} catch (rollbackError) {
|
|
6618
6627
|
console.log(
|
|
6619
|
-
`[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(
|
|
6620
6629
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6621
6630
|
)}`
|
|
6622
6631
|
);
|
|
6623
6632
|
}
|
|
6624
6633
|
}
|
|
6625
|
-
for (const { path:
|
|
6634
|
+
for (const { path: path5, previous } of [...appliedWrites].reverse()) {
|
|
6626
6635
|
try {
|
|
6627
6636
|
if (previous == null) {
|
|
6628
|
-
await deleteSandboxFile(context,
|
|
6637
|
+
await deleteSandboxFile(context, path5);
|
|
6629
6638
|
} else {
|
|
6630
|
-
await writeSandboxFileAtomic(context,
|
|
6639
|
+
await writeSandboxFileAtomic(context, path5, previous);
|
|
6631
6640
|
}
|
|
6632
6641
|
} catch (rollbackError) {
|
|
6633
6642
|
console.log(
|
|
6634
|
-
`[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(
|
|
6635
6644
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6636
6645
|
)}`
|
|
6637
6646
|
);
|
|
@@ -6665,39 +6674,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6665
6674
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6666
6675
|
);
|
|
6667
6676
|
const presentPaths = [];
|
|
6668
|
-
for (const
|
|
6677
|
+
for (const path5 of candidates) {
|
|
6669
6678
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6670
|
-
|
|
6671
|
-
kinds.get(
|
|
6679
|
+
path5,
|
|
6680
|
+
kinds.get(path5) ?? "unreadable"
|
|
6672
6681
|
);
|
|
6673
|
-
if (presence === "file") presentPaths.push(
|
|
6682
|
+
if (presence === "file") presentPaths.push(path5);
|
|
6674
6683
|
}
|
|
6675
6684
|
let contents;
|
|
6676
6685
|
try {
|
|
6677
6686
|
contents = await readFilesMany(context, presentPaths);
|
|
6678
6687
|
} catch (error) {
|
|
6679
|
-
const
|
|
6688
|
+
const path5 = readFailurePath(error);
|
|
6680
6689
|
throw new Error(
|
|
6681
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6690
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path5)}: ${redactSecrets(
|
|
6682
6691
|
error instanceof Error ? error.message : String(error)
|
|
6683
6692
|
)}`
|
|
6684
6693
|
);
|
|
6685
6694
|
}
|
|
6686
6695
|
const owned = [];
|
|
6687
|
-
for (const
|
|
6688
|
-
const content = contents.get(
|
|
6696
|
+
for (const path5 of presentPaths) {
|
|
6697
|
+
const content = contents.get(path5);
|
|
6689
6698
|
const marker = parseCompositionArtifactMarker(content);
|
|
6690
|
-
if (parseLegacyApiAdapter(
|
|
6691
|
-
owned.push(
|
|
6699
|
+
if (parseLegacyApiAdapter(path5, content) || parseLegacyRouteAdapter(path5, content)) {
|
|
6700
|
+
owned.push(path5);
|
|
6692
6701
|
continue;
|
|
6693
6702
|
}
|
|
6694
6703
|
if (marker == null) continue;
|
|
6695
|
-
if (!markerCongruentWithPath(
|
|
6704
|
+
if (!markerCongruentWithPath(path5, marker)) {
|
|
6696
6705
|
throw new Error(
|
|
6697
|
-
`[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)`
|
|
6698
6707
|
);
|
|
6699
6708
|
}
|
|
6700
|
-
owned.push(
|
|
6709
|
+
owned.push(path5);
|
|
6701
6710
|
}
|
|
6702
6711
|
return owned;
|
|
6703
6712
|
}
|
|
@@ -6830,7 +6839,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6830
6839
|
{ raiseOnError: false }
|
|
6831
6840
|
);
|
|
6832
6841
|
if (result.exitCode !== 0) {
|
|
6833
|
-
for (const
|
|
6842
|
+
for (const path5 of chunk) kinds.set(path5, "unreadable");
|
|
6834
6843
|
continue;
|
|
6835
6844
|
}
|
|
6836
6845
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6841,31 +6850,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6841
6850
|
}
|
|
6842
6851
|
} else {
|
|
6843
6852
|
for (const line of lines) {
|
|
6844
|
-
const [rawKind,
|
|
6853
|
+
const [rawKind, path5] = line.split(" ", 2);
|
|
6845
6854
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6846
|
-
if (
|
|
6847
|
-
kinds.set(
|
|
6855
|
+
if (path5 && chunk.includes(path5) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
6856
|
+
kinds.set(path5, kind);
|
|
6848
6857
|
}
|
|
6849
6858
|
}
|
|
6850
6859
|
}
|
|
6851
|
-
for (const
|
|
6852
|
-
if (!kinds.has(
|
|
6860
|
+
for (const path5 of chunk) {
|
|
6861
|
+
if (!kinds.has(path5)) kinds.set(path5, "unreadable");
|
|
6853
6862
|
}
|
|
6854
6863
|
}
|
|
6855
6864
|
return kinds;
|
|
6856
6865
|
}
|
|
6857
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6858
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
6866
|
+
async function classifySandboxPathPresenceKind(context, path5, probeToken) {
|
|
6867
|
+
return (await classifySandboxPathPresenceKindMany(context, [path5], probeToken)).get(path5);
|
|
6859
6868
|
}
|
|
6860
6869
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6861
|
-
function assertRealLocaleJsonFilePresence(
|
|
6870
|
+
function assertRealLocaleJsonFilePresence(path5, presence) {
|
|
6862
6871
|
if (presence === "file") return;
|
|
6863
6872
|
if (presence === "absent") {
|
|
6864
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
6873
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path5}`);
|
|
6865
6874
|
}
|
|
6866
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6875
|
+
const presenceError = formatConventionalEntryPresenceError(path5, presence);
|
|
6867
6876
|
throw new Error(
|
|
6868
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6877
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real locale JSON file`}`
|
|
6869
6878
|
);
|
|
6870
6879
|
}
|
|
6871
6880
|
function splitTaggedLine(line) {
|
|
@@ -6940,11 +6949,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6940
6949
|
}
|
|
6941
6950
|
async function readFilesMany(context, paths) {
|
|
6942
6951
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6943
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
6952
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path5) => {
|
|
6944
6953
|
try {
|
|
6945
|
-
return { path:
|
|
6954
|
+
return { path: path5, value: await context.sandbox.readFile(path5) };
|
|
6946
6955
|
} catch (error) {
|
|
6947
|
-
return { path:
|
|
6956
|
+
return { path: path5, error };
|
|
6948
6957
|
}
|
|
6949
6958
|
});
|
|
6950
6959
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6967,58 +6976,58 @@ function readFailurePath(error) {
|
|
|
6967
6976
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6968
6977
|
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6969
6978
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6970
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6971
|
-
return classifySandboxPathPresenceKind(context,
|
|
6979
|
+
async function classifyCompositionArtifactPathKind(context, path5) {
|
|
6980
|
+
return classifySandboxPathPresenceKind(context, path5, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6972
6981
|
}
|
|
6973
|
-
function assertRealCompositionArtifactFilePresence(
|
|
6982
|
+
function assertRealCompositionArtifactFilePresence(path5, presence) {
|
|
6974
6983
|
if (presence === "file") return "file";
|
|
6975
6984
|
if (presence === "absent") return "absent";
|
|
6976
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6985
|
+
const presenceError = formatConventionalEntryPresenceError(path5, presence);
|
|
6977
6986
|
throw new Error(
|
|
6978
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6987
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real composition artifact file`}`
|
|
6979
6988
|
);
|
|
6980
6989
|
}
|
|
6981
|
-
async function readConventionalEntryIfRealFile(
|
|
6982
|
-
const presence = presences.get(
|
|
6983
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6990
|
+
async function readConventionalEntryIfRealFile(path5, kind, presences, files) {
|
|
6991
|
+
const presence = presences.get(path5) ?? "unreadable";
|
|
6992
|
+
const presenceError = formatConventionalEntryPresenceError(path5, presence);
|
|
6984
6993
|
if (presenceError) {
|
|
6985
6994
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6986
6995
|
}
|
|
6987
6996
|
if (presence === "absent") {
|
|
6988
6997
|
return null;
|
|
6989
6998
|
}
|
|
6990
|
-
const source = files.get(
|
|
6999
|
+
const source = files.get(path5);
|
|
6991
7000
|
switch (kind) {
|
|
6992
7001
|
case "contributions":
|
|
6993
|
-
assertContributionsExport(source,
|
|
6994
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7002
|
+
assertContributionsExport(source, path5);
|
|
7003
|
+
assertClientSafeConventionalEntryImports(source, path5, "contributions");
|
|
6995
7004
|
break;
|
|
6996
7005
|
case "slotCatalogs":
|
|
6997
|
-
assertSlotCatalogsExport(source,
|
|
6998
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7006
|
+
assertSlotCatalogsExport(source, path5);
|
|
7007
|
+
assertClientSafeConventionalEntryImports(source, path5, "slotCatalogs");
|
|
6999
7008
|
break;
|
|
7000
7009
|
case "initializeEnhancement":
|
|
7001
|
-
assertInitializeEnhancementExport(source,
|
|
7010
|
+
assertInitializeEnhancementExport(source, path5);
|
|
7002
7011
|
break;
|
|
7003
7012
|
}
|
|
7004
7013
|
return source;
|
|
7005
7014
|
}
|
|
7006
7015
|
function hasRealServerRegistrations(modulePath, presences) {
|
|
7007
|
-
const
|
|
7008
|
-
return assertRealCompositionArtifactFilePresence(
|
|
7016
|
+
const path5 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
7017
|
+
return assertRealCompositionArtifactFilePresence(path5, presences.get(path5) ?? "unreadable") === "file";
|
|
7009
7018
|
}
|
|
7010
|
-
async function readJsonObjectFile(
|
|
7011
|
-
const raw = files.get(
|
|
7019
|
+
async function readJsonObjectFile(path5, files) {
|
|
7020
|
+
const raw = files.get(path5);
|
|
7012
7021
|
let parsed;
|
|
7013
7022
|
try {
|
|
7014
7023
|
parsed = JSON.parse(raw);
|
|
7015
7024
|
} catch (error) {
|
|
7016
7025
|
throw new Error(
|
|
7017
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
7026
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path5}: ${error instanceof Error ? error.message : String(error)}`
|
|
7018
7027
|
);
|
|
7019
7028
|
}
|
|
7020
7029
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
7021
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
7030
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path5} root must be an object`);
|
|
7022
7031
|
}
|
|
7023
7032
|
return parsed;
|
|
7024
7033
|
}
|
|
@@ -7067,11 +7076,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7067
7076
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7068
7077
|
]);
|
|
7069
7078
|
const presentConventionalPaths = [];
|
|
7070
|
-
for (const
|
|
7071
|
-
const presence = conventionalPresences.get(
|
|
7072
|
-
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);
|
|
7073
7082
|
}
|
|
7074
|
-
const presentLocalePaths = localePaths.filter((
|
|
7083
|
+
const presentLocalePaths = localePaths.filter((path5) => localePresences.get(path5) === "file");
|
|
7075
7084
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7076
7085
|
const substrateLocales = [];
|
|
7077
7086
|
for (const locale of substrateNames) {
|
|
@@ -7169,8 +7178,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7169
7178
|
});
|
|
7170
7179
|
if (rootLocales.length > 0) {
|
|
7171
7180
|
for (const locale of rootLocales) {
|
|
7172
|
-
const
|
|
7173
|
-
assertRealLocaleJsonFilePresence(
|
|
7181
|
+
const path5 = `${modulePath}/i18n/${locale}.json`;
|
|
7182
|
+
assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
|
|
7174
7183
|
}
|
|
7175
7184
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7176
7185
|
const localeMessages = { en: enMessages };
|
|
@@ -7194,8 +7203,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7194
7203
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7195
7204
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7196
7205
|
for (const locale of enh.locales) {
|
|
7197
|
-
const
|
|
7198
|
-
assertRealLocaleJsonFilePresence(
|
|
7206
|
+
const path5 = `${peerDir}/i18n/${locale}.json`;
|
|
7207
|
+
assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
|
|
7199
7208
|
}
|
|
7200
7209
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7201
7210
|
const localeMessages = { en: enMessages };
|
|
@@ -7396,16 +7405,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7396
7405
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7397
7406
|
const existingFiles = {};
|
|
7398
7407
|
const remainingCandidatePaths = [];
|
|
7399
|
-
for (const
|
|
7400
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7401
|
-
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];
|
|
7402
7411
|
continue;
|
|
7403
7412
|
}
|
|
7404
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7405
|
-
existingFiles[
|
|
7413
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path5)) {
|
|
7414
|
+
existingFiles[path5] = existingAppRoutes[path5];
|
|
7406
7415
|
continue;
|
|
7407
7416
|
}
|
|
7408
|
-
remainingCandidatePaths.push(
|
|
7417
|
+
remainingCandidatePaths.push(path5);
|
|
7409
7418
|
}
|
|
7410
7419
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7411
7420
|
context,
|
|
@@ -7413,12 +7422,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7413
7422
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7414
7423
|
);
|
|
7415
7424
|
const presentCandidatePaths = [];
|
|
7416
|
-
for (const
|
|
7425
|
+
for (const path5 of remainingCandidatePaths) {
|
|
7417
7426
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7418
|
-
|
|
7419
|
-
candidateKinds.get(
|
|
7427
|
+
path5,
|
|
7428
|
+
candidateKinds.get(path5) ?? "unreadable"
|
|
7420
7429
|
);
|
|
7421
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7430
|
+
if (presence === "file") presentCandidatePaths.push(path5);
|
|
7422
7431
|
}
|
|
7423
7432
|
try {
|
|
7424
7433
|
Object.assign(
|
|
@@ -7426,9 +7435,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7426
7435
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7427
7436
|
);
|
|
7428
7437
|
} catch (error) {
|
|
7429
|
-
const
|
|
7438
|
+
const path5 = readFailurePath(error);
|
|
7430
7439
|
throw new Error(
|
|
7431
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7440
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path5)} during preflight: ${redactSecrets(
|
|
7432
7441
|
error instanceof Error ? error.message : String(error)
|
|
7433
7442
|
)}`
|
|
7434
7443
|
);
|
|
@@ -7497,14 +7506,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7497
7506
|
var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
7498
7507
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
7499
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
|
+
|
|
7500
7634
|
// src/index.ts
|
|
7501
7635
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7502
7636
|
function readComposeInputs(cwd) {
|
|
7503
|
-
const file =
|
|
7504
|
-
if (!
|
|
7637
|
+
const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7638
|
+
if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7505
7639
|
let raw;
|
|
7506
7640
|
try {
|
|
7507
|
-
raw = JSON.parse(
|
|
7641
|
+
raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
|
|
7508
7642
|
} catch (error) {
|
|
7509
7643
|
throw new Error(
|
|
7510
7644
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7546,18 +7680,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7546
7680
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7547
7681
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7548
7682
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7549
|
-
const file =
|
|
7550
|
-
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;
|
|
7551
7685
|
if (existing !== content) {
|
|
7552
|
-
|
|
7553
|
-
|
|
7686
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
|
|
7687
|
+
import_node_fs3.default.writeFileSync(file, content);
|
|
7554
7688
|
}
|
|
7555
7689
|
return entries.length;
|
|
7556
7690
|
}
|
|
7557
7691
|
function assertStubGitignoreOwned(cwd) {
|
|
7558
|
-
const file =
|
|
7559
|
-
if (!
|
|
7560
|
-
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] ?? "";
|
|
7561
7695
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7562
7696
|
throw new Error(
|
|
7563
7697
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7565,8 +7699,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7565
7699
|
}
|
|
7566
7700
|
}
|
|
7567
7701
|
function assertAppRoot(cwd) {
|
|
7568
|
-
const appPath =
|
|
7569
|
-
if (!
|
|
7702
|
+
const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
|
|
7703
|
+
if (!import_node_fs3.default.existsSync(appPath)) {
|
|
7570
7704
|
throw new Error(
|
|
7571
7705
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7572
7706
|
);
|
|
@@ -7612,9 +7746,9 @@ ${porcelain.trim()}`
|
|
|
7612
7746
|
}
|
|
7613
7747
|
var APP_SRC_DIR = "apps/web/src";
|
|
7614
7748
|
function walkSources(dir, skip, out) {
|
|
7615
|
-
if (!
|
|
7616
|
-
for (const entry of
|
|
7617
|
-
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);
|
|
7618
7752
|
if (full === skip) continue;
|
|
7619
7753
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7620
7754
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7623,13 +7757,13 @@ function walkSources(dir, skip, out) {
|
|
|
7623
7757
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7624
7758
|
if (excluded.length === 0) return;
|
|
7625
7759
|
const files = [];
|
|
7626
|
-
walkSources(
|
|
7760
|
+
walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
|
|
7627
7761
|
const offenders = [];
|
|
7628
7762
|
for (const file of files) {
|
|
7629
|
-
const source =
|
|
7763
|
+
const source = import_node_fs3.default.readFileSync(file, "utf8");
|
|
7630
7764
|
for (const name of excluded) {
|
|
7631
7765
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7632
|
-
offenders.push(`${
|
|
7766
|
+
offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
|
|
7633
7767
|
}
|
|
7634
7768
|
}
|
|
7635
7769
|
}
|
|
@@ -7642,8 +7776,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
|
|
|
7642
7776
|
}
|
|
7643
7777
|
}
|
|
7644
7778
|
function removeDir(absolute) {
|
|
7645
|
-
if (!
|
|
7646
|
-
|
|
7779
|
+
if (!import_node_fs3.default.existsSync(absolute)) return false;
|
|
7780
|
+
import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
|
|
7647
7781
|
return true;
|
|
7648
7782
|
}
|
|
7649
7783
|
async function compose(options) {
|
|
@@ -7670,6 +7804,16 @@ async function compose(options) {
|
|
|
7670
7804
|
dryRun: true
|
|
7671
7805
|
});
|
|
7672
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
|
+
}
|
|
7673
7817
|
}
|
|
7674
7818
|
const result = await reconcileCompositionArtifacts(context, {
|
|
7675
7819
|
enabledModulesRaw: options.enabled,
|
|
@@ -7681,11 +7825,11 @@ async function compose(options) {
|
|
|
7681
7825
|
let pruned = 0;
|
|
7682
7826
|
if (options.prune) {
|
|
7683
7827
|
for (const name of result.excludedModules) {
|
|
7684
|
-
if (removeDir(
|
|
7828
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7685
7829
|
}
|
|
7686
7830
|
for (const owner of result.composedModules) {
|
|
7687
7831
|
for (const peer of result.excludedModules) {
|
|
7688
|
-
if (removeDir(
|
|
7832
|
+
if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7689
7833
|
}
|
|
7690
7834
|
}
|
|
7691
7835
|
}
|
|
@@ -7701,15 +7845,28 @@ async function compose(options) {
|
|
|
7701
7845
|
|
|
7702
7846
|
// src/cli.ts
|
|
7703
7847
|
async function main() {
|
|
7704
|
-
assertSupportedTypescript(
|
|
7848
|
+
assertSupportedTypescript(import_typescript6.default.versionMajorMinor);
|
|
7705
7849
|
const { values } = (0, import_node_util.parseArgs)({
|
|
7706
7850
|
options: {
|
|
7707
7851
|
enabled: { type: "string" },
|
|
7708
7852
|
prune: { type: "boolean", default: false },
|
|
7709
|
-
cwd: { type: "string" }
|
|
7853
|
+
cwd: { type: "string" },
|
|
7854
|
+
"check-boundaries": { type: "boolean", default: false }
|
|
7710
7855
|
},
|
|
7711
7856
|
strict: true
|
|
7712
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
|
+
}
|
|
7713
7870
|
const enabled = values.enabled ?? process.env[ENABLED_MODULES_ENV];
|
|
7714
7871
|
if (values.prune && !enabled) {
|
|
7715
7872
|
throw new Error(
|