@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/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
|
}
|
|
@@ -4256,13 +4256,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4256
4256
|
function createMessageTree() {
|
|
4257
4257
|
return /* @__PURE__ */ Object.create(null);
|
|
4258
4258
|
}
|
|
4259
|
-
function assertMessageTree(value,
|
|
4259
|
+
function assertMessageTree(value, path4) {
|
|
4260
4260
|
if (typeof value === "string") return;
|
|
4261
4261
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4262
|
-
throw new Error(`Invalid message at ${
|
|
4262
|
+
throw new Error(`Invalid message at ${path4}: expected string or nested object`);
|
|
4263
4263
|
}
|
|
4264
4264
|
for (const [key, child] of Object.entries(value)) {
|
|
4265
|
-
assertMessageTree(child, `${
|
|
4265
|
+
assertMessageTree(child, `${path4}.${key}`);
|
|
4266
4266
|
}
|
|
4267
4267
|
}
|
|
4268
4268
|
function cloneTree(value) {
|
|
@@ -4272,17 +4272,17 @@ function cloneTree(value) {
|
|
|
4272
4272
|
}
|
|
4273
4273
|
return clone;
|
|
4274
4274
|
}
|
|
4275
|
-
function collectLeafPaths(value,
|
|
4275
|
+
function collectLeafPaths(value, path4, out) {
|
|
4276
4276
|
if (typeof value === "string") {
|
|
4277
|
-
out.push(
|
|
4277
|
+
out.push(path4);
|
|
4278
4278
|
return;
|
|
4279
4279
|
}
|
|
4280
|
-
assertMessageTree(value,
|
|
4281
|
-
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);
|
|
4282
4282
|
}
|
|
4283
|
-
function overrideInto(target, source, owner,
|
|
4283
|
+
function overrideInto(target, source, owner, path4, missing) {
|
|
4284
4284
|
for (const [key, value] of Object.entries(source)) {
|
|
4285
|
-
const nextPath =
|
|
4285
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4286
4286
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4287
4287
|
collectLeafPaths(value, nextPath, missing);
|
|
4288
4288
|
continue;
|
|
@@ -4309,9 +4309,9 @@ function overrideInto(target, source, owner, path3, missing) {
|
|
|
4309
4309
|
function composeLocaleMessagesForPlan(layers) {
|
|
4310
4310
|
const result = createMessageTree();
|
|
4311
4311
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4312
|
-
function mergeInto(target, source, owner,
|
|
4312
|
+
function mergeInto(target, source, owner, path4) {
|
|
4313
4313
|
for (const [key, value] of Object.entries(source)) {
|
|
4314
|
-
const nextPath =
|
|
4314
|
+
const nextPath = path4 ? `${path4}.${key}` : key;
|
|
4315
4315
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4316
4316
|
const existing = hasExisting ? target[key] : void 0;
|
|
4317
4317
|
if (typeof value === "string") {
|
|
@@ -4855,8 +4855,8 @@ var METHOD_ORDER = {
|
|
|
4855
4855
|
function compareNames2(a, b) {
|
|
4856
4856
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4857
4857
|
}
|
|
4858
|
-
function endpointPairKey(method,
|
|
4859
|
-
return `${method} ${
|
|
4858
|
+
function endpointPairKey(method, path4) {
|
|
4859
|
+
return `${method} ${path4}`;
|
|
4860
4860
|
}
|
|
4861
4861
|
function apiPathToRouteFile(apiPath) {
|
|
4862
4862
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4870,11 +4870,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4870
4870
|
}
|
|
4871
4871
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4872
4872
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4873
|
-
function appRouteFileToNextRelativePath(
|
|
4874
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
4873
|
+
function appRouteFileToNextRelativePath(path4) {
|
|
4874
|
+
const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
4875
4875
|
if (pagePath != null) return pagePath;
|
|
4876
|
-
if (!
|
|
4877
|
-
const relative =
|
|
4876
|
+
if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
4877
|
+
const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4878
4878
|
if (!relative.startsWith("api/")) return null;
|
|
4879
4879
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4880
4880
|
if (!suffix) return null;
|
|
@@ -4966,9 +4966,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4966
4966
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4967
4967
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4968
4968
|
}
|
|
4969
|
-
function parseLegacyApiAdapter(
|
|
4970
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4971
|
-
if (expectedMarkerKindForPath(
|
|
4969
|
+
function parseLegacyApiAdapter(path4, source) {
|
|
4970
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
4971
|
+
if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
|
|
4972
4972
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4973
4973
|
const match = firstLine.match(
|
|
4974
4974
|
new RegExp(
|
|
@@ -4978,7 +4978,7 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
4978
4978
|
if (!match || match[2] !== physicalPath) return null;
|
|
4979
4979
|
const owner = match[1];
|
|
4980
4980
|
if (!isValidModuleName(owner)) return null;
|
|
4981
|
-
const file = ts4.createSourceFile(
|
|
4981
|
+
const file = ts4.createSourceFile(path4, source, ts4.ScriptTarget.Latest, false, ts4.ScriptKind.TS);
|
|
4982
4982
|
if (file.parseDiagnostics.length > 0) {
|
|
4983
4983
|
return null;
|
|
4984
4984
|
}
|
|
@@ -5020,9 +5020,9 @@ function parseLegacyApiAdapter(path3, source) {
|
|
|
5020
5020
|
}
|
|
5021
5021
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
5022
5022
|
}
|
|
5023
|
-
function parseLegacyRouteAdapter(
|
|
5024
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
5025
|
-
if (expectedMarkerKindForPath(
|
|
5023
|
+
function parseLegacyRouteAdapter(path4, source) {
|
|
5024
|
+
const physicalPath = appRouteFileToNextRelativePath(path4);
|
|
5025
|
+
if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
|
|
5026
5026
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
5027
5027
|
const match = normalized.match(
|
|
5028
5028
|
new RegExp(
|
|
@@ -5034,30 +5034,30 @@ function parseLegacyRouteAdapter(path3, source) {
|
|
|
5034
5034
|
const nextRelativePath = match[2];
|
|
5035
5035
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
5036
5036
|
}
|
|
5037
|
-
function legacyRouteAdapterMatchesStub(
|
|
5038
|
-
const legacy = parseLegacyRouteAdapter(
|
|
5037
|
+
function legacyRouteAdapterMatchesStub(path4, source, stub) {
|
|
5038
|
+
const legacy = parseLegacyRouteAdapter(path4, source);
|
|
5039
5039
|
return legacy != null && legacy.owner === stub.owner;
|
|
5040
5040
|
}
|
|
5041
5041
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
5042
5042
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
5043
5043
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
5044
|
-
function expectedMarkerKindForPath(
|
|
5045
|
-
if (
|
|
5046
|
-
if (
|
|
5047
|
-
if (
|
|
5048
|
-
if (
|
|
5049
|
-
if (
|
|
5050
|
-
if (
|
|
5051
|
-
if (
|
|
5052
|
-
if (
|
|
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))) {
|
|
5053
5053
|
return "route";
|
|
5054
5054
|
}
|
|
5055
5055
|
return null;
|
|
5056
5056
|
}
|
|
5057
|
-
function markerCongruentWithPath(
|
|
5058
|
-
if (expectedMarkerKindForPath(
|
|
5057
|
+
function markerCongruentWithPath(path4, marker) {
|
|
5058
|
+
if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
|
|
5059
5059
|
if (marker.kind === "route") {
|
|
5060
|
-
const derived = appPageFileToNextRelativePath(
|
|
5060
|
+
const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
|
|
5061
5061
|
return derived != null && derived === marker.nextRelativePath;
|
|
5062
5062
|
}
|
|
5063
5063
|
return true;
|
|
@@ -5328,8 +5328,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5328
5328
|
function activeEnhancementPeers(registryModule) {
|
|
5329
5329
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5330
5330
|
}
|
|
5331
|
-
function endpointBucketKey(
|
|
5332
|
-
return `${
|
|
5331
|
+
function endpointBucketKey(path4, owner, scope) {
|
|
5332
|
+
return `${path4}\0${owner}\0${scopeLabel(scope)}`;
|
|
5333
5333
|
}
|
|
5334
5334
|
function planRouteStubs(input) {
|
|
5335
5335
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5453,7 +5453,7 @@ function planRouteStubs(input) {
|
|
|
5453
5453
|
for (const entry of entries) {
|
|
5454
5454
|
const nextRelativePath = entry.nextRelativePath;
|
|
5455
5455
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5456
|
-
const
|
|
5456
|
+
const path4 = routeEntryToAppPageFile(nextRelativePath);
|
|
5457
5457
|
const existing = routeDestinationOwners.get(destination);
|
|
5458
5458
|
if (existing) {
|
|
5459
5459
|
throw new Error(
|
|
@@ -5463,13 +5463,13 @@ function planRouteStubs(input) {
|
|
|
5463
5463
|
routeDestinationOwners.set(destination, {
|
|
5464
5464
|
owner: regMod.name,
|
|
5465
5465
|
nextRelativePath,
|
|
5466
|
-
path:
|
|
5466
|
+
path: path4
|
|
5467
5467
|
});
|
|
5468
5468
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5469
5469
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5470
5470
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5471
5471
|
routeStubs.push({
|
|
5472
|
-
path:
|
|
5472
|
+
path: path4,
|
|
5473
5473
|
owner: regMod.name,
|
|
5474
5474
|
nextRelativePath,
|
|
5475
5475
|
importModule,
|
|
@@ -5530,16 +5530,16 @@ function planCompositionArtifacts(input) {
|
|
|
5530
5530
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5531
5531
|
]);
|
|
5532
5532
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5533
|
-
const isProvenStaleDeletionCandidate = (
|
|
5534
|
-
if (
|
|
5535
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5536
|
-
if (desiredPaths.has(
|
|
5537
|
-
if (!existingGeneratedPathSet.has(
|
|
5538
|
-
const legacy = parseLegacyApiAdapter(
|
|
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);
|
|
5539
5539
|
if (legacy) return true;
|
|
5540
|
-
if (parseLegacyRouteAdapter(
|
|
5540
|
+
if (parseLegacyRouteAdapter(path4, content)) return true;
|
|
5541
5541
|
const marker = parseCompositionArtifactMarker(content);
|
|
5542
|
-
return marker != null && markerCongruentWithPath(
|
|
5542
|
+
return marker != null && markerCongruentWithPath(path4, marker);
|
|
5543
5543
|
};
|
|
5544
5544
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5545
5545
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5693,13 +5693,13 @@ function planCompositionArtifacts(input) {
|
|
|
5693
5693
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5694
5694
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5695
5695
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5696
|
-
for (const [
|
|
5697
|
-
const existing = input.existingFiles[
|
|
5696
|
+
for (const [path4] of desired) {
|
|
5697
|
+
const existing = input.existingFiles[path4];
|
|
5698
5698
|
if (existing == null) continue;
|
|
5699
5699
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5700
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5701
|
-
const plannedRoute = routeStubByPath.get(
|
|
5702
|
-
const legacy = parseLegacyApiAdapter(
|
|
5700
|
+
const plannedEndpoint = endpointStubByPath.get(path4);
|
|
5701
|
+
const plannedRoute = routeStubByPath.get(path4);
|
|
5702
|
+
const legacy = parseLegacyApiAdapter(path4, existing);
|
|
5703
5703
|
if (legacy && plannedEndpoint) {
|
|
5704
5704
|
marker = {
|
|
5705
5705
|
kind: "endpoint",
|
|
@@ -5707,66 +5707,66 @@ function planCompositionArtifacts(input) {
|
|
|
5707
5707
|
scope: plannedEndpoint.scope
|
|
5708
5708
|
};
|
|
5709
5709
|
}
|
|
5710
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5710
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
|
|
5711
5711
|
marker = {
|
|
5712
5712
|
kind: "route",
|
|
5713
5713
|
owner: plannedRoute.owner,
|
|
5714
5714
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5715
5715
|
};
|
|
5716
5716
|
}
|
|
5717
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5717
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
|
|
5718
5718
|
if (!marker) {
|
|
5719
5719
|
return {
|
|
5720
5720
|
ok: false,
|
|
5721
|
-
error:
|
|
5722
|
-
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
|
|
5723
5723
|
};
|
|
5724
5724
|
}
|
|
5725
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5725
|
+
const expectedKind = expectedMarkerKindForPath(path4);
|
|
5726
5726
|
if (marker.kind !== expectedKind) {
|
|
5727
5727
|
return {
|
|
5728
5728
|
ok: false,
|
|
5729
|
-
error:
|
|
5730
|
-
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
|
|
5731
5731
|
};
|
|
5732
5732
|
}
|
|
5733
5733
|
if (marker.kind === "endpoint") {
|
|
5734
|
-
const stub = endpointStubByPath.get(
|
|
5734
|
+
const stub = endpointStubByPath.get(path4);
|
|
5735
5735
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5736
5736
|
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5737
5737
|
if (!ownerMatches || !scopeMatches) {
|
|
5738
5738
|
return {
|
|
5739
5739
|
ok: false,
|
|
5740
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5741
|
-
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,
|
|
5742
5742
|
owner: stub?.owner
|
|
5743
5743
|
};
|
|
5744
5744
|
}
|
|
5745
5745
|
} else if (marker.kind === "route") {
|
|
5746
|
-
const stub = routeStubByPath.get(
|
|
5746
|
+
const stub = routeStubByPath.get(path4);
|
|
5747
5747
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5748
5748
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5749
5749
|
if (!ownerMatches || !pathMatches) {
|
|
5750
5750
|
return {
|
|
5751
5751
|
ok: false,
|
|
5752
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5753
|
-
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,
|
|
5754
5754
|
owner: stub?.owner
|
|
5755
5755
|
};
|
|
5756
5756
|
}
|
|
5757
5757
|
}
|
|
5758
5758
|
}
|
|
5759
5759
|
const deletes = [];
|
|
5760
|
-
for (const
|
|
5761
|
-
const existing = input.existingFiles[
|
|
5760
|
+
for (const path4 of input.existingGeneratedPaths) {
|
|
5761
|
+
const existing = input.existingFiles[path4];
|
|
5762
5762
|
if (existing == null) continue;
|
|
5763
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5763
|
+
if (!isProvenStaleDeletionCandidate(path4, existing)) {
|
|
5764
5764
|
continue;
|
|
5765
5765
|
}
|
|
5766
|
-
deletes.push(
|
|
5766
|
+
deletes.push(path4);
|
|
5767
5767
|
}
|
|
5768
5768
|
deletes.sort(compareNames2);
|
|
5769
|
-
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));
|
|
5770
5770
|
return {
|
|
5771
5771
|
ok: true,
|
|
5772
5772
|
plan: {
|
|
@@ -5795,8 +5795,16 @@ async function checkoutParsesTypedStores(sandbox) {
|
|
|
5795
5795
|
try {
|
|
5796
5796
|
if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
|
|
5797
5797
|
const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
|
|
5798
|
-
|
|
5799
|
-
|
|
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 {
|
|
5800
5808
|
const lowest = import_semver.default.minVersion(range);
|
|
5801
5809
|
return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
|
|
5802
5810
|
} catch {
|
|
@@ -6241,11 +6249,11 @@ function countedCompositionContext(context) {
|
|
|
6241
6249
|
counts.sandboxExecs += 1;
|
|
6242
6250
|
return context.sandbox.exec(...args);
|
|
6243
6251
|
},
|
|
6244
|
-
readFile: (
|
|
6252
|
+
readFile: (path4) => {
|
|
6245
6253
|
counts.sandboxReads += 1;
|
|
6246
|
-
return context.sandbox.readFile(
|
|
6254
|
+
return context.sandbox.readFile(path4);
|
|
6247
6255
|
},
|
|
6248
|
-
fileExists: (
|
|
6256
|
+
fileExists: (path4) => context.sandbox.fileExists(path4),
|
|
6249
6257
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6250
6258
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6251
6259
|
}
|
|
@@ -6457,14 +6465,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6457
6465
|
}
|
|
6458
6466
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6459
6467
|
}
|
|
6460
|
-
async function writeSandboxFileAtomic(target,
|
|
6461
|
-
const dir = dirnamePosix(
|
|
6462
|
-
const tmp = `${
|
|
6468
|
+
async function writeSandboxFileAtomic(target, path4, content) {
|
|
6469
|
+
const dir = dirnamePosix(path4);
|
|
6470
|
+
const tmp = `${path4}.tmp.${target.runId}`;
|
|
6463
6471
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6464
6472
|
const cmd = [
|
|
6465
6473
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6466
6474
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6467
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6475
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
|
|
6468
6476
|
].join(" && ");
|
|
6469
6477
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6470
6478
|
if (result.exitCode !== 0) {
|
|
@@ -6479,14 +6487,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
|
|
|
6479
6487
|
}
|
|
6480
6488
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6481
6489
|
throw new Error(
|
|
6482
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6490
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6483
6491
|
);
|
|
6484
6492
|
}
|
|
6485
6493
|
}
|
|
6486
6494
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6487
6495
|
const absent = /* @__PURE__ */ new Set();
|
|
6488
|
-
for (const
|
|
6489
|
-
let dir = dirnamePosix(
|
|
6496
|
+
for (const path4 of writePaths) {
|
|
6497
|
+
let dir = dirnamePosix(path4);
|
|
6490
6498
|
while (dir !== "." && dir !== "") {
|
|
6491
6499
|
if (absent.has(dir)) {
|
|
6492
6500
|
dir = dirnamePosix(dir);
|
|
@@ -6523,12 +6531,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6523
6531
|
);
|
|
6524
6532
|
}
|
|
6525
6533
|
}
|
|
6526
|
-
async function deleteSandboxFile(context,
|
|
6527
|
-
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 });
|
|
6528
6536
|
if (result.exitCode !== 0) {
|
|
6529
6537
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6530
6538
|
throw new Error(
|
|
6531
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6539
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
|
|
6532
6540
|
);
|
|
6533
6541
|
}
|
|
6534
6542
|
}
|
|
@@ -6595,37 +6603,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6595
6603
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6596
6604
|
appliedWrites.push({ path: write.path, previous });
|
|
6597
6605
|
}
|
|
6598
|
-
for (const
|
|
6599
|
-
const previous = existingFiles[
|
|
6606
|
+
for (const path4 of plan.deletes) {
|
|
6607
|
+
const previous = existingFiles[path4];
|
|
6600
6608
|
if (previous == null) continue;
|
|
6601
|
-
await deleteSandboxFile(context,
|
|
6602
|
-
appliedDeletes.push({ path:
|
|
6609
|
+
await deleteSandboxFile(context, path4);
|
|
6610
|
+
appliedDeletes.push({ path: path4, previous });
|
|
6603
6611
|
}
|
|
6604
6612
|
} catch (error) {
|
|
6605
6613
|
console.log(
|
|
6606
6614
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6607
6615
|
);
|
|
6608
|
-
for (const { path:
|
|
6616
|
+
for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
|
|
6609
6617
|
try {
|
|
6610
|
-
await writeSandboxFileAtomic(context,
|
|
6618
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6611
6619
|
} catch (rollbackError) {
|
|
6612
6620
|
console.log(
|
|
6613
|
-
`[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(
|
|
6614
6622
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6615
6623
|
)}`
|
|
6616
6624
|
);
|
|
6617
6625
|
}
|
|
6618
6626
|
}
|
|
6619
|
-
for (const { path:
|
|
6627
|
+
for (const { path: path4, previous } of [...appliedWrites].reverse()) {
|
|
6620
6628
|
try {
|
|
6621
6629
|
if (previous == null) {
|
|
6622
|
-
await deleteSandboxFile(context,
|
|
6630
|
+
await deleteSandboxFile(context, path4);
|
|
6623
6631
|
} else {
|
|
6624
|
-
await writeSandboxFileAtomic(context,
|
|
6632
|
+
await writeSandboxFileAtomic(context, path4, previous);
|
|
6625
6633
|
}
|
|
6626
6634
|
} catch (rollbackError) {
|
|
6627
6635
|
console.log(
|
|
6628
|
-
`[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(
|
|
6629
6637
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6630
6638
|
)}`
|
|
6631
6639
|
);
|
|
@@ -6659,39 +6667,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6659
6667
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6660
6668
|
);
|
|
6661
6669
|
const presentPaths = [];
|
|
6662
|
-
for (const
|
|
6670
|
+
for (const path4 of candidates) {
|
|
6663
6671
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6664
|
-
|
|
6665
|
-
kinds.get(
|
|
6672
|
+
path4,
|
|
6673
|
+
kinds.get(path4) ?? "unreadable"
|
|
6666
6674
|
);
|
|
6667
|
-
if (presence === "file") presentPaths.push(
|
|
6675
|
+
if (presence === "file") presentPaths.push(path4);
|
|
6668
6676
|
}
|
|
6669
6677
|
let contents;
|
|
6670
6678
|
try {
|
|
6671
6679
|
contents = await readFilesMany(context, presentPaths);
|
|
6672
6680
|
} catch (error) {
|
|
6673
|
-
const
|
|
6681
|
+
const path4 = readFailurePath(error);
|
|
6674
6682
|
throw new Error(
|
|
6675
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6683
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
|
|
6676
6684
|
error instanceof Error ? error.message : String(error)
|
|
6677
6685
|
)}`
|
|
6678
6686
|
);
|
|
6679
6687
|
}
|
|
6680
6688
|
const owned = [];
|
|
6681
|
-
for (const
|
|
6682
|
-
const content = contents.get(
|
|
6689
|
+
for (const path4 of presentPaths) {
|
|
6690
|
+
const content = contents.get(path4);
|
|
6683
6691
|
const marker = parseCompositionArtifactMarker(content);
|
|
6684
|
-
if (parseLegacyApiAdapter(
|
|
6685
|
-
owned.push(
|
|
6692
|
+
if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
|
|
6693
|
+
owned.push(path4);
|
|
6686
6694
|
continue;
|
|
6687
6695
|
}
|
|
6688
6696
|
if (marker == null) continue;
|
|
6689
|
-
if (!markerCongruentWithPath(
|
|
6697
|
+
if (!markerCongruentWithPath(path4, marker)) {
|
|
6690
6698
|
throw new Error(
|
|
6691
|
-
`[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)`
|
|
6692
6700
|
);
|
|
6693
6701
|
}
|
|
6694
|
-
owned.push(
|
|
6702
|
+
owned.push(path4);
|
|
6695
6703
|
}
|
|
6696
6704
|
return owned;
|
|
6697
6705
|
}
|
|
@@ -6824,7 +6832,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6824
6832
|
{ raiseOnError: false }
|
|
6825
6833
|
);
|
|
6826
6834
|
if (result.exitCode !== 0) {
|
|
6827
|
-
for (const
|
|
6835
|
+
for (const path4 of chunk) kinds.set(path4, "unreadable");
|
|
6828
6836
|
continue;
|
|
6829
6837
|
}
|
|
6830
6838
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6835,31 +6843,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6835
6843
|
}
|
|
6836
6844
|
} else {
|
|
6837
6845
|
for (const line of lines) {
|
|
6838
|
-
const [rawKind,
|
|
6846
|
+
const [rawKind, path4] = line.split(" ", 2);
|
|
6839
6847
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6840
|
-
if (
|
|
6841
|
-
kinds.set(
|
|
6848
|
+
if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
6849
|
+
kinds.set(path4, kind);
|
|
6842
6850
|
}
|
|
6843
6851
|
}
|
|
6844
6852
|
}
|
|
6845
|
-
for (const
|
|
6846
|
-
if (!kinds.has(
|
|
6853
|
+
for (const path4 of chunk) {
|
|
6854
|
+
if (!kinds.has(path4)) kinds.set(path4, "unreadable");
|
|
6847
6855
|
}
|
|
6848
6856
|
}
|
|
6849
6857
|
return kinds;
|
|
6850
6858
|
}
|
|
6851
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6852
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
6859
|
+
async function classifySandboxPathPresenceKind(context, path4, probeToken) {
|
|
6860
|
+
return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
|
|
6853
6861
|
}
|
|
6854
6862
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6855
|
-
function assertRealLocaleJsonFilePresence(
|
|
6863
|
+
function assertRealLocaleJsonFilePresence(path4, presence) {
|
|
6856
6864
|
if (presence === "file") return;
|
|
6857
6865
|
if (presence === "absent") {
|
|
6858
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
6866
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
|
|
6859
6867
|
}
|
|
6860
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6868
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6861
6869
|
throw new Error(
|
|
6862
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6870
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
|
|
6863
6871
|
);
|
|
6864
6872
|
}
|
|
6865
6873
|
function splitTaggedLine(line) {
|
|
@@ -6934,11 +6942,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6934
6942
|
}
|
|
6935
6943
|
async function readFilesMany(context, paths) {
|
|
6936
6944
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6937
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
6945
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
|
|
6938
6946
|
try {
|
|
6939
|
-
return { path:
|
|
6947
|
+
return { path: path4, value: await context.sandbox.readFile(path4) };
|
|
6940
6948
|
} catch (error) {
|
|
6941
|
-
return { path:
|
|
6949
|
+
return { path: path4, error };
|
|
6942
6950
|
}
|
|
6943
6951
|
});
|
|
6944
6952
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6961,58 +6969,58 @@ function readFailurePath(error) {
|
|
|
6961
6969
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6962
6970
|
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6963
6971
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6964
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6965
|
-
return classifySandboxPathPresenceKind(context,
|
|
6972
|
+
async function classifyCompositionArtifactPathKind(context, path4) {
|
|
6973
|
+
return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6966
6974
|
}
|
|
6967
|
-
function assertRealCompositionArtifactFilePresence(
|
|
6975
|
+
function assertRealCompositionArtifactFilePresence(path4, presence) {
|
|
6968
6976
|
if (presence === "file") return "file";
|
|
6969
6977
|
if (presence === "absent") return "absent";
|
|
6970
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6978
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6971
6979
|
throw new Error(
|
|
6972
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
6980
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
|
|
6973
6981
|
);
|
|
6974
6982
|
}
|
|
6975
|
-
async function readConventionalEntryIfRealFile(
|
|
6976
|
-
const presence = presences.get(
|
|
6977
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
6983
|
+
async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
6984
|
+
const presence = presences.get(path4) ?? "unreadable";
|
|
6985
|
+
const presenceError = formatConventionalEntryPresenceError(path4, presence);
|
|
6978
6986
|
if (presenceError) {
|
|
6979
6987
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6980
6988
|
}
|
|
6981
6989
|
if (presence === "absent") {
|
|
6982
6990
|
return null;
|
|
6983
6991
|
}
|
|
6984
|
-
const source = files.get(
|
|
6992
|
+
const source = files.get(path4);
|
|
6985
6993
|
switch (kind) {
|
|
6986
6994
|
case "contributions":
|
|
6987
|
-
assertContributionsExport(source,
|
|
6988
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6995
|
+
assertContributionsExport(source, path4);
|
|
6996
|
+
assertClientSafeConventionalEntryImports(source, path4, "contributions");
|
|
6989
6997
|
break;
|
|
6990
6998
|
case "slotCatalogs":
|
|
6991
|
-
assertSlotCatalogsExport(source,
|
|
6992
|
-
assertClientSafeConventionalEntryImports(source,
|
|
6999
|
+
assertSlotCatalogsExport(source, path4);
|
|
7000
|
+
assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
|
|
6993
7001
|
break;
|
|
6994
7002
|
case "initializeEnhancement":
|
|
6995
|
-
assertInitializeEnhancementExport(source,
|
|
7003
|
+
assertInitializeEnhancementExport(source, path4);
|
|
6996
7004
|
break;
|
|
6997
7005
|
}
|
|
6998
7006
|
return source;
|
|
6999
7007
|
}
|
|
7000
7008
|
function hasRealServerRegistrations(modulePath, presences) {
|
|
7001
|
-
const
|
|
7002
|
-
return assertRealCompositionArtifactFilePresence(
|
|
7009
|
+
const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
7010
|
+
return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
|
|
7003
7011
|
}
|
|
7004
|
-
async function readJsonObjectFile(
|
|
7005
|
-
const raw = files.get(
|
|
7012
|
+
async function readJsonObjectFile(path4, files) {
|
|
7013
|
+
const raw = files.get(path4);
|
|
7006
7014
|
let parsed;
|
|
7007
7015
|
try {
|
|
7008
7016
|
parsed = JSON.parse(raw);
|
|
7009
7017
|
} catch (error) {
|
|
7010
7018
|
throw new Error(
|
|
7011
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
7019
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
|
|
7012
7020
|
);
|
|
7013
7021
|
}
|
|
7014
7022
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
7015
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
7023
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
|
|
7016
7024
|
}
|
|
7017
7025
|
return parsed;
|
|
7018
7026
|
}
|
|
@@ -7061,11 +7069,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7061
7069
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7062
7070
|
]);
|
|
7063
7071
|
const presentConventionalPaths = [];
|
|
7064
|
-
for (const
|
|
7065
|
-
const presence = conventionalPresences.get(
|
|
7066
|
-
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);
|
|
7067
7075
|
}
|
|
7068
|
-
const presentLocalePaths = localePaths.filter((
|
|
7076
|
+
const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
|
|
7069
7077
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7070
7078
|
const substrateLocales = [];
|
|
7071
7079
|
for (const locale of substrateNames) {
|
|
@@ -7163,8 +7171,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7163
7171
|
});
|
|
7164
7172
|
if (rootLocales.length > 0) {
|
|
7165
7173
|
for (const locale of rootLocales) {
|
|
7166
|
-
const
|
|
7167
|
-
assertRealLocaleJsonFilePresence(
|
|
7174
|
+
const path4 = `${modulePath}/i18n/${locale}.json`;
|
|
7175
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7168
7176
|
}
|
|
7169
7177
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7170
7178
|
const localeMessages = { en: enMessages };
|
|
@@ -7188,8 +7196,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7188
7196
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7189
7197
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7190
7198
|
for (const locale of enh.locales) {
|
|
7191
|
-
const
|
|
7192
|
-
assertRealLocaleJsonFilePresence(
|
|
7199
|
+
const path4 = `${peerDir}/i18n/${locale}.json`;
|
|
7200
|
+
assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
|
|
7193
7201
|
}
|
|
7194
7202
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7195
7203
|
const localeMessages = { en: enMessages };
|
|
@@ -7390,16 +7398,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7390
7398
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7391
7399
|
const existingFiles = {};
|
|
7392
7400
|
const remainingCandidatePaths = [];
|
|
7393
|
-
for (const
|
|
7394
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7395
|
-
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];
|
|
7396
7404
|
continue;
|
|
7397
7405
|
}
|
|
7398
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7399
|
-
existingFiles[
|
|
7406
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
|
|
7407
|
+
existingFiles[path4] = existingAppRoutes[path4];
|
|
7400
7408
|
continue;
|
|
7401
7409
|
}
|
|
7402
|
-
remainingCandidatePaths.push(
|
|
7410
|
+
remainingCandidatePaths.push(path4);
|
|
7403
7411
|
}
|
|
7404
7412
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7405
7413
|
context,
|
|
@@ -7407,12 +7415,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7407
7415
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7408
7416
|
);
|
|
7409
7417
|
const presentCandidatePaths = [];
|
|
7410
|
-
for (const
|
|
7418
|
+
for (const path4 of remainingCandidatePaths) {
|
|
7411
7419
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7412
|
-
|
|
7413
|
-
candidateKinds.get(
|
|
7420
|
+
path4,
|
|
7421
|
+
candidateKinds.get(path4) ?? "unreadable"
|
|
7414
7422
|
);
|
|
7415
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7423
|
+
if (presence === "file") presentCandidatePaths.push(path4);
|
|
7416
7424
|
}
|
|
7417
7425
|
try {
|
|
7418
7426
|
Object.assign(
|
|
@@ -7420,9 +7428,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7420
7428
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7421
7429
|
);
|
|
7422
7430
|
} catch (error) {
|
|
7423
|
-
const
|
|
7431
|
+
const path4 = readFailurePath(error);
|
|
7424
7432
|
throw new Error(
|
|
7425
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7433
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
|
|
7426
7434
|
error instanceof Error ? error.message : String(error)
|
|
7427
7435
|
)}`
|
|
7428
7436
|
);
|
|
@@ -7491,14 +7499,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7491
7499
|
var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
7492
7500
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
7493
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
|
+
|
|
7494
7627
|
// src/index.ts
|
|
7495
7628
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7496
7629
|
function readComposeInputs(cwd) {
|
|
7497
|
-
const file =
|
|
7498
|
-
if (!
|
|
7630
|
+
const file = path3.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7631
|
+
if (!fs3.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7499
7632
|
let raw;
|
|
7500
7633
|
try {
|
|
7501
|
-
raw = JSON.parse(
|
|
7634
|
+
raw = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
7502
7635
|
} catch (error) {
|
|
7503
7636
|
throw new Error(
|
|
7504
7637
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7540,18 +7673,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7540
7673
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7541
7674
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7542
7675
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7543
|
-
const file =
|
|
7544
|
-
const existing =
|
|
7676
|
+
const file = path3.join(cwd, STUB_GITIGNORE_PATH);
|
|
7677
|
+
const existing = fs3.existsSync(file) ? fs3.readFileSync(file, "utf8") : null;
|
|
7545
7678
|
if (existing !== content) {
|
|
7546
|
-
|
|
7547
|
-
|
|
7679
|
+
fs3.mkdirSync(path3.dirname(file), { recursive: true });
|
|
7680
|
+
fs3.writeFileSync(file, content);
|
|
7548
7681
|
}
|
|
7549
7682
|
return entries.length;
|
|
7550
7683
|
}
|
|
7551
7684
|
function assertStubGitignoreOwned(cwd) {
|
|
7552
|
-
const file =
|
|
7553
|
-
if (!
|
|
7554
|
-
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] ?? "";
|
|
7555
7688
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7556
7689
|
throw new Error(
|
|
7557
7690
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7559,8 +7692,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7559
7692
|
}
|
|
7560
7693
|
}
|
|
7561
7694
|
function assertAppRoot(cwd) {
|
|
7562
|
-
const appPath =
|
|
7563
|
-
if (!
|
|
7695
|
+
const appPath = path3.join(cwd, APP_PACKAGE_JSON);
|
|
7696
|
+
if (!fs3.existsSync(appPath)) {
|
|
7564
7697
|
throw new Error(
|
|
7565
7698
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7566
7699
|
);
|
|
@@ -7606,9 +7739,9 @@ ${porcelain.trim()}`
|
|
|
7606
7739
|
}
|
|
7607
7740
|
var APP_SRC_DIR = "apps/web/src";
|
|
7608
7741
|
function walkSources(dir, skip, out) {
|
|
7609
|
-
if (!
|
|
7610
|
-
for (const entry of
|
|
7611
|
-
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);
|
|
7612
7745
|
if (full === skip) continue;
|
|
7613
7746
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7614
7747
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7617,13 +7750,13 @@ function walkSources(dir, skip, out) {
|
|
|
7617
7750
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7618
7751
|
if (excluded.length === 0) return;
|
|
7619
7752
|
const files = [];
|
|
7620
|
-
walkSources(
|
|
7753
|
+
walkSources(path3.join(cwd, APP_SRC_DIR), path3.join(cwd, MODULES_DIR2), files);
|
|
7621
7754
|
const offenders = [];
|
|
7622
7755
|
for (const file of files) {
|
|
7623
|
-
const source =
|
|
7756
|
+
const source = fs3.readFileSync(file, "utf8");
|
|
7624
7757
|
for (const name of excluded) {
|
|
7625
7758
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7626
|
-
offenders.push(`${
|
|
7759
|
+
offenders.push(`${path3.relative(cwd, file)}: ${name}`);
|
|
7627
7760
|
}
|
|
7628
7761
|
}
|
|
7629
7762
|
}
|
|
@@ -7636,8 +7769,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
|
|
|
7636
7769
|
}
|
|
7637
7770
|
}
|
|
7638
7771
|
function removeDir(absolute) {
|
|
7639
|
-
if (!
|
|
7640
|
-
|
|
7772
|
+
if (!fs3.existsSync(absolute)) return false;
|
|
7773
|
+
fs3.rmSync(absolute, { recursive: true, force: true });
|
|
7641
7774
|
return true;
|
|
7642
7775
|
}
|
|
7643
7776
|
async function compose(options) {
|
|
@@ -7664,6 +7797,16 @@ async function compose(options) {
|
|
|
7664
7797
|
dryRun: true
|
|
7665
7798
|
});
|
|
7666
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
|
+
}
|
|
7667
7810
|
}
|
|
7668
7811
|
const result = await reconcileCompositionArtifacts(context, {
|
|
7669
7812
|
enabledModulesRaw: options.enabled,
|
|
@@ -7675,11 +7818,11 @@ async function compose(options) {
|
|
|
7675
7818
|
let pruned = 0;
|
|
7676
7819
|
if (options.prune) {
|
|
7677
7820
|
for (const name of result.excludedModules) {
|
|
7678
|
-
if (removeDir(
|
|
7821
|
+
if (removeDir(path3.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7679
7822
|
}
|
|
7680
7823
|
for (const owner of result.composedModules) {
|
|
7681
7824
|
for (const peer of result.excludedModules) {
|
|
7682
|
-
if (removeDir(
|
|
7825
|
+
if (removeDir(path3.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7683
7826
|
}
|
|
7684
7827
|
}
|
|
7685
7828
|
}
|
|
@@ -7697,5 +7840,8 @@ export {
|
|
|
7697
7840
|
assertSupportedTypescript,
|
|
7698
7841
|
compose,
|
|
7699
7842
|
escapeGitignore,
|
|
7843
|
+
findModuleBoundaryViolations,
|
|
7844
|
+
findOptionalPeerViolations,
|
|
7845
|
+
formatBoundaryViolations,
|
|
7700
7846
|
readComposeInputs
|
|
7701
7847
|
};
|