@stardeck-customer-apps/compose 0.14.0 → 0.15.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 +16 -0
- package/dist/cli.js +742 -452
- package/dist/index.js +210 -190
- package/dist/index.mjs +210 -190
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1145,16 +1145,16 @@ var require_range = __commonJS({
|
|
|
1145
1145
|
};
|
|
1146
1146
|
var replaceTilde = (comp, options) => {
|
|
1147
1147
|
const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
|
|
1148
|
-
const
|
|
1148
|
+
const z11 = options.includePrerelease ? "-0" : "";
|
|
1149
1149
|
return comp.replace(r, (_, M, m, p, pr) => {
|
|
1150
1150
|
debug("tilde", comp, _, M, m, p, pr);
|
|
1151
1151
|
let ret;
|
|
1152
1152
|
if (isX(M)) {
|
|
1153
1153
|
ret = "";
|
|
1154
1154
|
} else if (isX(m)) {
|
|
1155
|
-
ret = `>=${M}.0.0${
|
|
1155
|
+
ret = `>=${M}.0.0${z11} <${+M + 1}.0.0-0`;
|
|
1156
1156
|
} else if (isX(p)) {
|
|
1157
|
-
ret = `>=${M}.${m}.0${
|
|
1157
|
+
ret = `>=${M}.${m}.0${z11} <${M}.${+m + 1}.0-0`;
|
|
1158
1158
|
} else if (pr) {
|
|
1159
1159
|
debug("replaceTilde pr", pr);
|
|
1160
1160
|
ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`;
|
|
@@ -1171,19 +1171,19 @@ var require_range = __commonJS({
|
|
|
1171
1171
|
var replaceCaret = (comp, options) => {
|
|
1172
1172
|
debug("caret", comp, options);
|
|
1173
1173
|
const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
|
|
1174
|
-
const
|
|
1174
|
+
const z11 = options.includePrerelease ? "-0" : "";
|
|
1175
1175
|
return comp.replace(r, (_, M, m, p, pr) => {
|
|
1176
1176
|
debug("caret", comp, _, M, m, p, pr);
|
|
1177
1177
|
let ret;
|
|
1178
1178
|
if (isX(M)) {
|
|
1179
1179
|
ret = "";
|
|
1180
1180
|
} else if (isX(m)) {
|
|
1181
|
-
ret = `>=${M}.0.0${
|
|
1181
|
+
ret = `>=${M}.0.0${z11} <${+M + 1}.0.0-0`;
|
|
1182
1182
|
} else if (isX(p)) {
|
|
1183
1183
|
if (M === "0") {
|
|
1184
|
-
ret = `>=${M}.${m}.0${
|
|
1184
|
+
ret = `>=${M}.${m}.0${z11} <${M}.${+m + 1}.0-0`;
|
|
1185
1185
|
} else {
|
|
1186
|
-
ret = `>=${M}.${m}.0${
|
|
1186
|
+
ret = `>=${M}.${m}.0${z11} <${+M + 1}.0.0-0`;
|
|
1187
1187
|
}
|
|
1188
1188
|
} else if (pr) {
|
|
1189
1189
|
debug("replaceCaret pr", pr);
|
|
@@ -2021,8 +2021,13 @@ var require_semver2 = __commonJS({
|
|
|
2021
2021
|
});
|
|
2022
2022
|
|
|
2023
2023
|
// src/cli.ts
|
|
2024
|
+
var import_node_child_process3 = require("child_process");
|
|
2024
2025
|
var import_node_util = require("util");
|
|
2025
2026
|
|
|
2027
|
+
// ../../packages/lib/src/server/module-rail/ownership-guard.ts
|
|
2028
|
+
var import_node_fs = __toESM(require("fs"));
|
|
2029
|
+
var import_node_path = __toESM(require("path"));
|
|
2030
|
+
|
|
2026
2031
|
// ../../packages/module-contract/src/module-manifest.ts
|
|
2027
2032
|
var import_zod = require("zod");
|
|
2028
2033
|
|
|
@@ -2053,6 +2058,13 @@ function validateSlug(slug) {
|
|
|
2053
2058
|
var MODULES_DIR = "src/modules";
|
|
2054
2059
|
var APP_PACKAGE_DIR = "apps/web";
|
|
2055
2060
|
var MODULES_SANDBOX_DIR = `${APP_PACKAGE_DIR}/${MODULES_DIR}`;
|
|
2061
|
+
var MODULE_MANIFEST_FILENAME = "module.json";
|
|
2062
|
+
var MODULE_UI_SUBPATHS = [
|
|
2063
|
+
"components/",
|
|
2064
|
+
"routes/",
|
|
2065
|
+
"enhancements/*/components/",
|
|
2066
|
+
"enhancements/*/routes/"
|
|
2067
|
+
];
|
|
2056
2068
|
var KEBAB_SLUG_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2057
2069
|
var SNAKE_TABLE_REGEX = /^[a-z][a-z0-9_]*$/;
|
|
2058
2070
|
var ROUTE_GROUP_REGEX = /^\([a-z0-9-]+\)$/;
|
|
@@ -2066,23 +2078,23 @@ function isValidModuleName(value) {
|
|
|
2066
2078
|
var projectSlugSchema = import_zod.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
|
|
2067
2079
|
var moduleKindSchema = import_zod.z.enum(["feature", "library", "contract"]);
|
|
2068
2080
|
var endpointMethodSchema = import_zod.z.enum(ENDPOINT_METHODS);
|
|
2069
|
-
function endpointPathValidationError(
|
|
2070
|
-
if (!
|
|
2081
|
+
function endpointPathValidationError(path6) {
|
|
2082
|
+
if (!path6.startsWith("/api/")) {
|
|
2071
2083
|
return 'must begin with "/api/"';
|
|
2072
2084
|
}
|
|
2073
|
-
if (
|
|
2085
|
+
if (path6.endsWith("/")) {
|
|
2074
2086
|
return "must not end with a trailing slash";
|
|
2075
2087
|
}
|
|
2076
|
-
if (
|
|
2088
|
+
if (path6.includes("?") || path6.includes("#")) {
|
|
2077
2089
|
return "must not include query or hash";
|
|
2078
2090
|
}
|
|
2079
|
-
if (
|
|
2091
|
+
if (path6.includes("\\")) {
|
|
2080
2092
|
return "must not include backslashes";
|
|
2081
2093
|
}
|
|
2082
|
-
if (
|
|
2094
|
+
if (path6.includes("%")) {
|
|
2083
2095
|
return "must not include percent-encoded segments";
|
|
2084
2096
|
}
|
|
2085
|
-
const parts =
|
|
2097
|
+
const parts = path6.split("/");
|
|
2086
2098
|
if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
|
|
2087
2099
|
return 'must begin with "/api/"';
|
|
2088
2100
|
}
|
|
@@ -2132,12 +2144,12 @@ function endpointPathValidationError(path5) {
|
|
|
2132
2144
|
}
|
|
2133
2145
|
return null;
|
|
2134
2146
|
}
|
|
2135
|
-
function normalizeEndpointPathForCollision(
|
|
2136
|
-
const error = endpointPathValidationError(
|
|
2147
|
+
function normalizeEndpointPathForCollision(path6) {
|
|
2148
|
+
const error = endpointPathValidationError(path6);
|
|
2137
2149
|
if (error != null) {
|
|
2138
2150
|
throw new Error(`invalid endpoint path: ${error}`);
|
|
2139
2151
|
}
|
|
2140
|
-
return
|
|
2152
|
+
return path6.split("/").map((segment, index) => {
|
|
2141
2153
|
if (index < 2) {
|
|
2142
2154
|
return segment;
|
|
2143
2155
|
}
|
|
@@ -2150,8 +2162,8 @@ function normalizeEndpointPathForCollision(path5) {
|
|
|
2150
2162
|
return segment;
|
|
2151
2163
|
}).join("/");
|
|
2152
2164
|
}
|
|
2153
|
-
var endpointPathSchema = import_zod.z.string().superRefine((
|
|
2154
|
-
const error = endpointPathValidationError(
|
|
2165
|
+
var endpointPathSchema = import_zod.z.string().superRefine((path6, ctx) => {
|
|
2166
|
+
const error = endpointPathValidationError(path6);
|
|
2155
2167
|
if (error != null) {
|
|
2156
2168
|
ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
|
|
2157
2169
|
}
|
|
@@ -2259,10 +2271,12 @@ var modulePermissionClaimSchema = import_zod.z.object({
|
|
|
2259
2271
|
/** Human label for the deployment auth config row. */
|
|
2260
2272
|
description: descriptionSchema,
|
|
2261
2273
|
/**
|
|
2262
|
-
* The
|
|
2263
|
-
*
|
|
2264
|
-
*
|
|
2265
|
-
*
|
|
2274
|
+
* The standard organization roles that hold this key. The platform sets them on every app
|
|
2275
|
+
* (a Blueprint fork, a Module install or uninstall, every Blueprint update), so this list is
|
|
2276
|
+
* the truth: a role added here gains the key, a role removed loses it, and nobody can edit a
|
|
2277
|
+
* standard role's grants on a Module key. Admin holds every key without being listed;
|
|
2278
|
+
* External Viewer never holds one. An organization that wants different access duplicates the
|
|
2279
|
+
* standard role and edits the copy.
|
|
2266
2280
|
*/
|
|
2267
2281
|
roles: import_zod.z.array(import_zod.z.enum(MODULE_PERMISSION_DEFAULT_ROLES)).optional()
|
|
2268
2282
|
}).strict();
|
|
@@ -2412,16 +2426,16 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2412
2426
|
}
|
|
2413
2427
|
}
|
|
2414
2428
|
const seenWorkflows = /* @__PURE__ */ new Map();
|
|
2415
|
-
const noteWorkflow = (name,
|
|
2429
|
+
const noteWorkflow = (name, path6) => {
|
|
2416
2430
|
const existing = seenWorkflows.get(name);
|
|
2417
2431
|
if (existing != null) {
|
|
2418
2432
|
ctx.addIssue({
|
|
2419
2433
|
code: import_zod.z.ZodIssueCode.custom,
|
|
2420
2434
|
message: `duplicate workflow name "${name}" (also at ${existing})`,
|
|
2421
|
-
path:
|
|
2435
|
+
path: path6
|
|
2422
2436
|
});
|
|
2423
2437
|
} else {
|
|
2424
|
-
seenWorkflows.set(name,
|
|
2438
|
+
seenWorkflows.set(name, path6.join("."));
|
|
2425
2439
|
}
|
|
2426
2440
|
};
|
|
2427
2441
|
for (let i = 0; i < manifest.workflows.length; i += 1) {
|
|
@@ -2433,7 +2447,7 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2433
2447
|
}
|
|
2434
2448
|
}
|
|
2435
2449
|
const seenEndpointKeys = /* @__PURE__ */ new Map();
|
|
2436
|
-
const noteEndpoint = (claim,
|
|
2450
|
+
const noteEndpoint = (claim, path6) => {
|
|
2437
2451
|
let key;
|
|
2438
2452
|
try {
|
|
2439
2453
|
key = normalizeEndpointPathForCollision(claim.path);
|
|
@@ -2445,10 +2459,10 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2445
2459
|
ctx.addIssue({
|
|
2446
2460
|
code: import_zod.z.ZodIssueCode.custom,
|
|
2447
2461
|
message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
|
|
2448
|
-
path:
|
|
2462
|
+
path: path6
|
|
2449
2463
|
});
|
|
2450
2464
|
} else {
|
|
2451
|
-
seenEndpointKeys.set(key,
|
|
2465
|
+
seenEndpointKeys.set(key, path6.join("."));
|
|
2452
2466
|
}
|
|
2453
2467
|
};
|
|
2454
2468
|
for (let i = 0; i < manifest.endpoints.length; i += 1) {
|
|
@@ -2549,8 +2563,8 @@ var moduleManifestSchema = import_zod.z.object({
|
|
|
2549
2563
|
}).transform(({ version: _legacyVersion, ...manifest }) => manifest);
|
|
2550
2564
|
function formatZodErrors(error) {
|
|
2551
2565
|
return error.issues.map((issue) => {
|
|
2552
|
-
const
|
|
2553
|
-
return `${
|
|
2566
|
+
const path6 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2567
|
+
return `${path6}: ${issue.message}`;
|
|
2554
2568
|
});
|
|
2555
2569
|
}
|
|
2556
2570
|
function parseModuleManifest(raw) {
|
|
@@ -2950,16 +2964,16 @@ var collectedE2eFactsSchema = import_zod3.z.object({
|
|
|
2950
2964
|
function validateRouteExportEvidenceCongruence(input) {
|
|
2951
2965
|
const entrySet = new Set(input.routeEntries);
|
|
2952
2966
|
const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
|
|
2953
|
-
for (const
|
|
2954
|
-
if (!entrySet.has(
|
|
2955
|
-
input.onIssue(`routePageExports path "${
|
|
2967
|
+
for (const path6 of exportPaths) {
|
|
2968
|
+
if (!entrySet.has(path6)) {
|
|
2969
|
+
input.onIssue(`routePageExports path "${path6}" is not listed in routeEntries`);
|
|
2956
2970
|
}
|
|
2957
2971
|
}
|
|
2958
2972
|
if (input.routeCollectionErrors.length === 0) {
|
|
2959
2973
|
const exportSet = new Set(exportPaths);
|
|
2960
|
-
for (const
|
|
2961
|
-
if (!exportSet.has(
|
|
2962
|
-
input.onIssue(`routeEntries path "${
|
|
2974
|
+
for (const path6 of input.routeEntries) {
|
|
2975
|
+
if (!exportSet.has(path6)) {
|
|
2976
|
+
input.onIssue(`routeEntries path "${path6}" is missing from routePageExports`);
|
|
2963
2977
|
}
|
|
2964
2978
|
}
|
|
2965
2979
|
}
|
|
@@ -3904,6 +3918,50 @@ function analyzeClientSafeEntryImports(sourceText, fileName) {
|
|
|
3904
3918
|
return analyzeClientSafeEntryImportsWithTs(import_typescript3.default, sourceText, fileName);
|
|
3905
3919
|
}
|
|
3906
3920
|
|
|
3921
|
+
// ../../packages/lib/src/shared/ownership-record.ts
|
|
3922
|
+
var import_zod6 = require("zod");
|
|
3923
|
+
var OWNERSHIP_RECORD_PATH = "stardeck.ownership.json";
|
|
3924
|
+
var OWNERSHIP_RECORD_VERSION = 1;
|
|
3925
|
+
var moduleList = import_zod6.z.array(import_zod6.z.string().refine(isValidModuleName, "not a Module name"));
|
|
3926
|
+
var ownershipRecordSchema = import_zod6.z.object({
|
|
3927
|
+
version: import_zod6.z.literal(OWNERSHIP_RECORD_VERSION),
|
|
3928
|
+
/** False once the app stopped all Stardeck updates: then every file is the app's. */
|
|
3929
|
+
stardeckUpdates: import_zod6.z.boolean(),
|
|
3930
|
+
/** projects.ejected_modules: updates stopped for these Modules, so their folders are the app's. */
|
|
3931
|
+
updatesStoppedModules: moduleList,
|
|
3932
|
+
/** projects.ui_ejected_modules: Customize UI, so their screens are the app's. */
|
|
3933
|
+
customizedUiModules: moduleList,
|
|
3934
|
+
/** projects.external_modules: installed from another app, so the app owns the copy. */
|
|
3935
|
+
externalModules: moduleList,
|
|
3936
|
+
/** projects.removed_modules names: uninstalled on purpose. */
|
|
3937
|
+
uninstalledModules: moduleList
|
|
3938
|
+
});
|
|
3939
|
+
function parseOwnershipRecord(content) {
|
|
3940
|
+
let json;
|
|
3941
|
+
try {
|
|
3942
|
+
json = JSON.parse(content);
|
|
3943
|
+
} catch (error) {
|
|
3944
|
+
return { ok: false, error: `${OWNERSHIP_RECORD_PATH} is not valid JSON: ${String(error)}` };
|
|
3945
|
+
}
|
|
3946
|
+
const parsed = ownershipRecordSchema.safeParse(json);
|
|
3947
|
+
if (!parsed.success) {
|
|
3948
|
+
return {
|
|
3949
|
+
ok: false,
|
|
3950
|
+
error: `${OWNERSHIP_RECORD_PATH} is not a record Stardeck wrote: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ")}`
|
|
3951
|
+
};
|
|
3952
|
+
}
|
|
3953
|
+
return { ok: true, record: parsed.data };
|
|
3954
|
+
}
|
|
3955
|
+
var BLUEPRINT_RELEASE_COMMITTER_EMAIL = "ci@stardeck.ai";
|
|
3956
|
+
var BLUEPRINT_RELEASE_SUBJECT_PREFIX = "publish: ";
|
|
3957
|
+
var STARDECK_OWNED_ALTERNATIVES = [
|
|
3958
|
+
"If the Module has a setting for this, change the setting.",
|
|
3959
|
+
"If a slot contribution can do it, write it in app code outside the Module folder (`apps/web/src/lib/modules/app-extensions.ts` registers it).",
|
|
3960
|
+
"To rework a Module's screens, the app owner can choose **Customize UI** in the dashboard's Modules tab (run **Sync** there first if it is refused). The app then owns that Module's `components/` and `routes/`, and its logic keeps updating.",
|
|
3961
|
+
"If the Module's logic is wrong for every app, report it to Stardeck so every app gets the fix.",
|
|
3962
|
+
"If the app cannot wait, the app owner can stop Stardeck updates for this Module, or for the whole app, in the Modules tab. Both are permanent."
|
|
3963
|
+
];
|
|
3964
|
+
|
|
3907
3965
|
// ../../packages/lib/src/shared/platform-owned-paths.ts
|
|
3908
3966
|
var APP_WEB_PREFIX = "apps/web/";
|
|
3909
3967
|
var BLUEPRINT_LINT_MARKER = `${APP_WEB_PREFIX}eslint-rules/module-boundaries.mjs`;
|
|
@@ -3917,20 +3975,217 @@ var MODULE_INIT_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-init.server.gen.t
|
|
|
3917
3975
|
var MODULE_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-server.gen.ts`;
|
|
3918
3976
|
var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
|
|
3919
3977
|
var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
|
|
3978
|
+
var APP_OWNED_PATHS = [APP_EXTENSIONS_PATH];
|
|
3979
|
+
function platformOwnedSet(input) {
|
|
3980
|
+
const externalModules = input.externalModules ?? [];
|
|
3981
|
+
const removedModules = input.removedModules ?? [];
|
|
3982
|
+
const moduleNames = [
|
|
3983
|
+
...input.moduleNames,
|
|
3984
|
+
...input.ejectedModules,
|
|
3985
|
+
...input.uiEjectedModules,
|
|
3986
|
+
...externalModules,
|
|
3987
|
+
...removedModules
|
|
3988
|
+
];
|
|
3989
|
+
for (const moduleName of moduleNames) {
|
|
3990
|
+
if (!isValidModuleName(moduleName)) {
|
|
3991
|
+
throw new Error(`Invalid Module name: ${moduleName}`);
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
const appOwned = /* @__PURE__ */ new Set([...input.ejectedModules, ...externalModules]);
|
|
3995
|
+
const activeModules = input.moduleNames.filter((name) => !appOwned.has(name));
|
|
3996
|
+
const uiEjectedModules = new Set(input.uiEjectedModules);
|
|
3997
|
+
return {
|
|
3998
|
+
prefixes: [
|
|
3999
|
+
...activeModules.map((name) => `${APP_WEB_PREFIX}src/modules/${name}/`),
|
|
4000
|
+
`${APP_WEB_PREFIX}src/lib/modules/`
|
|
4001
|
+
].sort(),
|
|
4002
|
+
// Build outputs, not source: compose regenerates them from Module source on
|
|
4003
|
+
// every dev/build/lint/typecheck/test run and git ignores them, so there is
|
|
4004
|
+
// nothing for the write refusal or the Blueprint updater to protect.
|
|
4005
|
+
files: [],
|
|
4006
|
+
// Detach dominates: a detached Module has no prefix, so it needs no carve-out.
|
|
4007
|
+
carveOuts: activeModules.filter((name) => uiEjectedModules.has(name)).flatMap(
|
|
4008
|
+
(name) => MODULE_UI_SUBPATHS.map((subpath) => `${APP_WEB_PREFIX}src/modules/${name}/${subpath}`)
|
|
4009
|
+
).sort(),
|
|
4010
|
+
externalModules: [...externalModules].sort(),
|
|
4011
|
+
removedModules: [...removedModules].sort()
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
function appOwnedExcludePathspecs(set) {
|
|
4015
|
+
return [
|
|
4016
|
+
...APP_OWNED_PATHS.map((file) => `:(exclude)${file}`),
|
|
4017
|
+
...set.carveOuts.map((carveOut) => `:(glob,exclude)${carveOut}**`)
|
|
4018
|
+
];
|
|
4019
|
+
}
|
|
4020
|
+
function platformOwnedDiffPathspecs(set) {
|
|
4021
|
+
return [...set.prefixes, ...set.files, ...appOwnedExcludePathspecs(set)];
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
// ../../packages/lib/src/server/module-rail/ownership-guard.ts
|
|
4025
|
+
var STRICT_RECORD = {
|
|
4026
|
+
version: 1,
|
|
4027
|
+
stardeckUpdates: true,
|
|
4028
|
+
updatesStoppedModules: [],
|
|
4029
|
+
customizedUiModules: [],
|
|
4030
|
+
externalModules: [],
|
|
4031
|
+
uninstalledModules: []
|
|
4032
|
+
};
|
|
4033
|
+
var MANIFEST_PATH = new RegExp(
|
|
4034
|
+
`^${MODULES_SANDBOX_DIR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/([^/]+)/${MODULE_MANIFEST_FILENAME.replace(".", "\\.")}$`
|
|
4035
|
+
);
|
|
4036
|
+
function realpath(dir) {
|
|
4037
|
+
try {
|
|
4038
|
+
return import_node_fs.default.realpathSync(dir);
|
|
4039
|
+
} catch {
|
|
4040
|
+
return import_node_path.default.resolve(dir);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
function zList(stdout) {
|
|
4044
|
+
return stdout.split("\0").filter(Boolean);
|
|
4045
|
+
}
|
|
4046
|
+
async function findRelease(git) {
|
|
4047
|
+
const found = await git([
|
|
4048
|
+
"rev-list",
|
|
4049
|
+
"-n1",
|
|
4050
|
+
"--topo-order",
|
|
4051
|
+
`--committer=${BLUEPRINT_RELEASE_COMMITTER_EMAIL.replace(/\./g, "\\.")}`,
|
|
4052
|
+
`--grep=^${BLUEPRINT_RELEASE_SUBJECT_PREFIX}`,
|
|
4053
|
+
"HEAD"
|
|
4054
|
+
]);
|
|
4055
|
+
const sha = found.status === 0 ? found.stdout.trim() : "";
|
|
4056
|
+
return sha || null;
|
|
4057
|
+
}
|
|
4058
|
+
async function noReleaseReason(git) {
|
|
4059
|
+
const shallow = await git(["rev-parse", "--is-shallow-repository"]);
|
|
4060
|
+
if (shallow.status === 0 && shallow.stdout.trim() === "true") {
|
|
4061
|
+
return {
|
|
4062
|
+
status: "skipped",
|
|
4063
|
+
warning: true,
|
|
4064
|
+
reason: "this is a shallow clone, so Stardeck's version of the Module files is not in its history. Run `git fetch --unshallow` and lint again to check that no Stardeck-owned file changed."
|
|
4065
|
+
};
|
|
4066
|
+
}
|
|
4067
|
+
return {
|
|
4068
|
+
status: "skipped",
|
|
4069
|
+
warning: false,
|
|
4070
|
+
reason: "no Stardeck release in this repo's history, so it holds no Stardeck-owned files"
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
function readRecord(repoRoot) {
|
|
4074
|
+
let content;
|
|
4075
|
+
try {
|
|
4076
|
+
content = import_node_fs.default.readFileSync(import_node_path.default.join(repoRoot, OWNERSHIP_RECORD_PATH), "utf8");
|
|
4077
|
+
} catch (error) {
|
|
4078
|
+
if (error.code === "ENOENT") {
|
|
4079
|
+
return { ok: true, record: STRICT_RECORD, found: false };
|
|
4080
|
+
}
|
|
4081
|
+
throw error;
|
|
4082
|
+
}
|
|
4083
|
+
const parsed = parseOwnershipRecord(content);
|
|
4084
|
+
return parsed.ok ? { ok: true, record: parsed.record, found: true } : parsed;
|
|
4085
|
+
}
|
|
4086
|
+
async function releaseModules(git, release, record) {
|
|
4087
|
+
const listed = await git([
|
|
4088
|
+
"ls-tree",
|
|
4089
|
+
"-r",
|
|
4090
|
+
"-z",
|
|
4091
|
+
"--name-only",
|
|
4092
|
+
release,
|
|
4093
|
+
"--",
|
|
4094
|
+
MODULES_SANDBOX_DIR
|
|
4095
|
+
]);
|
|
4096
|
+
if (listed.status !== 0) throw new Error(`git ls-tree ${release} failed`);
|
|
4097
|
+
const uninstalled = new Set(record.uninstalledModules);
|
|
4098
|
+
return zList(listed.stdout).map((file) => MANIFEST_PATH.exec(file)?.[1]).filter((name) => !!name && !uninstalled.has(name));
|
|
4099
|
+
}
|
|
4100
|
+
async function checkStardeckOwnedFiles(input) {
|
|
4101
|
+
const { git, repoRoot } = input;
|
|
4102
|
+
const top = await git(["rev-parse", "--show-toplevel"]);
|
|
4103
|
+
if (top.status !== 0) {
|
|
4104
|
+
return { status: "skipped", warning: false, reason: "not a git checkout" };
|
|
4105
|
+
}
|
|
4106
|
+
if (realpath(top.stdout.trim()) !== realpath(repoRoot)) {
|
|
4107
|
+
return {
|
|
4108
|
+
status: "skipped",
|
|
4109
|
+
warning: false,
|
|
4110
|
+
reason: `${repoRoot} is not the root of its git repo, so it is not an app's own repo`
|
|
4111
|
+
};
|
|
4112
|
+
}
|
|
4113
|
+
const release = await findRelease(git);
|
|
4114
|
+
if (!release) return noReleaseReason(git);
|
|
4115
|
+
const read = readRecord(repoRoot);
|
|
4116
|
+
if (!read.ok) return { status: "invalid-record", error: read.error };
|
|
4117
|
+
const { record } = read;
|
|
4118
|
+
if (!record.stardeckUpdates) {
|
|
4119
|
+
return {
|
|
4120
|
+
status: "skipped",
|
|
4121
|
+
warning: false,
|
|
4122
|
+
reason: "this app stopped all Stardeck updates, so every file is the app's"
|
|
4123
|
+
};
|
|
4124
|
+
}
|
|
4125
|
+
const moduleNames = await releaseModules(git, release, record);
|
|
4126
|
+
const set = platformOwnedSet({
|
|
4127
|
+
moduleNames,
|
|
4128
|
+
ejectedModules: record.updatesStoppedModules,
|
|
4129
|
+
uiEjectedModules: record.customizedUiModules,
|
|
4130
|
+
externalModules: record.externalModules,
|
|
4131
|
+
removedModules: record.uninstalledModules
|
|
4132
|
+
});
|
|
4133
|
+
const pathspecs = [
|
|
4134
|
+
...platformOwnedDiffPathspecs(set),
|
|
4135
|
+
...record.uninstalledModules.map(
|
|
4136
|
+
(name) => `:(glob,exclude)${MODULES_SANDBOX_DIR}/*/enhancements/${name}/**`
|
|
4137
|
+
)
|
|
4138
|
+
];
|
|
4139
|
+
const [changed, added] = await Promise.all([
|
|
4140
|
+
git([
|
|
4141
|
+
"diff",
|
|
4142
|
+
"--name-only",
|
|
4143
|
+
"-z",
|
|
4144
|
+
"--no-renames",
|
|
4145
|
+
"--no-ext-diff",
|
|
4146
|
+
release,
|
|
4147
|
+
"--",
|
|
4148
|
+
...pathspecs
|
|
4149
|
+
]),
|
|
4150
|
+
git(["ls-files", "-z", "--others", "--exclude-standard", "--", ...pathspecs])
|
|
4151
|
+
]);
|
|
4152
|
+
if (changed.status !== 0 || added.status !== 0) {
|
|
4153
|
+
throw new Error(`git could not compare the checkout with Stardeck's version ${release}`);
|
|
4154
|
+
}
|
|
4155
|
+
const paths = [.../* @__PURE__ */ new Set([...zList(changed.stdout), ...zList(added.stdout)])].sort();
|
|
4156
|
+
console.log(
|
|
4157
|
+
`[OwnershipGuard] release=${release.slice(0, 12)} record=${read.found ? "found" : "missing"} modules=${moduleNames.length} carveOuts=${set.carveOuts.length} changed=${paths.length}`
|
|
4158
|
+
);
|
|
4159
|
+
return paths.length > 0 ? { status: "changed", release, paths } : { status: "clean", release };
|
|
4160
|
+
}
|
|
4161
|
+
function formatOwnershipGuardFailure(result) {
|
|
4162
|
+
if (result.status === "invalid-record") {
|
|
4163
|
+
return `${result.error}
|
|
4164
|
+
Restore it with \`git checkout origin/HEAD -- ${OWNERSHIP_RECORD_PATH}\`. Stardeck writes this file; ownership changes in the dashboard's Modules tab.`;
|
|
4165
|
+
}
|
|
4166
|
+
const count = `${result.paths.length} Stardeck-owned ${result.paths.length === 1 ? "file" : "files"}`;
|
|
4167
|
+
return [
|
|
4168
|
+
`${count} changed from Stardeck's version (${result.release.slice(0, 12)}). The next Stardeck update replaces them, so these changes would be lost:`,
|
|
4169
|
+
...result.paths.map((file) => ` - ${file}`),
|
|
4170
|
+
"",
|
|
4171
|
+
`Undo them (\`git checkout ${result.release.slice(0, 12)} -- <path>\`, or delete a new file) and do one of these instead:`,
|
|
4172
|
+
...STARDECK_OWNED_ALTERNATIVES.map((line) => ` - ${line.replace(/\*\*/g, "")}`)
|
|
4173
|
+
].join("\n");
|
|
4174
|
+
}
|
|
3920
4175
|
|
|
3921
4176
|
// src/cli.ts
|
|
3922
4177
|
var import_typescript7 = __toESM(require("typescript"));
|
|
3923
|
-
var
|
|
4178
|
+
var import_node_path5 = __toESM(require("path"));
|
|
3924
4179
|
|
|
3925
4180
|
// src/index.ts
|
|
3926
4181
|
var import_node_child_process2 = require("child_process");
|
|
3927
|
-
var
|
|
3928
|
-
var
|
|
4182
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
4183
|
+
var import_node_path4 = __toESM(require("path"));
|
|
3929
4184
|
|
|
3930
4185
|
// ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
|
|
3931
4186
|
var import_node_child_process = require("child_process");
|
|
3932
|
-
var
|
|
3933
|
-
var
|
|
4187
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
4188
|
+
var import_node_path2 = __toESM(require("path"));
|
|
3934
4189
|
function localFsSandbox(root) {
|
|
3935
4190
|
return {
|
|
3936
4191
|
async exec(command, options) {
|
|
@@ -3954,13 +4209,13 @@ ${result.error}`);
|
|
|
3954
4209
|
},
|
|
3955
4210
|
async fileExists(relative) {
|
|
3956
4211
|
try {
|
|
3957
|
-
return
|
|
4212
|
+
return import_node_fs2.default.statSync(import_node_path2.default.join(root, relative)).isFile();
|
|
3958
4213
|
} catch {
|
|
3959
4214
|
return false;
|
|
3960
4215
|
}
|
|
3961
4216
|
},
|
|
3962
4217
|
async readFile(relative) {
|
|
3963
|
-
return
|
|
4218
|
+
return import_node_fs2.default.readFileSync(import_node_path2.default.join(root, relative), "utf8");
|
|
3964
4219
|
},
|
|
3965
4220
|
async fetchRemoteRef() {
|
|
3966
4221
|
throw new Error("[ModuleRail] local filesystem sandbox cannot reach the network");
|
|
@@ -3982,6 +4237,7 @@ var JSON_SECRET_PAIR = new RegExp(
|
|
|
3982
4237
|
"g"
|
|
3983
4238
|
);
|
|
3984
4239
|
var SECRET_QUOTE_OPENER = new RegExp(String.raw`\b${SECRET_NAME}\s*=\s*(['"])`, "g");
|
|
4240
|
+
var SHELL_QUOTE_CONTINUATION = String.raw`\''`;
|
|
3985
4241
|
function redactSecrets(text) {
|
|
3986
4242
|
return text.replace(/x-access-token:[^@\s]+@/g, "x-access-token:***@").replace(/https:\/\/(?!x-access-token:)[^/\s:@]+:[^/\s@]+@/gi, "https://***:***@").replace(/\b(gh[pousr]_)[A-Za-z0-9]+/g, "$1***").replace(/\b(github_pat_)[A-Za-z0-9_]+/g, "$1***").replace(/\b(postgres(?:ql)?:\/\/)([^:\s/@]+):([^@\s/]+)@/gi, "$1$2:***@").replace(/\b(npm_[A-Za-z0-9]+)/g, "npm_***").replace(/(_authToken=)([^\s&]+)/gi, "$1***").replace(SECRET_ASSIGNMENT, "$1=***").replace(JSON_SECRET_PAIR, '"$1"$2"***"').replace(/\b(Authorization:\s*(?:Bearer|Basic)\s+)\S+/gi, "$1***");
|
|
3987
4243
|
}
|
|
@@ -3992,28 +4248,28 @@ function shellQuote(value) {
|
|
|
3992
4248
|
}
|
|
3993
4249
|
|
|
3994
4250
|
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
3995
|
-
var
|
|
4251
|
+
var import_zod7 = require("zod");
|
|
3996
4252
|
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
3997
|
-
var composeInputsSchema =
|
|
3998
|
-
connectedStores:
|
|
3999
|
-
|
|
4000
|
-
storeId:
|
|
4001
|
-
slug:
|
|
4253
|
+
var composeInputsSchema = import_zod7.z.strictObject({
|
|
4254
|
+
connectedStores: import_zod7.z.array(
|
|
4255
|
+
import_zod7.z.strictObject({
|
|
4256
|
+
storeId: import_zod7.z.string().min(1),
|
|
4257
|
+
slug: import_zod7.z.string().min(1),
|
|
4002
4258
|
// A row with an empty binding key must not fail a deploy: the rail
|
|
4003
4259
|
// treats falsy as "no binding key", so normalise rather than reject.
|
|
4004
|
-
bindingKey:
|
|
4005
|
-
accessLevel:
|
|
4260
|
+
bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
|
|
4261
|
+
accessLevel: import_zod7.z.enum(["read", "write", "admin"]),
|
|
4006
4262
|
// Defaulted so a file written before the platform emitted store types
|
|
4007
4263
|
// (database rows only) still parses. The platform starts writing
|
|
4008
4264
|
// storage rows, with `type` set, once forks run a compose that reads it.
|
|
4009
|
-
type:
|
|
4265
|
+
type: import_zod7.z.enum(["database", "storage"]).default("database")
|
|
4010
4266
|
})
|
|
4011
4267
|
).default([]),
|
|
4012
|
-
installs:
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
installedVersion:
|
|
4016
|
-
datastoreBindings:
|
|
4268
|
+
installs: import_zod7.z.record(
|
|
4269
|
+
import_zod7.z.string(),
|
|
4270
|
+
import_zod7.z.strictObject({
|
|
4271
|
+
installedVersion: import_zod7.z.string().min(1),
|
|
4272
|
+
datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
|
|
4017
4273
|
})
|
|
4018
4274
|
).default({})
|
|
4019
4275
|
});
|
|
@@ -4173,13 +4429,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
|
4173
4429
|
function createMessageTree() {
|
|
4174
4430
|
return /* @__PURE__ */ Object.create(null);
|
|
4175
4431
|
}
|
|
4176
|
-
function assertMessageTree(value,
|
|
4432
|
+
function assertMessageTree(value, path6) {
|
|
4177
4433
|
if (typeof value === "string") return;
|
|
4178
4434
|
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
4179
|
-
throw new Error(`Invalid message at ${
|
|
4435
|
+
throw new Error(`Invalid message at ${path6}: expected string or nested object`);
|
|
4180
4436
|
}
|
|
4181
4437
|
for (const [key, child] of Object.entries(value)) {
|
|
4182
|
-
assertMessageTree(child, `${
|
|
4438
|
+
assertMessageTree(child, `${path6}.${key}`);
|
|
4183
4439
|
}
|
|
4184
4440
|
}
|
|
4185
4441
|
function cloneTree(value) {
|
|
@@ -4189,17 +4445,17 @@ function cloneTree(value) {
|
|
|
4189
4445
|
}
|
|
4190
4446
|
return clone;
|
|
4191
4447
|
}
|
|
4192
|
-
function collectLeafPaths(value,
|
|
4448
|
+
function collectLeafPaths(value, path6, out) {
|
|
4193
4449
|
if (typeof value === "string") {
|
|
4194
|
-
out.push(
|
|
4450
|
+
out.push(path6);
|
|
4195
4451
|
return;
|
|
4196
4452
|
}
|
|
4197
|
-
assertMessageTree(value,
|
|
4198
|
-
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${
|
|
4453
|
+
assertMessageTree(value, path6);
|
|
4454
|
+
for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path6}.${key}`, out);
|
|
4199
4455
|
}
|
|
4200
|
-
function overrideInto(target, source, owner,
|
|
4456
|
+
function overrideInto(target, source, owner, path6, missing) {
|
|
4201
4457
|
for (const [key, value] of Object.entries(source)) {
|
|
4202
|
-
const nextPath =
|
|
4458
|
+
const nextPath = path6 ? `${path6}.${key}` : key;
|
|
4203
4459
|
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
4204
4460
|
collectLeafPaths(value, nextPath, missing);
|
|
4205
4461
|
continue;
|
|
@@ -4226,9 +4482,9 @@ function overrideInto(target, source, owner, path5, missing) {
|
|
|
4226
4482
|
function composeLocaleMessagesForPlan(layers) {
|
|
4227
4483
|
const result = createMessageTree();
|
|
4228
4484
|
const leafOwners = /* @__PURE__ */ new Map();
|
|
4229
|
-
function mergeInto(target, source, owner,
|
|
4485
|
+
function mergeInto(target, source, owner, path6) {
|
|
4230
4486
|
for (const [key, value] of Object.entries(source)) {
|
|
4231
|
-
const nextPath =
|
|
4487
|
+
const nextPath = path6 ? `${path6}.${key}` : key;
|
|
4232
4488
|
const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
|
|
4233
4489
|
const existing = hasExisting ? target[key] : void 0;
|
|
4234
4490
|
if (typeof value === "string") {
|
|
@@ -4789,8 +5045,8 @@ var METHOD_ORDER = {
|
|
|
4789
5045
|
function compareNames2(a, b) {
|
|
4790
5046
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
4791
5047
|
}
|
|
4792
|
-
function endpointPairKey(method,
|
|
4793
|
-
return `${method} ${
|
|
5048
|
+
function endpointPairKey(method, path6) {
|
|
5049
|
+
return `${method} ${path6}`;
|
|
4794
5050
|
}
|
|
4795
5051
|
function apiPathToRouteFile(apiPath) {
|
|
4796
5052
|
if (!apiPath.startsWith("/api/")) {
|
|
@@ -4804,11 +5060,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
|
|
|
4804
5060
|
}
|
|
4805
5061
|
var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4806
5062
|
var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
|
|
4807
|
-
function appRouteFileToNextRelativePath(
|
|
4808
|
-
const pagePath = appPageFileToNextRelativePath(
|
|
5063
|
+
function appRouteFileToNextRelativePath(path6) {
|
|
5064
|
+
const pagePath = appPageFileToNextRelativePath(path6, APP_PACKAGE_DIR);
|
|
4809
5065
|
if (pagePath != null) return pagePath;
|
|
4810
|
-
if (!
|
|
4811
|
-
const relative =
|
|
5066
|
+
if (!path6.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
|
|
5067
|
+
const relative = path6.slice(APP_ROUTE_FILE_PREFIX.length);
|
|
4812
5068
|
if (!relative.startsWith("api/")) return null;
|
|
4813
5069
|
const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
|
|
4814
5070
|
if (!suffix) return null;
|
|
@@ -4827,6 +5083,9 @@ function enhancementImport(owner, peer) {
|
|
|
4827
5083
|
function scopeLabel(scope) {
|
|
4828
5084
|
return scope.kind === "root" ? "root" : `enhancement:${scope.peer}`;
|
|
4829
5085
|
}
|
|
5086
|
+
function endpointMarkerOwnsStub(marker, stub) {
|
|
5087
|
+
return stub != null && marker?.kind === "endpoint" && marker.owner === stub.owner;
|
|
5088
|
+
}
|
|
4830
5089
|
function formatEndpointMarker(owner, scope) {
|
|
4831
5090
|
return `// ${MODULE_STUB_MARKER} owner=${owner} scope=${scopeLabel(scope)}`;
|
|
4832
5091
|
}
|
|
@@ -4900,9 +5159,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
|
|
|
4900
5159
|
const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
|
|
4901
5160
|
return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
|
|
4902
5161
|
}
|
|
4903
|
-
function parseLegacyApiAdapter(
|
|
4904
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4905
|
-
if (expectedMarkerKindForPath(
|
|
5162
|
+
function parseLegacyApiAdapter(path6, source) {
|
|
5163
|
+
const physicalPath = appRouteFileToNextRelativePath(path6);
|
|
5164
|
+
if (expectedMarkerKindForPath(path6) !== "endpoint" || physicalPath == null) return null;
|
|
4906
5165
|
const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
4907
5166
|
const match = firstLine.match(
|
|
4908
5167
|
new RegExp(
|
|
@@ -4912,7 +5171,7 @@ function parseLegacyApiAdapter(path5, source) {
|
|
|
4912
5171
|
if (!match || match[2] !== physicalPath) return null;
|
|
4913
5172
|
const owner = match[1];
|
|
4914
5173
|
if (!isValidModuleName(owner)) return null;
|
|
4915
|
-
const file = import_typescript4.default.createSourceFile(
|
|
5174
|
+
const file = import_typescript4.default.createSourceFile(path6, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
|
|
4916
5175
|
if (file.parseDiagnostics.length > 0) {
|
|
4917
5176
|
return null;
|
|
4918
5177
|
}
|
|
@@ -4954,9 +5213,9 @@ function parseLegacyApiAdapter(path5, source) {
|
|
|
4954
5213
|
}
|
|
4955
5214
|
return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
|
|
4956
5215
|
}
|
|
4957
|
-
function parseLegacyRouteAdapter(
|
|
4958
|
-
const physicalPath = appRouteFileToNextRelativePath(
|
|
4959
|
-
if (expectedMarkerKindForPath(
|
|
5216
|
+
function parseLegacyRouteAdapter(path6, source) {
|
|
5217
|
+
const physicalPath = appRouteFileToNextRelativePath(path6);
|
|
5218
|
+
if (expectedMarkerKindForPath(path6) !== "route" || physicalPath == null) return null;
|
|
4960
5219
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
4961
5220
|
const match = normalized.match(
|
|
4962
5221
|
new RegExp(
|
|
@@ -4968,30 +5227,30 @@ function parseLegacyRouteAdapter(path5, source) {
|
|
|
4968
5227
|
const nextRelativePath = match[2];
|
|
4969
5228
|
return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
|
|
4970
5229
|
}
|
|
4971
|
-
function legacyRouteAdapterMatchesStub(
|
|
4972
|
-
const legacy = parseLegacyRouteAdapter(
|
|
5230
|
+
function legacyRouteAdapterMatchesStub(path6, source, stub) {
|
|
5231
|
+
const legacy = parseLegacyRouteAdapter(path6, source);
|
|
4973
5232
|
return legacy != null && legacy.owner === stub.owner;
|
|
4974
5233
|
}
|
|
4975
5234
|
var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
|
|
4976
5235
|
var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
|
|
4977
5236
|
var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
|
|
4978
|
-
function expectedMarkerKindForPath(
|
|
4979
|
-
if (
|
|
4980
|
-
if (
|
|
4981
|
-
if (
|
|
4982
|
-
if (
|
|
4983
|
-
if (
|
|
4984
|
-
if (
|
|
4985
|
-
if (
|
|
4986
|
-
if (
|
|
5237
|
+
function expectedMarkerKindForPath(path6) {
|
|
5238
|
+
if (path6 === MODULES_GEN_PATH) return "registry";
|
|
5239
|
+
if (path6 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
|
|
5240
|
+
if (path6 === MODULE_DATASTORES_GEN_PATH) return "datastores";
|
|
5241
|
+
if (path6 === MODULE_I18N_GEN_PATH) return "i18n";
|
|
5242
|
+
if (path6 === MODULE_INIT_SERVER_GEN_PATH) return "init";
|
|
5243
|
+
if (path6 === MODULE_SERVER_GEN_PATH) return "server";
|
|
5244
|
+
if (path6.startsWith(API_ROUTE_DIR_PREFIX) && path6.endsWith("/route.ts")) return "endpoint";
|
|
5245
|
+
if (path6.startsWith(APP_DIR_PREFIX) && !path6.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path6.endsWith(suffix))) {
|
|
4987
5246
|
return "route";
|
|
4988
5247
|
}
|
|
4989
5248
|
return null;
|
|
4990
5249
|
}
|
|
4991
|
-
function markerCongruentWithPath(
|
|
4992
|
-
if (expectedMarkerKindForPath(
|
|
5250
|
+
function markerCongruentWithPath(path6, marker) {
|
|
5251
|
+
if (expectedMarkerKindForPath(path6) !== marker.kind) return false;
|
|
4993
5252
|
if (marker.kind === "route") {
|
|
4994
|
-
const derived = appPageFileToNextRelativePath(
|
|
5253
|
+
const derived = appPageFileToNextRelativePath(path6, APP_PACKAGE_DIR);
|
|
4995
5254
|
return derived != null && derived === marker.nextRelativePath;
|
|
4996
5255
|
}
|
|
4997
5256
|
return true;
|
|
@@ -5267,8 +5526,8 @@ function buildGeneratedModuleRegistry(input) {
|
|
|
5267
5526
|
function activeEnhancementPeers(registryModule) {
|
|
5268
5527
|
return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
|
|
5269
5528
|
}
|
|
5270
|
-
function endpointBucketKey(
|
|
5271
|
-
return `${
|
|
5529
|
+
function endpointBucketKey(path6, owner, scope) {
|
|
5530
|
+
return `${path6}\0${owner}\0${scopeLabel(scope)}`;
|
|
5272
5531
|
}
|
|
5273
5532
|
function planRouteStubs(input) {
|
|
5274
5533
|
const endpointBuckets = /* @__PURE__ */ new Map();
|
|
@@ -5392,7 +5651,7 @@ function planRouteStubs(input) {
|
|
|
5392
5651
|
for (const entry of entries) {
|
|
5393
5652
|
const nextRelativePath = entry.nextRelativePath;
|
|
5394
5653
|
const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
|
|
5395
|
-
const
|
|
5654
|
+
const path6 = routeEntryToAppPageFile(nextRelativePath);
|
|
5396
5655
|
const existing = routeDestinationOwners.get(destination);
|
|
5397
5656
|
if (existing) {
|
|
5398
5657
|
throw new Error(
|
|
@@ -5402,13 +5661,13 @@ function planRouteStubs(input) {
|
|
|
5402
5661
|
routeDestinationOwners.set(destination, {
|
|
5403
5662
|
owner: regMod.name,
|
|
5404
5663
|
nextRelativePath,
|
|
5405
|
-
path:
|
|
5664
|
+
path: path6
|
|
5406
5665
|
});
|
|
5407
5666
|
const importModule = routeEntryImport(regMod.name, nextRelativePath);
|
|
5408
5667
|
const analyzed = analyzeRouteEntryPath(nextRelativePath);
|
|
5409
5668
|
const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
|
|
5410
5669
|
routeStubs.push({
|
|
5411
|
-
path:
|
|
5670
|
+
path: path6,
|
|
5412
5671
|
owner: regMod.name,
|
|
5413
5672
|
nextRelativePath,
|
|
5414
5673
|
importModule,
|
|
@@ -5469,16 +5728,16 @@ function planCompositionArtifacts(input) {
|
|
|
5469
5728
|
...stubs.routeStubs.map((stub) => stub.path)
|
|
5470
5729
|
]);
|
|
5471
5730
|
const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
|
|
5472
|
-
const isProvenStaleDeletionCandidate = (
|
|
5473
|
-
if (
|
|
5474
|
-
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5475
|
-
if (desiredPaths.has(
|
|
5476
|
-
if (!existingGeneratedPathSet.has(
|
|
5477
|
-
const legacy = parseLegacyApiAdapter(
|
|
5731
|
+
const isProvenStaleDeletionCandidate = (path6, content) => {
|
|
5732
|
+
if (path6 === MODULES_GEN_PATH) return false;
|
|
5733
|
+
if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path6)) return false;
|
|
5734
|
+
if (desiredPaths.has(path6)) return false;
|
|
5735
|
+
if (!existingGeneratedPathSet.has(path6)) return false;
|
|
5736
|
+
const legacy = parseLegacyApiAdapter(path6, content);
|
|
5478
5737
|
if (legacy) return true;
|
|
5479
|
-
if (parseLegacyRouteAdapter(
|
|
5738
|
+
if (parseLegacyRouteAdapter(path6, content)) return true;
|
|
5480
5739
|
const marker = parseCompositionArtifactMarker(content);
|
|
5481
|
-
return marker != null && markerCongruentWithPath(
|
|
5740
|
+
return marker != null && markerCongruentWithPath(path6, marker);
|
|
5482
5741
|
};
|
|
5483
5742
|
const plannedByDestination = /* @__PURE__ */ new Map();
|
|
5484
5743
|
for (const stub of stubs.routeStubs) {
|
|
@@ -5528,7 +5787,7 @@ function planCompositionArtifacts(input) {
|
|
|
5528
5787
|
const legacy = parseLegacyApiAdapter(pagePath, content);
|
|
5529
5788
|
const plannedRoute = plannedRouteByPath.get(pagePath);
|
|
5530
5789
|
const plannedEndpoint = plannedEndpointByPath.get(pagePath);
|
|
5531
|
-
const isExactPlannedMatch = plannedRoute != null && marker != null && marker.kind === "route" && marker.owner === plannedRoute.owner && marker.nextRelativePath === plannedRoute.nextRelativePath || plannedEndpoint != null && (
|
|
5790
|
+
const isExactPlannedMatch = plannedRoute != null && marker != null && marker.kind === "route" && marker.owner === plannedRoute.owner && marker.nextRelativePath === plannedRoute.nextRelativePath || plannedEndpoint != null && (endpointMarkerOwnsStub(marker, plannedEndpoint) || legacy != null) || plannedRoute != null && legacyRouteAdapterMatchesStub(pagePath, content, plannedRoute);
|
|
5532
5791
|
if (isExactPlannedMatch) {
|
|
5533
5792
|
continue;
|
|
5534
5793
|
}
|
|
@@ -5632,13 +5891,13 @@ function planCompositionArtifacts(input) {
|
|
|
5632
5891
|
for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
|
|
5633
5892
|
const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
|
|
5634
5893
|
const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
|
|
5635
|
-
for (const [
|
|
5636
|
-
const existing = input.existingFiles[
|
|
5894
|
+
for (const [path6] of desired) {
|
|
5895
|
+
const existing = input.existingFiles[path6];
|
|
5637
5896
|
if (existing == null) continue;
|
|
5638
5897
|
let marker = parseCompositionArtifactMarker(existing);
|
|
5639
|
-
const plannedEndpoint = endpointStubByPath.get(
|
|
5640
|
-
const plannedRoute = routeStubByPath.get(
|
|
5641
|
-
const legacy = parseLegacyApiAdapter(
|
|
5898
|
+
const plannedEndpoint = endpointStubByPath.get(path6);
|
|
5899
|
+
const plannedRoute = routeStubByPath.get(path6);
|
|
5900
|
+
const legacy = parseLegacyApiAdapter(path6, existing);
|
|
5642
5901
|
if (legacy && plannedEndpoint) {
|
|
5643
5902
|
marker = {
|
|
5644
5903
|
kind: "endpoint",
|
|
@@ -5646,66 +5905,64 @@ function planCompositionArtifacts(input) {
|
|
|
5646
5905
|
scope: plannedEndpoint.scope
|
|
5647
5906
|
};
|
|
5648
5907
|
}
|
|
5649
|
-
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(
|
|
5908
|
+
if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path6, existing, plannedRoute)) {
|
|
5650
5909
|
marker = {
|
|
5651
5910
|
kind: "route",
|
|
5652
5911
|
owner: plannedRoute.owner,
|
|
5653
5912
|
nextRelativePath: plannedRoute.nextRelativePath
|
|
5654
5913
|
};
|
|
5655
5914
|
}
|
|
5656
|
-
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(
|
|
5915
|
+
const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path6);
|
|
5657
5916
|
if (!marker) {
|
|
5658
5917
|
return {
|
|
5659
5918
|
ok: false,
|
|
5660
|
-
error:
|
|
5661
|
-
path:
|
|
5919
|
+
error: path6 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path6}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path6}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path6}`,
|
|
5920
|
+
path: path6
|
|
5662
5921
|
};
|
|
5663
5922
|
}
|
|
5664
|
-
const expectedKind = expectedMarkerKindForPath(
|
|
5923
|
+
const expectedKind = expectedMarkerKindForPath(path6);
|
|
5665
5924
|
if (marker.kind !== expectedKind) {
|
|
5666
5925
|
return {
|
|
5667
5926
|
ok: false,
|
|
5668
|
-
error:
|
|
5669
|
-
path:
|
|
5927
|
+
error: path6 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path6} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path6} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path6} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
|
|
5928
|
+
path: path6
|
|
5670
5929
|
};
|
|
5671
5930
|
}
|
|
5672
5931
|
if (marker.kind === "endpoint") {
|
|
5673
|
-
const stub = endpointStubByPath.get(
|
|
5674
|
-
|
|
5675
|
-
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5676
|
-
if (!ownerMatches || !scopeMatches) {
|
|
5932
|
+
const stub = endpointStubByPath.get(path6);
|
|
5933
|
+
if (!endpointMarkerOwnsStub(marker, stub)) {
|
|
5677
5934
|
return {
|
|
5678
5935
|
ok: false,
|
|
5679
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5680
|
-
path:
|
|
5936
|
+
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path6} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
|
|
5937
|
+
path: path6,
|
|
5681
5938
|
owner: stub?.owner
|
|
5682
5939
|
};
|
|
5683
5940
|
}
|
|
5684
5941
|
} else if (marker.kind === "route") {
|
|
5685
|
-
const stub = routeStubByPath.get(
|
|
5942
|
+
const stub = routeStubByPath.get(path6);
|
|
5686
5943
|
const ownerMatches = stub != null && marker.owner === stub.owner;
|
|
5687
5944
|
const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
|
|
5688
5945
|
if (!ownerMatches || !pathMatches) {
|
|
5689
5946
|
return {
|
|
5690
5947
|
ok: false,
|
|
5691
|
-
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${
|
|
5692
|
-
path:
|
|
5948
|
+
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path6} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
|
|
5949
|
+
path: path6,
|
|
5693
5950
|
owner: stub?.owner
|
|
5694
5951
|
};
|
|
5695
5952
|
}
|
|
5696
5953
|
}
|
|
5697
5954
|
}
|
|
5698
5955
|
const deletes = [];
|
|
5699
|
-
for (const
|
|
5700
|
-
const existing = input.existingFiles[
|
|
5956
|
+
for (const path6 of input.existingGeneratedPaths) {
|
|
5957
|
+
const existing = input.existingFiles[path6];
|
|
5701
5958
|
if (existing == null) continue;
|
|
5702
|
-
if (!isProvenStaleDeletionCandidate(
|
|
5959
|
+
if (!isProvenStaleDeletionCandidate(path6, existing)) {
|
|
5703
5960
|
continue;
|
|
5704
5961
|
}
|
|
5705
|
-
deletes.push(
|
|
5962
|
+
deletes.push(path6);
|
|
5706
5963
|
}
|
|
5707
5964
|
deletes.sort(compareNames2);
|
|
5708
|
-
const writes = [...desired.entries()].filter(([
|
|
5965
|
+
const writes = [...desired.entries()].filter(([path6, content]) => input.existingFiles[path6] !== content).map(([path6, content]) => ({ path: path6, content })).sort((a, b) => compareNames2(a.path, b.path));
|
|
5709
5966
|
return {
|
|
5710
5967
|
ok: true,
|
|
5711
5968
|
plan: {
|
|
@@ -5976,14 +6233,14 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
|
|
|
5976
6233
|
}
|
|
5977
6234
|
|
|
5978
6235
|
// ../../packages/lib/src/shared/module-composition.ts
|
|
5979
|
-
var
|
|
5980
|
-
var creationSourceBlankSchema =
|
|
5981
|
-
type:
|
|
6236
|
+
var import_zod8 = require("zod");
|
|
6237
|
+
var creationSourceBlankSchema = import_zod8.z.object({
|
|
6238
|
+
type: import_zod8.z.literal("blank")
|
|
5982
6239
|
});
|
|
5983
|
-
var creationSourceBlueprintSchema =
|
|
5984
|
-
type:
|
|
6240
|
+
var creationSourceBlueprintSchema = import_zod8.z.object({
|
|
6241
|
+
type: import_zod8.z.literal("blueprint"),
|
|
5985
6242
|
/** Internal template/fork project id — keep template naming in identifiers. */
|
|
5986
|
-
blueprintProjectId:
|
|
6243
|
+
blueprintProjectId: import_zod8.z.string().uuid(),
|
|
5987
6244
|
/**
|
|
5988
6245
|
* `null`/absent means every feature Module: the platform stores that as the
|
|
5989
6246
|
* explicit list of feature Modules in the published Blueprint version the fork
|
|
@@ -5991,192 +6248,197 @@ var creationSourceBlueprintSchema = import_zod7.z.object({
|
|
|
5991
6248
|
* a custom app: no feature Module, Library Modules only (`resolvePlatformEnv`
|
|
5992
6249
|
* emits it as a set value naming no Module).
|
|
5993
6250
|
*/
|
|
5994
|
-
enabledModules:
|
|
6251
|
+
enabledModules: import_zod8.z.array(moduleNameSchema).max(100).nullable().optional(),
|
|
6252
|
+
/**
|
|
6253
|
+
* The kind of business (a preset id the published version defines). Its Modules are built
|
|
6254
|
+
* and `enabledModules` adds extras on top; for the general kind `enabledModules` is the pick.
|
|
6255
|
+
*/
|
|
6256
|
+
businessKind: import_zod8.z.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional()
|
|
5995
6257
|
});
|
|
5996
|
-
var creationSourceSchema =
|
|
6258
|
+
var creationSourceSchema = import_zod8.z.discriminatedUnion("type", [
|
|
5997
6259
|
creationSourceBlankSchema,
|
|
5998
6260
|
creationSourceBlueprintSchema
|
|
5999
6261
|
]);
|
|
6000
|
-
var moduleInstallHandoffSchema =
|
|
6001
|
-
moduleName:
|
|
6002
|
-
homeProjectId:
|
|
6003
|
-
installedVersion:
|
|
6004
|
-
baselineSha:
|
|
6005
|
-
mounts:
|
|
6006
|
-
|
|
6007
|
-
group:
|
|
6008
|
-
segment:
|
|
6262
|
+
var moduleInstallHandoffSchema = import_zod8.z.object({
|
|
6263
|
+
moduleName: import_zod8.z.string().min(1),
|
|
6264
|
+
homeProjectId: import_zod8.z.string().uuid(),
|
|
6265
|
+
installedVersion: import_zod8.z.string().min(1),
|
|
6266
|
+
baselineSha: import_zod8.z.string().min(1),
|
|
6267
|
+
mounts: import_zod8.z.array(
|
|
6268
|
+
import_zod8.z.object({
|
|
6269
|
+
group: import_zod8.z.string(),
|
|
6270
|
+
segment: import_zod8.z.string()
|
|
6009
6271
|
})
|
|
6010
6272
|
),
|
|
6011
|
-
integration:
|
|
6012
|
-
workflows:
|
|
6013
|
-
prunedEnhancements:
|
|
6014
|
-
restoredEnhancements:
|
|
6015
|
-
permissions:
|
|
6016
|
-
|
|
6017
|
-
key:
|
|
6018
|
-
description:
|
|
6273
|
+
integration: import_zod8.z.array(import_zod8.z.string()),
|
|
6274
|
+
workflows: import_zod8.z.array(import_zod8.z.string()),
|
|
6275
|
+
prunedEnhancements: import_zod8.z.array(import_zod8.z.string()),
|
|
6276
|
+
restoredEnhancements: import_zod8.z.array(import_zod8.z.string()).optional(),
|
|
6277
|
+
permissions: import_zod8.z.array(
|
|
6278
|
+
import_zod8.z.object({
|
|
6279
|
+
key: import_zod8.z.string().min(1),
|
|
6280
|
+
description: import_zod8.z.string().min(1)
|
|
6019
6281
|
})
|
|
6020
6282
|
).optional()
|
|
6021
6283
|
});
|
|
6022
6284
|
|
|
6023
6285
|
// ../../packages/lib/src/shared/schema-ops.ts
|
|
6024
|
-
var
|
|
6025
|
-
var columnSpecSchema =
|
|
6026
|
-
name:
|
|
6027
|
-
pgType:
|
|
6028
|
-
nullable:
|
|
6286
|
+
var import_zod9 = require("zod");
|
|
6287
|
+
var columnSpecSchema = import_zod9.z.object({
|
|
6288
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6289
|
+
pgType: import_zod9.z.string().min(1),
|
|
6290
|
+
nullable: import_zod9.z.boolean(),
|
|
6029
6291
|
/**
|
|
6030
6292
|
* A SQL expression used as the column default, as a raw string (e.g. `'t'`,
|
|
6031
6293
|
* `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
|
|
6032
6294
|
* NOT a JS literal — must already be a valid SQL expression.
|
|
6033
6295
|
*/
|
|
6034
|
-
default:
|
|
6035
|
-
fieldType:
|
|
6036
|
-
fieldConfig:
|
|
6296
|
+
default: import_zod9.z.string().nullable().optional(),
|
|
6297
|
+
fieldType: import_zod9.z.string().optional(),
|
|
6298
|
+
fieldConfig: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.unknown()).optional()
|
|
6037
6299
|
});
|
|
6038
|
-
var referentialActionSchema =
|
|
6300
|
+
var referentialActionSchema = import_zod9.z.enum([
|
|
6039
6301
|
"cascade",
|
|
6040
6302
|
"set null",
|
|
6041
6303
|
"set default",
|
|
6042
6304
|
"restrict",
|
|
6043
6305
|
"no action"
|
|
6044
6306
|
]);
|
|
6045
|
-
var schemaOpSchema =
|
|
6307
|
+
var schemaOpSchema = import_zod9.z.discriminatedUnion("type", [
|
|
6046
6308
|
// Tables
|
|
6047
|
-
|
|
6048
|
-
type:
|
|
6049
|
-
table:
|
|
6050
|
-
columns:
|
|
6051
|
-
primaryKey:
|
|
6052
|
-
ifNotExists:
|
|
6309
|
+
import_zod9.z.object({
|
|
6310
|
+
type: import_zod9.z.literal("createTable"),
|
|
6311
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6312
|
+
columns: import_zod9.z.array(columnSpecSchema).min(1),
|
|
6313
|
+
primaryKey: import_zod9.z.array(import_zod9.z.string()).optional(),
|
|
6314
|
+
ifNotExists: import_zod9.z.boolean().optional()
|
|
6053
6315
|
}),
|
|
6054
|
-
|
|
6055
|
-
type:
|
|
6056
|
-
table:
|
|
6057
|
-
cascade:
|
|
6058
|
-
ifExists:
|
|
6316
|
+
import_zod9.z.object({
|
|
6317
|
+
type: import_zod9.z.literal("dropTable"),
|
|
6318
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6319
|
+
cascade: import_zod9.z.boolean().optional(),
|
|
6320
|
+
ifExists: import_zod9.z.boolean().optional()
|
|
6059
6321
|
}),
|
|
6060
|
-
|
|
6061
|
-
type:
|
|
6062
|
-
from:
|
|
6063
|
-
to:
|
|
6322
|
+
import_zod9.z.object({
|
|
6323
|
+
type: import_zod9.z.literal("renameTable"),
|
|
6324
|
+
from: import_zod9.z.string().min(1).max(63),
|
|
6325
|
+
to: import_zod9.z.string().min(1).max(63)
|
|
6064
6326
|
}),
|
|
6065
6327
|
// Columns
|
|
6066
|
-
|
|
6067
|
-
type:
|
|
6068
|
-
table:
|
|
6328
|
+
import_zod9.z.object({
|
|
6329
|
+
type: import_zod9.z.literal("addColumn"),
|
|
6330
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6069
6331
|
column: columnSpecSchema,
|
|
6070
|
-
ifNotExists:
|
|
6332
|
+
ifNotExists: import_zod9.z.boolean().optional()
|
|
6071
6333
|
}),
|
|
6072
|
-
|
|
6073
|
-
type:
|
|
6074
|
-
table:
|
|
6075
|
-
column:
|
|
6076
|
-
cascade:
|
|
6077
|
-
ifExists:
|
|
6334
|
+
import_zod9.z.object({
|
|
6335
|
+
type: import_zod9.z.literal("dropColumn"),
|
|
6336
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6337
|
+
column: import_zod9.z.string().min(1).max(63),
|
|
6338
|
+
cascade: import_zod9.z.boolean().optional(),
|
|
6339
|
+
ifExists: import_zod9.z.boolean().optional()
|
|
6078
6340
|
}),
|
|
6079
|
-
|
|
6080
|
-
type:
|
|
6081
|
-
table:
|
|
6082
|
-
from:
|
|
6083
|
-
to:
|
|
6341
|
+
import_zod9.z.object({
|
|
6342
|
+
type: import_zod9.z.literal("renameColumn"),
|
|
6343
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6344
|
+
from: import_zod9.z.string().min(1).max(63),
|
|
6345
|
+
to: import_zod9.z.string().min(1).max(63)
|
|
6084
6346
|
}),
|
|
6085
|
-
|
|
6086
|
-
type:
|
|
6087
|
-
table:
|
|
6088
|
-
column:
|
|
6089
|
-
newType:
|
|
6347
|
+
import_zod9.z.object({
|
|
6348
|
+
type: import_zod9.z.literal("alterColumnType"),
|
|
6349
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6350
|
+
column: import_zod9.z.string().min(1).max(63),
|
|
6351
|
+
newType: import_zod9.z.string().min(1),
|
|
6090
6352
|
/** Optional USING expression for potentially-lossy casts */
|
|
6091
|
-
using:
|
|
6353
|
+
using: import_zod9.z.string().optional()
|
|
6092
6354
|
}),
|
|
6093
|
-
|
|
6094
|
-
type:
|
|
6095
|
-
table:
|
|
6096
|
-
column:
|
|
6097
|
-
nullable:
|
|
6355
|
+
import_zod9.z.object({
|
|
6356
|
+
type: import_zod9.z.literal("alterColumnNullable"),
|
|
6357
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6358
|
+
column: import_zod9.z.string().min(1).max(63),
|
|
6359
|
+
nullable: import_zod9.z.boolean()
|
|
6098
6360
|
}),
|
|
6099
|
-
|
|
6100
|
-
type:
|
|
6101
|
-
table:
|
|
6102
|
-
column:
|
|
6361
|
+
import_zod9.z.object({
|
|
6362
|
+
type: import_zod9.z.literal("alterColumnDefault"),
|
|
6363
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6364
|
+
column: import_zod9.z.string().min(1).max(63),
|
|
6103
6365
|
/** null means drop the default; otherwise a SQL expression string. */
|
|
6104
|
-
default:
|
|
6366
|
+
default: import_zod9.z.string().nullable()
|
|
6105
6367
|
}),
|
|
6106
6368
|
// Indexes
|
|
6107
|
-
|
|
6108
|
-
type:
|
|
6109
|
-
table:
|
|
6110
|
-
name:
|
|
6111
|
-
columns:
|
|
6112
|
-
unique:
|
|
6369
|
+
import_zod9.z.object({
|
|
6370
|
+
type: import_zod9.z.literal("addIndex"),
|
|
6371
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6372
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6373
|
+
columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
|
|
6374
|
+
unique: import_zod9.z.boolean().optional(),
|
|
6113
6375
|
/** Partial-index WHERE clause, as a raw SQL expression */
|
|
6114
|
-
where:
|
|
6115
|
-
ifNotExists:
|
|
6376
|
+
where: import_zod9.z.string().optional(),
|
|
6377
|
+
ifNotExists: import_zod9.z.boolean().optional()
|
|
6116
6378
|
}),
|
|
6117
|
-
|
|
6118
|
-
type:
|
|
6119
|
-
name:
|
|
6120
|
-
cascade:
|
|
6121
|
-
ifExists:
|
|
6379
|
+
import_zod9.z.object({
|
|
6380
|
+
type: import_zod9.z.literal("dropIndex"),
|
|
6381
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6382
|
+
cascade: import_zod9.z.boolean().optional(),
|
|
6383
|
+
ifExists: import_zod9.z.boolean().optional()
|
|
6122
6384
|
}),
|
|
6123
6385
|
// Constraints
|
|
6124
|
-
|
|
6125
|
-
type:
|
|
6126
|
-
table:
|
|
6127
|
-
name:
|
|
6128
|
-
columns:
|
|
6129
|
-
refTable:
|
|
6130
|
-
refColumns:
|
|
6386
|
+
import_zod9.z.object({
|
|
6387
|
+
type: import_zod9.z.literal("addForeignKey"),
|
|
6388
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6389
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6390
|
+
columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
|
|
6391
|
+
refTable: import_zod9.z.string().min(1).max(63),
|
|
6392
|
+
refColumns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
|
|
6131
6393
|
onDelete: referentialActionSchema.optional(),
|
|
6132
6394
|
onUpdate: referentialActionSchema.optional()
|
|
6133
6395
|
}),
|
|
6134
|
-
|
|
6135
|
-
type:
|
|
6136
|
-
table:
|
|
6137
|
-
name:
|
|
6138
|
-
columns:
|
|
6396
|
+
import_zod9.z.object({
|
|
6397
|
+
type: import_zod9.z.literal("addUniqueConstraint"),
|
|
6398
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6399
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6400
|
+
columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1)
|
|
6139
6401
|
}),
|
|
6140
|
-
|
|
6141
|
-
type:
|
|
6142
|
-
table:
|
|
6143
|
-
name:
|
|
6144
|
-
expression:
|
|
6402
|
+
import_zod9.z.object({
|
|
6403
|
+
type: import_zod9.z.literal("addCheckConstraint"),
|
|
6404
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6405
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6406
|
+
expression: import_zod9.z.string().min(1)
|
|
6145
6407
|
}),
|
|
6146
|
-
|
|
6147
|
-
type:
|
|
6148
|
-
table:
|
|
6149
|
-
name:
|
|
6150
|
-
cascade:
|
|
6151
|
-
ifExists:
|
|
6408
|
+
import_zod9.z.object({
|
|
6409
|
+
type: import_zod9.z.literal("dropConstraint"),
|
|
6410
|
+
table: import_zod9.z.string().min(1).max(63),
|
|
6411
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6412
|
+
cascade: import_zod9.z.boolean().optional(),
|
|
6413
|
+
ifExists: import_zod9.z.boolean().optional()
|
|
6152
6414
|
}),
|
|
6153
6415
|
// Enum types
|
|
6154
|
-
|
|
6155
|
-
type:
|
|
6156
|
-
name:
|
|
6157
|
-
values:
|
|
6416
|
+
import_zod9.z.object({
|
|
6417
|
+
type: import_zod9.z.literal("createEnum"),
|
|
6418
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6419
|
+
values: import_zod9.z.array(import_zod9.z.string().min(1)).min(1)
|
|
6158
6420
|
}),
|
|
6159
|
-
|
|
6160
|
-
type:
|
|
6161
|
-
name:
|
|
6162
|
-
value:
|
|
6421
|
+
import_zod9.z.object({
|
|
6422
|
+
type: import_zod9.z.literal("alterEnumAddValue"),
|
|
6423
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6424
|
+
value: import_zod9.z.string().min(1),
|
|
6163
6425
|
/** Position the new value before an existing one (mutually exclusive with `after`) */
|
|
6164
|
-
before:
|
|
6426
|
+
before: import_zod9.z.string().optional(),
|
|
6165
6427
|
/** Position the new value after an existing one (mutually exclusive with `before`) */
|
|
6166
|
-
after:
|
|
6167
|
-
ifNotExists:
|
|
6428
|
+
after: import_zod9.z.string().optional(),
|
|
6429
|
+
ifNotExists: import_zod9.z.boolean().optional()
|
|
6168
6430
|
}),
|
|
6169
|
-
|
|
6170
|
-
type:
|
|
6171
|
-
name:
|
|
6172
|
-
cascade:
|
|
6173
|
-
ifExists:
|
|
6431
|
+
import_zod9.z.object({
|
|
6432
|
+
type: import_zod9.z.literal("dropEnum"),
|
|
6433
|
+
name: import_zod9.z.string().min(1).max(63),
|
|
6434
|
+
cascade: import_zod9.z.boolean().optional(),
|
|
6435
|
+
ifExists: import_zod9.z.boolean().optional()
|
|
6174
6436
|
}),
|
|
6175
6437
|
// Extensions
|
|
6176
|
-
|
|
6177
|
-
type:
|
|
6178
|
-
name:
|
|
6179
|
-
ifNotExists:
|
|
6438
|
+
import_zod9.z.object({
|
|
6439
|
+
type: import_zod9.z.literal("createExtension"),
|
|
6440
|
+
name: import_zod9.z.string().min(1),
|
|
6441
|
+
ifNotExists: import_zod9.z.boolean().optional()
|
|
6180
6442
|
}),
|
|
6181
6443
|
// Data operations
|
|
6182
6444
|
/**
|
|
@@ -6184,26 +6446,26 @@ var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
|
|
|
6184
6446
|
* destructive — requires explicit confirm to replay on prod. The
|
|
6185
6447
|
* `description` is what the agent/user sees at confirm time.
|
|
6186
6448
|
*/
|
|
6187
|
-
|
|
6188
|
-
type:
|
|
6189
|
-
description:
|
|
6190
|
-
sql:
|
|
6449
|
+
import_zod9.z.object({
|
|
6450
|
+
type: import_zod9.z.literal("backfill"),
|
|
6451
|
+
description: import_zod9.z.string().min(1),
|
|
6452
|
+
sql: import_zod9.z.string().min(1)
|
|
6191
6453
|
}),
|
|
6192
6454
|
/**
|
|
6193
6455
|
* Escape hatch for DDL the structured ops can't express. `destructive`
|
|
6194
6456
|
* forces classification — use false only for genuinely additive-only SQL
|
|
6195
6457
|
* (e.g. CREATE EXTENSION variants the structured op doesn't cover).
|
|
6196
6458
|
*/
|
|
6197
|
-
|
|
6198
|
-
type:
|
|
6199
|
-
description:
|
|
6200
|
-
sql:
|
|
6201
|
-
destructive:
|
|
6459
|
+
import_zod9.z.object({
|
|
6460
|
+
type: import_zod9.z.literal("rawSql"),
|
|
6461
|
+
description: import_zod9.z.string().min(1),
|
|
6462
|
+
sql: import_zod9.z.string().min(1),
|
|
6463
|
+
destructive: import_zod9.z.boolean()
|
|
6202
6464
|
})
|
|
6203
6465
|
]);
|
|
6204
6466
|
|
|
6205
6467
|
// ../../packages/lib/src/server/module-rail/ops.ts
|
|
6206
|
-
var
|
|
6468
|
+
var import_zod10 = require("zod");
|
|
6207
6469
|
|
|
6208
6470
|
// ../../packages/lib/src/server/module-rail/reconcile.ts
|
|
6209
6471
|
var SANDBOX_BATCH_SIZE = 200;
|
|
@@ -6233,11 +6495,11 @@ function countedCompositionContext(context) {
|
|
|
6233
6495
|
counts.sandboxExecs += 1;
|
|
6234
6496
|
return context.sandbox.exec(...args);
|
|
6235
6497
|
},
|
|
6236
|
-
readFile: (
|
|
6498
|
+
readFile: (path6) => {
|
|
6237
6499
|
counts.sandboxReads += 1;
|
|
6238
|
-
return context.sandbox.readFile(
|
|
6500
|
+
return context.sandbox.readFile(path6);
|
|
6239
6501
|
},
|
|
6240
|
-
fileExists: (
|
|
6502
|
+
fileExists: (path6) => context.sandbox.fileExists(path6),
|
|
6241
6503
|
fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
|
|
6242
6504
|
addDependencies: (specs) => context.sandbox.addDependencies(specs)
|
|
6243
6505
|
}
|
|
@@ -6316,39 +6578,24 @@ async function assertNoRootRoutesWhenMountsEmpty(context, moduleName) {
|
|
|
6316
6578
|
);
|
|
6317
6579
|
}
|
|
6318
6580
|
}
|
|
6319
|
-
async function
|
|
6320
|
-
const
|
|
6321
|
-
const
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
for (const entry of enhancementEntries) {
|
|
6326
|
-
if (entry.kind !== "dir") continue;
|
|
6327
|
-
const sliceRoutes = `${enhancementsDir}/${entry.name}/routes`;
|
|
6328
|
-
const sliceProbe = await context.sandbox.exec(
|
|
6329
|
-
[
|
|
6330
|
-
`p=${shellQuote(sliceRoutes)}`,
|
|
6331
|
-
`if test -L "$p" || test -e "$p"; then exit 2; else exit 0; fi`
|
|
6332
|
-
].join("\n"),
|
|
6333
|
-
{ raiseOnError: false }
|
|
6334
|
-
);
|
|
6335
|
-
if (sliceProbe.exitCode === 2) {
|
|
6581
|
+
async function assertNoEnhancementRoutePaths(context, paths) {
|
|
6582
|
+
const kinds = await classifySandboxPathPresenceKindMany(context, paths, "enhancement-route-kind");
|
|
6583
|
+
for (const sliceRoutes of paths) {
|
|
6584
|
+
const kind = kinds.get(sliceRoutes);
|
|
6585
|
+
if (kind === "absent") continue;
|
|
6586
|
+
if (kind !== "unreadable" && kind !== void 0) {
|
|
6336
6587
|
throw new Error(
|
|
6337
6588
|
`[ModuleRail] composition artifacts: enhancement slice routes/ are forbidden at ${sliceRoutes}`
|
|
6338
6589
|
);
|
|
6339
6590
|
}
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: ${redactSecrets(detail)}`
|
|
6344
|
-
);
|
|
6345
|
-
}
|
|
6591
|
+
throw new Error(
|
|
6592
|
+
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: entry-kind:${kind ?? "missing"}`
|
|
6593
|
+
);
|
|
6346
6594
|
}
|
|
6347
6595
|
}
|
|
6348
|
-
async function
|
|
6596
|
+
async function discoverRootRouteEntries(context, moduleName) {
|
|
6349
6597
|
const modulePath = `${MODULES_SANDBOX_DIR}/${moduleName}`;
|
|
6350
6598
|
const routesDir = `${modulePath}/routes`;
|
|
6351
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
6352
6599
|
const quotedRoutes = shellQuote(routesDir);
|
|
6353
6600
|
const listResult = await context.sandbox.exec(
|
|
6354
6601
|
[
|
|
@@ -6449,14 +6696,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
6449
6696
|
}
|
|
6450
6697
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
6451
6698
|
}
|
|
6452
|
-
async function writeSandboxFileAtomic(target,
|
|
6453
|
-
const dir = dirnamePosix(
|
|
6454
|
-
const tmp = `${
|
|
6699
|
+
async function writeSandboxFileAtomic(target, path6, content) {
|
|
6700
|
+
const dir = dirnamePosix(path6);
|
|
6701
|
+
const tmp = `${path6}.tmp.${target.runId}`;
|
|
6455
6702
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
6456
6703
|
const cmd = [
|
|
6457
6704
|
`mkdir -p ${shellQuote(dir)}`,
|
|
6458
6705
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
6459
|
-
`mv -f ${shellQuote(tmp)} ${shellQuote(
|
|
6706
|
+
`mv -f ${shellQuote(tmp)} ${shellQuote(path6)}`
|
|
6460
6707
|
].join(" && ");
|
|
6461
6708
|
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
6462
6709
|
if (result.exitCode !== 0) {
|
|
@@ -6471,14 +6718,14 @@ async function writeSandboxFileAtomic(target, path5, content) {
|
|
|
6471
6718
|
}
|
|
6472
6719
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6473
6720
|
throw new Error(
|
|
6474
|
-
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(
|
|
6721
|
+
`[ModuleRail] composition artifacts: failed to write ${redactSecrets(path6)}: ${redactSecrets(detail)}`
|
|
6475
6722
|
);
|
|
6476
6723
|
}
|
|
6477
6724
|
}
|
|
6478
6725
|
async function collectAbsentWriteParentDirs(context, writePaths) {
|
|
6479
6726
|
const absent = /* @__PURE__ */ new Set();
|
|
6480
|
-
for (const
|
|
6481
|
-
let dir = dirnamePosix(
|
|
6727
|
+
for (const path6 of writePaths) {
|
|
6728
|
+
let dir = dirnamePosix(path6);
|
|
6482
6729
|
while (dir !== "." && dir !== "") {
|
|
6483
6730
|
if (absent.has(dir)) {
|
|
6484
6731
|
dir = dirnamePosix(dir);
|
|
@@ -6515,12 +6762,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
|
|
|
6515
6762
|
);
|
|
6516
6763
|
}
|
|
6517
6764
|
}
|
|
6518
|
-
async function deleteSandboxFile(context,
|
|
6519
|
-
const result = await context.sandbox.exec(`rm -f ${shellQuote(
|
|
6765
|
+
async function deleteSandboxFile(context, path6) {
|
|
6766
|
+
const result = await context.sandbox.exec(`rm -f ${shellQuote(path6)}`, { raiseOnError: false });
|
|
6520
6767
|
if (result.exitCode !== 0) {
|
|
6521
6768
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
6522
6769
|
throw new Error(
|
|
6523
|
-
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(
|
|
6770
|
+
`[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path6)}: ${redactSecrets(detail)}`
|
|
6524
6771
|
);
|
|
6525
6772
|
}
|
|
6526
6773
|
}
|
|
@@ -6587,37 +6834,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
|
6587
6834
|
await writeSandboxFileAtomic(context, write.path, write.content);
|
|
6588
6835
|
appliedWrites.push({ path: write.path, previous });
|
|
6589
6836
|
}
|
|
6590
|
-
for (const
|
|
6591
|
-
const previous = existingFiles[
|
|
6837
|
+
for (const path6 of plan.deletes) {
|
|
6838
|
+
const previous = existingFiles[path6];
|
|
6592
6839
|
if (previous == null) continue;
|
|
6593
|
-
await deleteSandboxFile(context,
|
|
6594
|
-
appliedDeletes.push({ path:
|
|
6840
|
+
await deleteSandboxFile(context, path6);
|
|
6841
|
+
appliedDeletes.push({ path: path6, previous });
|
|
6595
6842
|
}
|
|
6596
6843
|
} catch (error) {
|
|
6597
6844
|
console.log(
|
|
6598
6845
|
`[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
|
|
6599
6846
|
);
|
|
6600
|
-
for (const { path:
|
|
6847
|
+
for (const { path: path6, previous } of [...appliedDeletes].reverse()) {
|
|
6601
6848
|
try {
|
|
6602
|
-
await writeSandboxFileAtomic(context,
|
|
6849
|
+
await writeSandboxFileAtomic(context, path6, previous);
|
|
6603
6850
|
} catch (rollbackError) {
|
|
6604
6851
|
console.log(
|
|
6605
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(
|
|
6852
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path6)}: ${redactSecrets(
|
|
6606
6853
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6607
6854
|
)}`
|
|
6608
6855
|
);
|
|
6609
6856
|
}
|
|
6610
6857
|
}
|
|
6611
|
-
for (const { path:
|
|
6858
|
+
for (const { path: path6, previous } of [...appliedWrites].reverse()) {
|
|
6612
6859
|
try {
|
|
6613
6860
|
if (previous == null) {
|
|
6614
|
-
await deleteSandboxFile(context,
|
|
6861
|
+
await deleteSandboxFile(context, path6);
|
|
6615
6862
|
} else {
|
|
6616
|
-
await writeSandboxFileAtomic(context,
|
|
6863
|
+
await writeSandboxFileAtomic(context, path6, previous);
|
|
6617
6864
|
}
|
|
6618
6865
|
} catch (rollbackError) {
|
|
6619
6866
|
console.log(
|
|
6620
|
-
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(
|
|
6867
|
+
`[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path6)}: ${redactSecrets(
|
|
6621
6868
|
rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
|
|
6622
6869
|
)}`
|
|
6623
6870
|
);
|
|
@@ -6651,39 +6898,39 @@ async function listExistingGeneratedStubPaths(context) {
|
|
|
6651
6898
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
6652
6899
|
);
|
|
6653
6900
|
const presentPaths = [];
|
|
6654
|
-
for (const
|
|
6901
|
+
for (const path6 of candidates) {
|
|
6655
6902
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
6656
|
-
|
|
6657
|
-
kinds.get(
|
|
6903
|
+
path6,
|
|
6904
|
+
kinds.get(path6) ?? "unreadable"
|
|
6658
6905
|
);
|
|
6659
|
-
if (presence === "file") presentPaths.push(
|
|
6906
|
+
if (presence === "file") presentPaths.push(path6);
|
|
6660
6907
|
}
|
|
6661
6908
|
let contents;
|
|
6662
6909
|
try {
|
|
6663
6910
|
contents = await readFilesMany(context, presentPaths);
|
|
6664
6911
|
} catch (error) {
|
|
6665
|
-
const
|
|
6912
|
+
const path6 = readFailurePath(error);
|
|
6666
6913
|
throw new Error(
|
|
6667
|
-
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(
|
|
6914
|
+
`[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path6)}: ${redactSecrets(
|
|
6668
6915
|
error instanceof Error ? error.message : String(error)
|
|
6669
6916
|
)}`
|
|
6670
6917
|
);
|
|
6671
6918
|
}
|
|
6672
6919
|
const owned = [];
|
|
6673
|
-
for (const
|
|
6674
|
-
const content = contents.get(
|
|
6920
|
+
for (const path6 of presentPaths) {
|
|
6921
|
+
const content = contents.get(path6);
|
|
6675
6922
|
const marker = parseCompositionArtifactMarker(content);
|
|
6676
|
-
if (parseLegacyApiAdapter(
|
|
6677
|
-
owned.push(
|
|
6923
|
+
if (parseLegacyApiAdapter(path6, content) || parseLegacyRouteAdapter(path6, content)) {
|
|
6924
|
+
owned.push(path6);
|
|
6678
6925
|
continue;
|
|
6679
6926
|
}
|
|
6680
6927
|
if (marker == null) continue;
|
|
6681
|
-
if (!markerCongruentWithPath(
|
|
6928
|
+
if (!markerCongruentWithPath(path6, marker)) {
|
|
6682
6929
|
throw new Error(
|
|
6683
|
-
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(
|
|
6930
|
+
`[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path6)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path6) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
|
|
6684
6931
|
);
|
|
6685
6932
|
}
|
|
6686
|
-
owned.push(
|
|
6933
|
+
owned.push(path6);
|
|
6687
6934
|
}
|
|
6688
6935
|
return owned;
|
|
6689
6936
|
}
|
|
@@ -6802,7 +7049,8 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6802
7049
|
`# Leaf is missing. A missing intermediate parent means the path is simply`,
|
|
6803
7050
|
`# absent (new nested writes). Only an existing-but-inaccessible parent`,
|
|
6804
7051
|
`# is unreadable \u2014 never conflate ENOENT parents with EACCES.`,
|
|
6805
|
-
|
|
7052
|
+
// Probe paths are nested repo-relative paths; do not spawn dirname per missing file.
|
|
7053
|
+
`parent=\${p%/*}`,
|
|
6806
7054
|
`if test ! -e "$parent" && test ! -L "$parent"; then`,
|
|
6807
7055
|
` printf 'entry-kind:absent\\t%s\\n' "$p"`,
|
|
6808
7056
|
`elif ls -ld -- "$parent" >/dev/null 2>&1; then`,
|
|
@@ -6816,7 +7064,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6816
7064
|
{ raiseOnError: false }
|
|
6817
7065
|
);
|
|
6818
7066
|
if (result.exitCode !== 0) {
|
|
6819
|
-
for (const
|
|
7067
|
+
for (const path6 of chunk) kinds.set(path6, "unreadable");
|
|
6820
7068
|
continue;
|
|
6821
7069
|
}
|
|
6822
7070
|
const lines = result.output.split("\n").filter(Boolean);
|
|
@@ -6827,31 +7075,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6827
7075
|
}
|
|
6828
7076
|
} else {
|
|
6829
7077
|
for (const line of lines) {
|
|
6830
|
-
const [rawKind,
|
|
7078
|
+
const [rawKind, path6] = line.split(" ", 2);
|
|
6831
7079
|
const kind = rawKind?.replace(/^entry-kind:/, "");
|
|
6832
|
-
if (
|
|
6833
|
-
kinds.set(
|
|
7080
|
+
if (path6 && chunk.includes(path6) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
|
|
7081
|
+
kinds.set(path6, kind);
|
|
6834
7082
|
}
|
|
6835
7083
|
}
|
|
6836
7084
|
}
|
|
6837
|
-
for (const
|
|
6838
|
-
if (!kinds.has(
|
|
7085
|
+
for (const path6 of chunk) {
|
|
7086
|
+
if (!kinds.has(path6)) kinds.set(path6, "unreadable");
|
|
6839
7087
|
}
|
|
6840
7088
|
}
|
|
6841
7089
|
return kinds;
|
|
6842
7090
|
}
|
|
6843
|
-
async function classifySandboxPathPresenceKind(context,
|
|
6844
|
-
return (await classifySandboxPathPresenceKindMany(context, [
|
|
7091
|
+
async function classifySandboxPathPresenceKind(context, path6, probeToken) {
|
|
7092
|
+
return (await classifySandboxPathPresenceKindMany(context, [path6], probeToken)).get(path6);
|
|
6845
7093
|
}
|
|
6846
7094
|
var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
|
|
6847
|
-
function assertRealLocaleJsonFilePresence(
|
|
7095
|
+
function assertRealLocaleJsonFilePresence(path6, presence) {
|
|
6848
7096
|
if (presence === "file") return;
|
|
6849
7097
|
if (presence === "absent") {
|
|
6850
|
-
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${
|
|
7098
|
+
throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path6}`);
|
|
6851
7099
|
}
|
|
6852
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
7100
|
+
const presenceError = formatConventionalEntryPresenceError(path6, presence);
|
|
6853
7101
|
throw new Error(
|
|
6854
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
7102
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path6} is not a real locale JSON file`}`
|
|
6855
7103
|
);
|
|
6856
7104
|
}
|
|
6857
7105
|
function splitTaggedLine(line) {
|
|
@@ -6926,11 +7174,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
|
|
|
6926
7174
|
}
|
|
6927
7175
|
async function readFilesMany(context, paths) {
|
|
6928
7176
|
const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
6929
|
-
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (
|
|
7177
|
+
const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path6) => {
|
|
6930
7178
|
try {
|
|
6931
|
-
return { path:
|
|
7179
|
+
return { path: path6, value: await context.sandbox.readFile(path6) };
|
|
6932
7180
|
} catch (error) {
|
|
6933
|
-
return { path:
|
|
7181
|
+
return { path: path6, error };
|
|
6934
7182
|
}
|
|
6935
7183
|
});
|
|
6936
7184
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -6953,61 +7201,61 @@ function readFailurePath(error) {
|
|
|
6953
7201
|
var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
|
|
6954
7202
|
var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
|
|
6955
7203
|
var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
|
|
6956
|
-
async function classifyCompositionArtifactPathKind(context,
|
|
6957
|
-
return classifySandboxPathPresenceKind(context,
|
|
7204
|
+
async function classifyCompositionArtifactPathKind(context, path6) {
|
|
7205
|
+
return classifySandboxPathPresenceKind(context, path6, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
|
|
6958
7206
|
}
|
|
6959
|
-
function assertRealCompositionArtifactFilePresence(
|
|
7207
|
+
function assertRealCompositionArtifactFilePresence(path6, presence) {
|
|
6960
7208
|
if (presence === "file") return "file";
|
|
6961
7209
|
if (presence === "absent") return "absent";
|
|
6962
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
7210
|
+
const presenceError = formatConventionalEntryPresenceError(path6, presence);
|
|
6963
7211
|
throw new Error(
|
|
6964
|
-
`[ModuleRail] composition artifacts: ${presenceError ?? `${
|
|
7212
|
+
`[ModuleRail] composition artifacts: ${presenceError ?? `${path6} is not a real composition artifact file`}`
|
|
6965
7213
|
);
|
|
6966
7214
|
}
|
|
6967
|
-
async function readConventionalEntryIfRealFile(
|
|
6968
|
-
const presence = presences.get(
|
|
6969
|
-
const presenceError = formatConventionalEntryPresenceError(
|
|
7215
|
+
async function readConventionalEntryIfRealFile(path6, kind, presences, files) {
|
|
7216
|
+
const presence = presences.get(path6) ?? "unreadable";
|
|
7217
|
+
const presenceError = formatConventionalEntryPresenceError(path6, presence);
|
|
6970
7218
|
if (presenceError) {
|
|
6971
7219
|
throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
|
|
6972
7220
|
}
|
|
6973
7221
|
if (presence === "absent") {
|
|
6974
7222
|
return null;
|
|
6975
7223
|
}
|
|
6976
|
-
const source = files.get(
|
|
7224
|
+
const source = files.get(path6);
|
|
6977
7225
|
switch (kind) {
|
|
6978
7226
|
case "contributions":
|
|
6979
|
-
assertContributionsExport(source,
|
|
6980
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7227
|
+
assertContributionsExport(source, path6);
|
|
7228
|
+
assertClientSafeConventionalEntryImports(source, path6, "contributions");
|
|
6981
7229
|
break;
|
|
6982
7230
|
case "slotCatalogs":
|
|
6983
|
-
assertSlotCatalogsExport(source,
|
|
6984
|
-
assertClientSafeConventionalEntryImports(source,
|
|
7231
|
+
assertSlotCatalogsExport(source, path6);
|
|
7232
|
+
assertClientSafeConventionalEntryImports(source, path6, "slotCatalogs");
|
|
6985
7233
|
break;
|
|
6986
7234
|
case "initializeEnhancement":
|
|
6987
|
-
assertInitializeEnhancementExport(source,
|
|
7235
|
+
assertInitializeEnhancementExport(source, path6);
|
|
6988
7236
|
break;
|
|
6989
7237
|
case "initializeModule":
|
|
6990
|
-
assertInitializeModuleExport(source,
|
|
7238
|
+
assertInitializeModuleExport(source, path6);
|
|
6991
7239
|
break;
|
|
6992
7240
|
}
|
|
6993
7241
|
return source;
|
|
6994
7242
|
}
|
|
6995
7243
|
function hasRealServerRegistrations(modulePath, presences) {
|
|
6996
|
-
const
|
|
6997
|
-
return assertRealCompositionArtifactFilePresence(
|
|
7244
|
+
const path6 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
|
|
7245
|
+
return assertRealCompositionArtifactFilePresence(path6, presences.get(path6) ?? "unreadable") === "file";
|
|
6998
7246
|
}
|
|
6999
|
-
async function readJsonObjectFile(
|
|
7000
|
-
const raw = files.get(
|
|
7247
|
+
async function readJsonObjectFile(path6, files) {
|
|
7248
|
+
const raw = files.get(path6);
|
|
7001
7249
|
let parsed;
|
|
7002
7250
|
try {
|
|
7003
7251
|
parsed = JSON.parse(raw);
|
|
7004
7252
|
} catch (error) {
|
|
7005
7253
|
throw new Error(
|
|
7006
|
-
`[ModuleRail] composition artifacts: invalid JSON at ${
|
|
7254
|
+
`[ModuleRail] composition artifacts: invalid JSON at ${path6}: ${error instanceof Error ? error.message : String(error)}`
|
|
7007
7255
|
);
|
|
7008
7256
|
}
|
|
7009
7257
|
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
7010
|
-
throw new Error(`[ModuleRail] composition artifacts: ${
|
|
7258
|
+
throw new Error(`[ModuleRail] composition artifacts: ${path6} root must be an object`);
|
|
7011
7259
|
}
|
|
7012
7260
|
return parsed;
|
|
7013
7261
|
}
|
|
@@ -7060,11 +7308,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7060
7308
|
classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
|
|
7061
7309
|
]);
|
|
7062
7310
|
const presentConventionalPaths = [];
|
|
7063
|
-
for (const
|
|
7064
|
-
const presence = conventionalPresences.get(
|
|
7065
|
-
if (presence === "file") presentConventionalPaths.push(
|
|
7311
|
+
for (const path6 of conventionalPaths) {
|
|
7312
|
+
const presence = conventionalPresences.get(path6) ?? "unreadable";
|
|
7313
|
+
if (presence === "file") presentConventionalPaths.push(path6);
|
|
7066
7314
|
}
|
|
7067
|
-
const presentLocalePaths = localePaths.filter((
|
|
7315
|
+
const presentLocalePaths = localePaths.filter((path6) => localePresences.get(path6) === "file");
|
|
7068
7316
|
const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
|
|
7069
7317
|
const substrateLocales = [];
|
|
7070
7318
|
for (const locale of substrateNames) {
|
|
@@ -7174,8 +7422,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7174
7422
|
});
|
|
7175
7423
|
if (rootLocales.length > 0) {
|
|
7176
7424
|
for (const locale of rootLocales) {
|
|
7177
|
-
const
|
|
7178
|
-
assertRealLocaleJsonFilePresence(
|
|
7425
|
+
const path6 = `${modulePath}/i18n/${locale}.json`;
|
|
7426
|
+
assertRealLocaleJsonFilePresence(path6, localePresences.get(path6) ?? "unreadable");
|
|
7179
7427
|
}
|
|
7180
7428
|
const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
|
|
7181
7429
|
const localeMessages = { en: enMessages };
|
|
@@ -7199,8 +7447,8 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7199
7447
|
if (!enh.active || enh.locales.length === 0) continue;
|
|
7200
7448
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7201
7449
|
for (const locale of enh.locales) {
|
|
7202
|
-
const
|
|
7203
|
-
assertRealLocaleJsonFilePresence(
|
|
7450
|
+
const path6 = `${peerDir}/i18n/${locale}.json`;
|
|
7451
|
+
assertRealLocaleJsonFilePresence(path6, localePresences.get(path6) ?? "unreadable");
|
|
7204
7452
|
}
|
|
7205
7453
|
const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
|
|
7206
7454
|
const localeMessages = { en: enMessages };
|
|
@@ -7347,14 +7595,21 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7347
7595
|
manifestsByName
|
|
7348
7596
|
);
|
|
7349
7597
|
const routeEntriesByOwner = {};
|
|
7598
|
+
await assertNoEnhancementRoutePaths(
|
|
7599
|
+
context,
|
|
7600
|
+
moduleRuntimeFacts.flatMap(
|
|
7601
|
+
({ moduleName, onDiskEnhancementDirs }) => onDiskEnhancementDirs.map(
|
|
7602
|
+
(peer) => `${MODULES_SANDBOX_DIR}/${moduleName}/enhancements/${peer}/routes`
|
|
7603
|
+
)
|
|
7604
|
+
)
|
|
7605
|
+
);
|
|
7350
7606
|
for (const moduleName of composedNames) {
|
|
7351
7607
|
const manifest = manifestsByName[moduleName];
|
|
7352
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
7353
7608
|
if (manifest.mounts.length === 0) {
|
|
7354
7609
|
await assertNoRootRoutesWhenMountsEmpty(context, moduleName);
|
|
7355
7610
|
routeEntriesByOwner[moduleName] = [];
|
|
7356
7611
|
} else {
|
|
7357
|
-
routeEntriesByOwner[moduleName] = await
|
|
7612
|
+
routeEntriesByOwner[moduleName] = await discoverRootRouteEntries(context, moduleName);
|
|
7358
7613
|
}
|
|
7359
7614
|
}
|
|
7360
7615
|
const existingGeneratedPaths = await listExistingGeneratedStubPaths(context);
|
|
@@ -7401,16 +7656,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7401
7656
|
for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
|
|
7402
7657
|
const existingFiles = {};
|
|
7403
7658
|
const remainingCandidatePaths = [];
|
|
7404
|
-
for (const
|
|
7405
|
-
if (Object.prototype.hasOwnProperty.call(existingAppPages,
|
|
7406
|
-
existingFiles[
|
|
7659
|
+
for (const path6 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
|
|
7660
|
+
if (Object.prototype.hasOwnProperty.call(existingAppPages, path6)) {
|
|
7661
|
+
existingFiles[path6] = existingAppPages[path6];
|
|
7407
7662
|
continue;
|
|
7408
7663
|
}
|
|
7409
|
-
if (Object.prototype.hasOwnProperty.call(existingAppRoutes,
|
|
7410
|
-
existingFiles[
|
|
7664
|
+
if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path6)) {
|
|
7665
|
+
existingFiles[path6] = existingAppRoutes[path6];
|
|
7411
7666
|
continue;
|
|
7412
7667
|
}
|
|
7413
|
-
remainingCandidatePaths.push(
|
|
7668
|
+
remainingCandidatePaths.push(path6);
|
|
7414
7669
|
}
|
|
7415
7670
|
const candidateKinds = await classifySandboxPathPresenceKindMany(
|
|
7416
7671
|
context,
|
|
@@ -7418,12 +7673,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7418
7673
|
COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
|
|
7419
7674
|
);
|
|
7420
7675
|
const presentCandidatePaths = [];
|
|
7421
|
-
for (const
|
|
7676
|
+
for (const path6 of remainingCandidatePaths) {
|
|
7422
7677
|
const presence = assertRealCompositionArtifactFilePresence(
|
|
7423
|
-
|
|
7424
|
-
candidateKinds.get(
|
|
7678
|
+
path6,
|
|
7679
|
+
candidateKinds.get(path6) ?? "unreadable"
|
|
7425
7680
|
);
|
|
7426
|
-
if (presence === "file") presentCandidatePaths.push(
|
|
7681
|
+
if (presence === "file") presentCandidatePaths.push(path6);
|
|
7427
7682
|
}
|
|
7428
7683
|
try {
|
|
7429
7684
|
Object.assign(
|
|
@@ -7431,9 +7686,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7431
7686
|
Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
|
|
7432
7687
|
);
|
|
7433
7688
|
} catch (error) {
|
|
7434
|
-
const
|
|
7689
|
+
const path6 = readFailurePath(error);
|
|
7435
7690
|
throw new Error(
|
|
7436
|
-
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(
|
|
7691
|
+
`[ModuleRail] composition artifacts: cannot read ${redactSecrets(path6)} during preflight: ${redactSecrets(
|
|
7437
7692
|
error instanceof Error ? error.message : String(error)
|
|
7438
7693
|
)}`
|
|
7439
7694
|
);
|
|
@@ -7506,8 +7761,8 @@ var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
|
7506
7761
|
var import_typescript6 = __toESM(require("typescript"));
|
|
7507
7762
|
|
|
7508
7763
|
// src/module-boundaries.ts
|
|
7509
|
-
var
|
|
7510
|
-
var
|
|
7764
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
7765
|
+
var import_node_path3 = __toESM(require("path"));
|
|
7511
7766
|
var import_typescript5 = __toESM(require("typescript"));
|
|
7512
7767
|
var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
|
|
7513
7768
|
var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
@@ -7526,26 +7781,26 @@ function dataPathsIn(fileName, source) {
|
|
|
7526
7781
|
}
|
|
7527
7782
|
function scanModule(modulesDir, module2) {
|
|
7528
7783
|
const scanned = /* @__PURE__ */ new Map();
|
|
7529
|
-
const moduleDir =
|
|
7530
|
-
if (!
|
|
7531
|
-
const files =
|
|
7784
|
+
const moduleDir = import_node_path3.default.join(modulesDir, module2);
|
|
7785
|
+
if (!import_node_fs3.default.existsSync(moduleDir)) return scanned;
|
|
7786
|
+
const files = import_node_fs3.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
|
|
7532
7787
|
for (const relative of files) {
|
|
7533
|
-
const absolute =
|
|
7534
|
-
if (!
|
|
7535
|
-
const source =
|
|
7788
|
+
const absolute = import_node_path3.default.join(moduleDir, relative);
|
|
7789
|
+
if (!import_node_fs3.default.statSync(absolute).isFile()) continue;
|
|
7790
|
+
const source = import_node_fs3.default.readFileSync(absolute, "utf8");
|
|
7536
7791
|
const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
|
|
7537
7792
|
const dataPaths = dataPathsIn(absolute, source);
|
|
7538
7793
|
const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
|
|
7539
7794
|
if (spec.startsWith("@/modules/"))
|
|
7540
|
-
return [
|
|
7795
|
+
return [import_node_path3.default.join(modulesDir, spec.slice("@/modules/".length))];
|
|
7541
7796
|
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
7542
|
-
return [
|
|
7797
|
+
return [import_node_path3.default.resolve(import_node_path3.default.dirname(absolute), spec)];
|
|
7543
7798
|
}
|
|
7544
7799
|
return [];
|
|
7545
7800
|
});
|
|
7546
7801
|
scanned.set(absolute, {
|
|
7547
7802
|
module: module2,
|
|
7548
|
-
relative: relative.split(
|
|
7803
|
+
relative: relative.split(import_node_path3.default.sep).join("/"),
|
|
7549
7804
|
targets,
|
|
7550
7805
|
dataPaths
|
|
7551
7806
|
});
|
|
@@ -7562,7 +7817,7 @@ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = fal
|
|
|
7562
7817
|
(q) => file.relative.startsWith(`enhancements/${q}/`)
|
|
7563
7818
|
);
|
|
7564
7819
|
const goneWith = (target, peer) => {
|
|
7565
|
-
const relative =
|
|
7820
|
+
const relative = import_node_path3.default.relative(modulesDir, target).split(import_node_path3.default.sep).join("/");
|
|
7566
7821
|
return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
|
|
7567
7822
|
};
|
|
7568
7823
|
const tainted = /* @__PURE__ */ new Map();
|
|
@@ -7608,12 +7863,12 @@ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = fal
|
|
|
7608
7863
|
return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
|
|
7609
7864
|
}
|
|
7610
7865
|
function findOptionalPeerViolations(modulesDir) {
|
|
7611
|
-
if (!
|
|
7866
|
+
if (!import_node_fs3.default.existsSync(modulesDir)) return [];
|
|
7612
7867
|
const manifests = {};
|
|
7613
|
-
for (const name of
|
|
7614
|
-
const manifestPath =
|
|
7615
|
-
if (!
|
|
7616
|
-
manifests[name] = JSON.parse(
|
|
7868
|
+
for (const name of import_node_fs3.default.readdirSync(modulesDir)) {
|
|
7869
|
+
const manifestPath = import_node_path3.default.join(modulesDir, name, "module.json");
|
|
7870
|
+
if (!import_node_fs3.default.existsSync(manifestPath)) continue;
|
|
7871
|
+
manifests[name] = JSON.parse(import_node_fs3.default.readFileSync(manifestPath, "utf8"));
|
|
7617
7872
|
}
|
|
7618
7873
|
const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
|
|
7619
7874
|
const peersByModule = {};
|
|
@@ -7633,11 +7888,11 @@ function formatBoundaryViolations(modulesDirLabel, violations) {
|
|
|
7633
7888
|
// src/index.ts
|
|
7634
7889
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
7635
7890
|
function readComposeInputs(cwd) {
|
|
7636
|
-
const file =
|
|
7637
|
-
if (!
|
|
7891
|
+
const file = import_node_path4.default.join(cwd, COMPOSE_INPUTS_PATH);
|
|
7892
|
+
if (!import_node_fs4.default.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
7638
7893
|
let raw;
|
|
7639
7894
|
try {
|
|
7640
|
-
raw = JSON.parse(
|
|
7895
|
+
raw = JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
|
|
7641
7896
|
} catch (error) {
|
|
7642
7897
|
throw new Error(
|
|
7643
7898
|
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7679,18 +7934,18 @@ function stubGitignoreEntries(artifactPaths) {
|
|
|
7679
7934
|
function writeStubGitignore(cwd, artifactPaths) {
|
|
7680
7935
|
const entries = stubGitignoreEntries(artifactPaths);
|
|
7681
7936
|
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
7682
|
-
const file =
|
|
7683
|
-
const existing =
|
|
7937
|
+
const file = import_node_path4.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7938
|
+
const existing = import_node_fs4.default.existsSync(file) ? import_node_fs4.default.readFileSync(file, "utf8") : null;
|
|
7684
7939
|
if (existing !== content) {
|
|
7685
|
-
|
|
7686
|
-
|
|
7940
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
7941
|
+
import_node_fs4.default.writeFileSync(file, content);
|
|
7687
7942
|
}
|
|
7688
7943
|
return entries.length;
|
|
7689
7944
|
}
|
|
7690
7945
|
function assertStubGitignoreOwned(cwd) {
|
|
7691
|
-
const file =
|
|
7692
|
-
if (!
|
|
7693
|
-
const firstLine =
|
|
7946
|
+
const file = import_node_path4.default.join(cwd, STUB_GITIGNORE_PATH);
|
|
7947
|
+
if (!import_node_fs4.default.existsSync(file)) return;
|
|
7948
|
+
const firstLine = import_node_fs4.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
|
|
7694
7949
|
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
7695
7950
|
throw new Error(
|
|
7696
7951
|
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
@@ -7698,8 +7953,8 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
7698
7953
|
}
|
|
7699
7954
|
}
|
|
7700
7955
|
function assertAppRoot(cwd) {
|
|
7701
|
-
const appPath =
|
|
7702
|
-
if (!
|
|
7956
|
+
const appPath = import_node_path4.default.join(cwd, APP_PACKAGE_JSON);
|
|
7957
|
+
if (!import_node_fs4.default.existsSync(appPath)) {
|
|
7703
7958
|
throw new Error(
|
|
7704
7959
|
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
7705
7960
|
);
|
|
@@ -7745,9 +8000,9 @@ ${porcelain.trim()}`
|
|
|
7745
8000
|
}
|
|
7746
8001
|
var APP_SRC_DIR = "apps/web/src";
|
|
7747
8002
|
function walkSources(dir, skip, out) {
|
|
7748
|
-
if (!
|
|
7749
|
-
for (const entry of
|
|
7750
|
-
const full =
|
|
8003
|
+
if (!import_node_fs4.default.existsSync(dir)) return;
|
|
8004
|
+
for (const entry of import_node_fs4.default.readdirSync(dir, { withFileTypes: true })) {
|
|
8005
|
+
const full = import_node_path4.default.join(dir, entry.name);
|
|
7751
8006
|
if (full === skip) continue;
|
|
7752
8007
|
if (entry.isDirectory()) walkSources(full, skip, out);
|
|
7753
8008
|
else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
|
|
@@ -7756,13 +8011,13 @@ function walkSources(dir, skip, out) {
|
|
|
7756
8011
|
function assertNoAppLayerImporters(cwd, excluded) {
|
|
7757
8012
|
if (excluded.length === 0) return;
|
|
7758
8013
|
const files = [];
|
|
7759
|
-
walkSources(
|
|
8014
|
+
walkSources(import_node_path4.default.join(cwd, APP_SRC_DIR), import_node_path4.default.join(cwd, MODULES_DIR2), files);
|
|
7760
8015
|
const offenders = [];
|
|
7761
8016
|
for (const file of files) {
|
|
7762
|
-
const source =
|
|
8017
|
+
const source = import_node_fs4.default.readFileSync(file, "utf8");
|
|
7763
8018
|
for (const name of excluded) {
|
|
7764
8019
|
if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
|
|
7765
|
-
offenders.push(`${
|
|
8020
|
+
offenders.push(`${import_node_path4.default.relative(cwd, file)}: ${name}`);
|
|
7766
8021
|
}
|
|
7767
8022
|
}
|
|
7768
8023
|
}
|
|
@@ -7796,8 +8051,8 @@ var BUILD_TYPECHECK_SKIP = [
|
|
|
7796
8051
|
"playwright.config.ts"
|
|
7797
8052
|
];
|
|
7798
8053
|
function writeBuildTsconfig(cwd, excluded) {
|
|
7799
|
-
const baseFile =
|
|
7800
|
-
if (!
|
|
8054
|
+
const baseFile = import_node_path4.default.join(cwd, TSCONFIG_PATH);
|
|
8055
|
+
if (!import_node_fs4.default.existsSync(import_node_path4.default.join(cwd, MODULES_DIR2)) || !import_node_fs4.default.existsSync(baseFile)) return;
|
|
7801
8056
|
const base = import_typescript6.default.readConfigFile(baseFile, import_typescript6.default.sys.readFile);
|
|
7802
8057
|
if (base.error) {
|
|
7803
8058
|
throw new Error(
|
|
@@ -7805,7 +8060,7 @@ function writeBuildTsconfig(cwd, excluded) {
|
|
|
7805
8060
|
);
|
|
7806
8061
|
}
|
|
7807
8062
|
const baseExclude = base.config?.exclude ?? ["node_modules"];
|
|
7808
|
-
const modulesDir =
|
|
8063
|
+
const modulesDir = import_node_path4.default.posix.relative("apps/web", MODULES_DIR2);
|
|
7809
8064
|
const config = {
|
|
7810
8065
|
extends: "./tsconfig.json",
|
|
7811
8066
|
exclude: [
|
|
@@ -7820,17 +8075,17 @@ function writeBuildTsconfig(cwd, excluded) {
|
|
|
7820
8075
|
const content = `${BUILD_TSCONFIG_HEADER}
|
|
7821
8076
|
${JSON.stringify(config, null, 2)}
|
|
7822
8077
|
`;
|
|
7823
|
-
const file =
|
|
7824
|
-
if (!
|
|
7825
|
-
|
|
8078
|
+
const file = import_node_path4.default.join(cwd, BUILD_TSCONFIG_PATH);
|
|
8079
|
+
if (!import_node_fs4.default.existsSync(file) || import_node_fs4.default.readFileSync(file, "utf8") !== content) {
|
|
8080
|
+
import_node_fs4.default.writeFileSync(file, content);
|
|
7826
8081
|
}
|
|
7827
8082
|
console.log(
|
|
7828
8083
|
`[compose] ${BUILD_TSCONFIG_PATH} excludes ${excluded.length} Module(s): ${excluded.join(",") || "none"}`
|
|
7829
8084
|
);
|
|
7830
8085
|
}
|
|
7831
8086
|
function removeDir(absolute) {
|
|
7832
|
-
if (!
|
|
7833
|
-
|
|
8087
|
+
if (!import_node_fs4.default.existsSync(absolute)) return false;
|
|
8088
|
+
import_node_fs4.default.rmSync(absolute, { recursive: true, force: true });
|
|
7834
8089
|
return true;
|
|
7835
8090
|
}
|
|
7836
8091
|
async function compose(options) {
|
|
@@ -7858,7 +8113,7 @@ async function compose(options) {
|
|
|
7858
8113
|
});
|
|
7859
8114
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
7860
8115
|
const violations = findModuleBoundaryViolations(
|
|
7861
|
-
|
|
8116
|
+
import_node_path4.default.join(options.cwd, MODULES_DIR2),
|
|
7862
8117
|
Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
|
|
7863
8118
|
{ pruning: true }
|
|
7864
8119
|
);
|
|
@@ -7879,11 +8134,11 @@ async function compose(options) {
|
|
|
7879
8134
|
let pruned = 0;
|
|
7880
8135
|
if (options.prune) {
|
|
7881
8136
|
for (const name of result.excludedModules) {
|
|
7882
|
-
if (removeDir(
|
|
8137
|
+
if (removeDir(import_node_path4.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
|
|
7883
8138
|
}
|
|
7884
8139
|
for (const owner of result.composedModules) {
|
|
7885
8140
|
for (const peer of result.excludedModules) {
|
|
7886
|
-
if (removeDir(
|
|
8141
|
+
if (removeDir(import_node_path4.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
|
|
7887
8142
|
}
|
|
7888
8143
|
}
|
|
7889
8144
|
}
|
|
@@ -7905,13 +8160,18 @@ async function main() {
|
|
|
7905
8160
|
enabled: { type: "string" },
|
|
7906
8161
|
prune: { type: "boolean", default: false },
|
|
7907
8162
|
cwd: { type: "string" },
|
|
7908
|
-
"check-boundaries": { type: "boolean", default: false }
|
|
8163
|
+
"check-boundaries": { type: "boolean", default: false },
|
|
8164
|
+
"check-ownership": { type: "boolean", default: false }
|
|
7909
8165
|
},
|
|
7910
8166
|
strict: true
|
|
7911
8167
|
});
|
|
8168
|
+
if (values["check-ownership"]) {
|
|
8169
|
+
await checkOwnership(values.cwd ?? process.cwd());
|
|
8170
|
+
return;
|
|
8171
|
+
}
|
|
7912
8172
|
if (values["check-boundaries"]) {
|
|
7913
8173
|
const violations = findOptionalPeerViolations(
|
|
7914
|
-
|
|
8174
|
+
import_node_path5.default.join(values.cwd ?? process.cwd(), MODULES_SANDBOX_DIR)
|
|
7915
8175
|
);
|
|
7916
8176
|
if (violations.length > 0) {
|
|
7917
8177
|
throw new Error(
|
|
@@ -7936,6 +8196,36 @@ async function main() {
|
|
|
7936
8196
|
`[compose] modules=${result.composedModules.join(",") || "none"} excluded=${result.excludedModules.join(",") || "none"} writes=${result.writes} deletes=${result.deletes} pruned=${result.pruned} gitignore=${result.gitignoreEntries ?? "kept"} stub paths`
|
|
7937
8197
|
);
|
|
7938
8198
|
}
|
|
8199
|
+
function gitAt(cwd) {
|
|
8200
|
+
return (args) => new Promise((resolve, reject) => {
|
|
8201
|
+
const child = (0, import_node_child_process3.spawn)("git", ["-c", "core.quotePath=false", ...args], {
|
|
8202
|
+
cwd,
|
|
8203
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
8204
|
+
});
|
|
8205
|
+
let stdout = "";
|
|
8206
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
|
|
8207
|
+
child.on("error", reject);
|
|
8208
|
+
child.on("close", (status) => resolve({ status: status ?? 1, stdout }));
|
|
8209
|
+
});
|
|
8210
|
+
}
|
|
8211
|
+
async function checkOwnership(cwd) {
|
|
8212
|
+
const repoRoot = import_node_path5.default.resolve(cwd);
|
|
8213
|
+
const result = await checkStardeckOwnedFiles({ repoRoot, git: gitAt(repoRoot) });
|
|
8214
|
+
switch (result.status) {
|
|
8215
|
+
case "skipped":
|
|
8216
|
+
if (result.warning)
|
|
8217
|
+
console.warn(`[compose] --check-ownership: not checked: ${result.reason}`);
|
|
8218
|
+
else console.log(`[compose] --check-ownership: skipped: ${result.reason}`);
|
|
8219
|
+
return;
|
|
8220
|
+
case "clean":
|
|
8221
|
+
console.log(
|
|
8222
|
+
`[compose] --check-ownership: no Stardeck-owned file changed from Stardeck's version (${result.release.slice(0, 12)})`
|
|
8223
|
+
);
|
|
8224
|
+
return;
|
|
8225
|
+
default:
|
|
8226
|
+
throw new Error(`[compose] --check-ownership: ${formatOwnershipGuardFailure(result)}`);
|
|
8227
|
+
}
|
|
8228
|
+
}
|
|
7939
8229
|
main().catch((error) => {
|
|
7940
8230
|
console.error(error instanceof Error ? error.message : String(error));
|
|
7941
8231
|
process.exit(1);
|