@stardeck-customer-apps/compose 0.14.0 → 0.14.1
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/dist/cli.js +33 -36
- package/dist/index.js +33 -36
- package/dist/index.mjs +33 -36
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2259,10 +2259,12 @@ var modulePermissionClaimSchema = import_zod.z.object({
|
|
|
2259
2259
|
/** Human label for the deployment auth config row. */
|
|
2260
2260
|
description: descriptionSchema,
|
|
2261
2261
|
/**
|
|
2262
|
-
* The
|
|
2263
|
-
*
|
|
2264
|
-
*
|
|
2265
|
-
*
|
|
2262
|
+
* The standard organization roles that hold this key. The platform sets them on every app
|
|
2263
|
+
* (a Blueprint fork, a Module install or uninstall, every Blueprint update), so this list is
|
|
2264
|
+
* the truth: a role added here gains the key, a role removed loses it, and nobody can edit a
|
|
2265
|
+
* standard role's grants on a Module key. Admin holds every key without being listed;
|
|
2266
|
+
* External Viewer never holds one. An organization that wants different access duplicates the
|
|
2267
|
+
* standard role and edits the copy.
|
|
2266
2268
|
*/
|
|
2267
2269
|
roles: import_zod.z.array(import_zod.z.enum(MODULE_PERMISSION_DEFAULT_ROLES)).optional()
|
|
2268
2270
|
}).strict();
|
|
@@ -3982,6 +3984,7 @@ var JSON_SECRET_PAIR = new RegExp(
|
|
|
3982
3984
|
"g"
|
|
3983
3985
|
);
|
|
3984
3986
|
var SECRET_QUOTE_OPENER = new RegExp(String.raw`\b${SECRET_NAME}\s*=\s*(['"])`, "g");
|
|
3987
|
+
var SHELL_QUOTE_CONTINUATION = String.raw`\''`;
|
|
3985
3988
|
function redactSecrets(text) {
|
|
3986
3989
|
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
3990
|
}
|
|
@@ -4827,6 +4830,9 @@ function enhancementImport(owner, peer) {
|
|
|
4827
4830
|
function scopeLabel(scope) {
|
|
4828
4831
|
return scope.kind === "root" ? "root" : `enhancement:${scope.peer}`;
|
|
4829
4832
|
}
|
|
4833
|
+
function endpointMarkerOwnsStub(marker, stub) {
|
|
4834
|
+
return stub != null && marker?.kind === "endpoint" && marker.owner === stub.owner;
|
|
4835
|
+
}
|
|
4830
4836
|
function formatEndpointMarker(owner, scope) {
|
|
4831
4837
|
return `// ${MODULE_STUB_MARKER} owner=${owner} scope=${scopeLabel(scope)}`;
|
|
4832
4838
|
}
|
|
@@ -5528,7 +5534,7 @@ function planCompositionArtifacts(input) {
|
|
|
5528
5534
|
const legacy = parseLegacyApiAdapter(pagePath, content);
|
|
5529
5535
|
const plannedRoute = plannedRouteByPath.get(pagePath);
|
|
5530
5536
|
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 && (
|
|
5537
|
+
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
5538
|
if (isExactPlannedMatch) {
|
|
5533
5539
|
continue;
|
|
5534
5540
|
}
|
|
@@ -5671,9 +5677,7 @@ function planCompositionArtifacts(input) {
|
|
|
5671
5677
|
}
|
|
5672
5678
|
if (marker.kind === "endpoint") {
|
|
5673
5679
|
const stub = endpointStubByPath.get(path5);
|
|
5674
|
-
|
|
5675
|
-
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5676
|
-
if (!ownerMatches || !scopeMatches) {
|
|
5680
|
+
if (!endpointMarkerOwnsStub(marker, stub)) {
|
|
5677
5681
|
return {
|
|
5678
5682
|
ok: false,
|
|
5679
5683
|
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
|
|
@@ -6316,39 +6320,24 @@ async function assertNoRootRoutesWhenMountsEmpty(context, moduleName) {
|
|
|
6316
6320
|
);
|
|
6317
6321
|
}
|
|
6318
6322
|
}
|
|
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) {
|
|
6323
|
+
async function assertNoEnhancementRoutePaths(context, paths) {
|
|
6324
|
+
const kinds = await classifySandboxPathPresenceKindMany(context, paths, "enhancement-route-kind");
|
|
6325
|
+
for (const sliceRoutes of paths) {
|
|
6326
|
+
const kind = kinds.get(sliceRoutes);
|
|
6327
|
+
if (kind === "absent") continue;
|
|
6328
|
+
if (kind !== "unreadable" && kind !== void 0) {
|
|
6336
6329
|
throw new Error(
|
|
6337
6330
|
`[ModuleRail] composition artifacts: enhancement slice routes/ are forbidden at ${sliceRoutes}`
|
|
6338
6331
|
);
|
|
6339
6332
|
}
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: ${redactSecrets(detail)}`
|
|
6344
|
-
);
|
|
6345
|
-
}
|
|
6333
|
+
throw new Error(
|
|
6334
|
+
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: entry-kind:${kind ?? "missing"}`
|
|
6335
|
+
);
|
|
6346
6336
|
}
|
|
6347
6337
|
}
|
|
6348
|
-
async function
|
|
6338
|
+
async function discoverRootRouteEntries(context, moduleName) {
|
|
6349
6339
|
const modulePath = `${MODULES_SANDBOX_DIR}/${moduleName}`;
|
|
6350
6340
|
const routesDir = `${modulePath}/routes`;
|
|
6351
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
6352
6341
|
const quotedRoutes = shellQuote(routesDir);
|
|
6353
6342
|
const listResult = await context.sandbox.exec(
|
|
6354
6343
|
[
|
|
@@ -6802,7 +6791,8 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6802
6791
|
`# Leaf is missing. A missing intermediate parent means the path is simply`,
|
|
6803
6792
|
`# absent (new nested writes). Only an existing-but-inaccessible parent`,
|
|
6804
6793
|
`# is unreadable \u2014 never conflate ENOENT parents with EACCES.`,
|
|
6805
|
-
|
|
6794
|
+
// Probe paths are nested repo-relative paths; do not spawn dirname per missing file.
|
|
6795
|
+
`parent=\${p%/*}`,
|
|
6806
6796
|
`if test ! -e "$parent" && test ! -L "$parent"; then`,
|
|
6807
6797
|
` printf 'entry-kind:absent\\t%s\\n' "$p"`,
|
|
6808
6798
|
`elif ls -ld -- "$parent" >/dev/null 2>&1; then`,
|
|
@@ -7347,14 +7337,21 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7347
7337
|
manifestsByName
|
|
7348
7338
|
);
|
|
7349
7339
|
const routeEntriesByOwner = {};
|
|
7340
|
+
await assertNoEnhancementRoutePaths(
|
|
7341
|
+
context,
|
|
7342
|
+
moduleRuntimeFacts.flatMap(
|
|
7343
|
+
({ moduleName, onDiskEnhancementDirs }) => onDiskEnhancementDirs.map(
|
|
7344
|
+
(peer) => `${MODULES_SANDBOX_DIR}/${moduleName}/enhancements/${peer}/routes`
|
|
7345
|
+
)
|
|
7346
|
+
)
|
|
7347
|
+
);
|
|
7350
7348
|
for (const moduleName of composedNames) {
|
|
7351
7349
|
const manifest = manifestsByName[moduleName];
|
|
7352
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
7353
7350
|
if (manifest.mounts.length === 0) {
|
|
7354
7351
|
await assertNoRootRoutesWhenMountsEmpty(context, moduleName);
|
|
7355
7352
|
routeEntriesByOwner[moduleName] = [];
|
|
7356
7353
|
} else {
|
|
7357
|
-
routeEntriesByOwner[moduleName] = await
|
|
7354
|
+
routeEntriesByOwner[moduleName] = await discoverRootRouteEntries(context, moduleName);
|
|
7358
7355
|
}
|
|
7359
7356
|
}
|
|
7360
7357
|
const existingGeneratedPaths = await listExistingGeneratedStubPaths(context);
|
package/dist/index.js
CHANGED
|
@@ -2097,6 +2097,7 @@ var JSON_SECRET_PAIR = new RegExp(
|
|
|
2097
2097
|
"g"
|
|
2098
2098
|
);
|
|
2099
2099
|
var SECRET_QUOTE_OPENER = new RegExp(String.raw`\b${SECRET_NAME}\s*=\s*(['"])`, "g");
|
|
2100
|
+
var SHELL_QUOTE_CONTINUATION = String.raw`\''`;
|
|
2100
2101
|
function redactSecrets(text) {
|
|
2101
2102
|
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***");
|
|
2102
2103
|
}
|
|
@@ -2388,10 +2389,12 @@ var modulePermissionClaimSchema = import_zod2.z.object({
|
|
|
2388
2389
|
/** Human label for the deployment auth config row. */
|
|
2389
2390
|
description: descriptionSchema,
|
|
2390
2391
|
/**
|
|
2391
|
-
* The
|
|
2392
|
-
*
|
|
2393
|
-
*
|
|
2394
|
-
*
|
|
2392
|
+
* The standard organization roles that hold this key. The platform sets them on every app
|
|
2393
|
+
* (a Blueprint fork, a Module install or uninstall, every Blueprint update), so this list is
|
|
2394
|
+
* the truth: a role added here gains the key, a role removed loses it, and nobody can edit a
|
|
2395
|
+
* standard role's grants on a Module key. Admin holds every key without being listed;
|
|
2396
|
+
* External Viewer never holds one. An organization that wants different access duplicates the
|
|
2397
|
+
* standard role and edits the copy.
|
|
2395
2398
|
*/
|
|
2396
2399
|
roles: import_zod2.z.array(import_zod2.z.enum(MODULE_PERMISSION_DEFAULT_ROLES)).optional()
|
|
2397
2400
|
}).strict();
|
|
@@ -4837,6 +4840,9 @@ function enhancementImport(owner, peer) {
|
|
|
4837
4840
|
function scopeLabel(scope) {
|
|
4838
4841
|
return scope.kind === "root" ? "root" : `enhancement:${scope.peer}`;
|
|
4839
4842
|
}
|
|
4843
|
+
function endpointMarkerOwnsStub(marker, stub) {
|
|
4844
|
+
return stub != null && marker?.kind === "endpoint" && marker.owner === stub.owner;
|
|
4845
|
+
}
|
|
4840
4846
|
function formatEndpointMarker(owner, scope) {
|
|
4841
4847
|
return `// ${MODULE_STUB_MARKER} owner=${owner} scope=${scopeLabel(scope)}`;
|
|
4842
4848
|
}
|
|
@@ -5538,7 +5544,7 @@ function planCompositionArtifacts(input) {
|
|
|
5538
5544
|
const legacy = parseLegacyApiAdapter(pagePath, content);
|
|
5539
5545
|
const plannedRoute = plannedRouteByPath.get(pagePath);
|
|
5540
5546
|
const plannedEndpoint = plannedEndpointByPath.get(pagePath);
|
|
5541
|
-
const isExactPlannedMatch = plannedRoute != null && marker != null && marker.kind === "route" && marker.owner === plannedRoute.owner && marker.nextRelativePath === plannedRoute.nextRelativePath || plannedEndpoint != null && (
|
|
5547
|
+
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);
|
|
5542
5548
|
if (isExactPlannedMatch) {
|
|
5543
5549
|
continue;
|
|
5544
5550
|
}
|
|
@@ -5681,9 +5687,7 @@ function planCompositionArtifacts(input) {
|
|
|
5681
5687
|
}
|
|
5682
5688
|
if (marker.kind === "endpoint") {
|
|
5683
5689
|
const stub = endpointStubByPath.get(path4);
|
|
5684
|
-
|
|
5685
|
-
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5686
|
-
if (!ownerMatches || !scopeMatches) {
|
|
5690
|
+
if (!endpointMarkerOwnsStub(marker, stub)) {
|
|
5687
5691
|
return {
|
|
5688
5692
|
ok: false,
|
|
5689
5693
|
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
|
|
@@ -6326,39 +6330,24 @@ async function assertNoRootRoutesWhenMountsEmpty(context, moduleName) {
|
|
|
6326
6330
|
);
|
|
6327
6331
|
}
|
|
6328
6332
|
}
|
|
6329
|
-
async function
|
|
6330
|
-
const
|
|
6331
|
-
const
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
for (const entry of enhancementEntries) {
|
|
6336
|
-
if (entry.kind !== "dir") continue;
|
|
6337
|
-
const sliceRoutes = `${enhancementsDir}/${entry.name}/routes`;
|
|
6338
|
-
const sliceProbe = await context.sandbox.exec(
|
|
6339
|
-
[
|
|
6340
|
-
`p=${shellQuote(sliceRoutes)}`,
|
|
6341
|
-
`if test -L "$p" || test -e "$p"; then exit 2; else exit 0; fi`
|
|
6342
|
-
].join("\n"),
|
|
6343
|
-
{ raiseOnError: false }
|
|
6344
|
-
);
|
|
6345
|
-
if (sliceProbe.exitCode === 2) {
|
|
6333
|
+
async function assertNoEnhancementRoutePaths(context, paths) {
|
|
6334
|
+
const kinds = await classifySandboxPathPresenceKindMany(context, paths, "enhancement-route-kind");
|
|
6335
|
+
for (const sliceRoutes of paths) {
|
|
6336
|
+
const kind = kinds.get(sliceRoutes);
|
|
6337
|
+
if (kind === "absent") continue;
|
|
6338
|
+
if (kind !== "unreadable" && kind !== void 0) {
|
|
6346
6339
|
throw new Error(
|
|
6347
6340
|
`[ModuleRail] composition artifacts: enhancement slice routes/ are forbidden at ${sliceRoutes}`
|
|
6348
6341
|
);
|
|
6349
6342
|
}
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: ${redactSecrets(detail)}`
|
|
6354
|
-
);
|
|
6355
|
-
}
|
|
6343
|
+
throw new Error(
|
|
6344
|
+
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: entry-kind:${kind ?? "missing"}`
|
|
6345
|
+
);
|
|
6356
6346
|
}
|
|
6357
6347
|
}
|
|
6358
|
-
async function
|
|
6348
|
+
async function discoverRootRouteEntries(context, moduleName) {
|
|
6359
6349
|
const modulePath = `${MODULES_SANDBOX_DIR}/${moduleName}`;
|
|
6360
6350
|
const routesDir = `${modulePath}/routes`;
|
|
6361
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
6362
6351
|
const quotedRoutes = shellQuote(routesDir);
|
|
6363
6352
|
const listResult = await context.sandbox.exec(
|
|
6364
6353
|
[
|
|
@@ -6812,7 +6801,8 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6812
6801
|
`# Leaf is missing. A missing intermediate parent means the path is simply`,
|
|
6813
6802
|
`# absent (new nested writes). Only an existing-but-inaccessible parent`,
|
|
6814
6803
|
`# is unreadable \u2014 never conflate ENOENT parents with EACCES.`,
|
|
6815
|
-
|
|
6804
|
+
// Probe paths are nested repo-relative paths; do not spawn dirname per missing file.
|
|
6805
|
+
`parent=\${p%/*}`,
|
|
6816
6806
|
`if test ! -e "$parent" && test ! -L "$parent"; then`,
|
|
6817
6807
|
` printf 'entry-kind:absent\\t%s\\n' "$p"`,
|
|
6818
6808
|
`elif ls -ld -- "$parent" >/dev/null 2>&1; then`,
|
|
@@ -7357,14 +7347,21 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7357
7347
|
manifestsByName
|
|
7358
7348
|
);
|
|
7359
7349
|
const routeEntriesByOwner = {};
|
|
7350
|
+
await assertNoEnhancementRoutePaths(
|
|
7351
|
+
context,
|
|
7352
|
+
moduleRuntimeFacts.flatMap(
|
|
7353
|
+
({ moduleName, onDiskEnhancementDirs }) => onDiskEnhancementDirs.map(
|
|
7354
|
+
(peer) => `${MODULES_SANDBOX_DIR}/${moduleName}/enhancements/${peer}/routes`
|
|
7355
|
+
)
|
|
7356
|
+
)
|
|
7357
|
+
);
|
|
7360
7358
|
for (const moduleName of composedNames) {
|
|
7361
7359
|
const manifest = manifestsByName[moduleName];
|
|
7362
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
7363
7360
|
if (manifest.mounts.length === 0) {
|
|
7364
7361
|
await assertNoRootRoutesWhenMountsEmpty(context, moduleName);
|
|
7365
7362
|
routeEntriesByOwner[moduleName] = [];
|
|
7366
7363
|
} else {
|
|
7367
|
-
routeEntriesByOwner[moduleName] = await
|
|
7364
|
+
routeEntriesByOwner[moduleName] = await discoverRootRouteEntries(context, moduleName);
|
|
7368
7365
|
}
|
|
7369
7366
|
}
|
|
7370
7367
|
const existingGeneratedPaths = await listExistingGeneratedStubPaths(context);
|
package/dist/index.mjs
CHANGED
|
@@ -2078,6 +2078,7 @@ var JSON_SECRET_PAIR = new RegExp(
|
|
|
2078
2078
|
"g"
|
|
2079
2079
|
);
|
|
2080
2080
|
var SECRET_QUOTE_OPENER = new RegExp(String.raw`\b${SECRET_NAME}\s*=\s*(['"])`, "g");
|
|
2081
|
+
var SHELL_QUOTE_CONTINUATION = String.raw`\''`;
|
|
2081
2082
|
function redactSecrets(text) {
|
|
2082
2083
|
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***");
|
|
2083
2084
|
}
|
|
@@ -2369,10 +2370,12 @@ var modulePermissionClaimSchema = z2.object({
|
|
|
2369
2370
|
/** Human label for the deployment auth config row. */
|
|
2370
2371
|
description: descriptionSchema,
|
|
2371
2372
|
/**
|
|
2372
|
-
* The
|
|
2373
|
-
*
|
|
2374
|
-
*
|
|
2375
|
-
*
|
|
2373
|
+
* The standard organization roles that hold this key. The platform sets them on every app
|
|
2374
|
+
* (a Blueprint fork, a Module install or uninstall, every Blueprint update), so this list is
|
|
2375
|
+
* the truth: a role added here gains the key, a role removed loses it, and nobody can edit a
|
|
2376
|
+
* standard role's grants on a Module key. Admin holds every key without being listed;
|
|
2377
|
+
* External Viewer never holds one. An organization that wants different access duplicates the
|
|
2378
|
+
* standard role and edits the copy.
|
|
2376
2379
|
*/
|
|
2377
2380
|
roles: z2.array(z2.enum(MODULE_PERMISSION_DEFAULT_ROLES)).optional()
|
|
2378
2381
|
}).strict();
|
|
@@ -4818,6 +4821,9 @@ function enhancementImport(owner, peer) {
|
|
|
4818
4821
|
function scopeLabel(scope) {
|
|
4819
4822
|
return scope.kind === "root" ? "root" : `enhancement:${scope.peer}`;
|
|
4820
4823
|
}
|
|
4824
|
+
function endpointMarkerOwnsStub(marker, stub) {
|
|
4825
|
+
return stub != null && marker?.kind === "endpoint" && marker.owner === stub.owner;
|
|
4826
|
+
}
|
|
4821
4827
|
function formatEndpointMarker(owner, scope) {
|
|
4822
4828
|
return `// ${MODULE_STUB_MARKER} owner=${owner} scope=${scopeLabel(scope)}`;
|
|
4823
4829
|
}
|
|
@@ -5519,7 +5525,7 @@ function planCompositionArtifacts(input) {
|
|
|
5519
5525
|
const legacy = parseLegacyApiAdapter(pagePath, content);
|
|
5520
5526
|
const plannedRoute = plannedRouteByPath.get(pagePath);
|
|
5521
5527
|
const plannedEndpoint = plannedEndpointByPath.get(pagePath);
|
|
5522
|
-
const isExactPlannedMatch = plannedRoute != null && marker != null && marker.kind === "route" && marker.owner === plannedRoute.owner && marker.nextRelativePath === plannedRoute.nextRelativePath || plannedEndpoint != null && (
|
|
5528
|
+
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);
|
|
5523
5529
|
if (isExactPlannedMatch) {
|
|
5524
5530
|
continue;
|
|
5525
5531
|
}
|
|
@@ -5662,9 +5668,7 @@ function planCompositionArtifacts(input) {
|
|
|
5662
5668
|
}
|
|
5663
5669
|
if (marker.kind === "endpoint") {
|
|
5664
5670
|
const stub = endpointStubByPath.get(path4);
|
|
5665
|
-
|
|
5666
|
-
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5667
|
-
if (!ownerMatches || !scopeMatches) {
|
|
5671
|
+
if (!endpointMarkerOwnsStub(marker, stub)) {
|
|
5668
5672
|
return {
|
|
5669
5673
|
ok: false,
|
|
5670
5674
|
error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
|
|
@@ -6307,39 +6311,24 @@ async function assertNoRootRoutesWhenMountsEmpty(context, moduleName) {
|
|
|
6307
6311
|
);
|
|
6308
6312
|
}
|
|
6309
6313
|
}
|
|
6310
|
-
async function
|
|
6311
|
-
const
|
|
6312
|
-
const
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
for (const entry of enhancementEntries) {
|
|
6317
|
-
if (entry.kind !== "dir") continue;
|
|
6318
|
-
const sliceRoutes = `${enhancementsDir}/${entry.name}/routes`;
|
|
6319
|
-
const sliceProbe = await context.sandbox.exec(
|
|
6320
|
-
[
|
|
6321
|
-
`p=${shellQuote(sliceRoutes)}`,
|
|
6322
|
-
`if test -L "$p" || test -e "$p"; then exit 2; else exit 0; fi`
|
|
6323
|
-
].join("\n"),
|
|
6324
|
-
{ raiseOnError: false }
|
|
6325
|
-
);
|
|
6326
|
-
if (sliceProbe.exitCode === 2) {
|
|
6314
|
+
async function assertNoEnhancementRoutePaths(context, paths) {
|
|
6315
|
+
const kinds = await classifySandboxPathPresenceKindMany(context, paths, "enhancement-route-kind");
|
|
6316
|
+
for (const sliceRoutes of paths) {
|
|
6317
|
+
const kind = kinds.get(sliceRoutes);
|
|
6318
|
+
if (kind === "absent") continue;
|
|
6319
|
+
if (kind !== "unreadable" && kind !== void 0) {
|
|
6327
6320
|
throw new Error(
|
|
6328
6321
|
`[ModuleRail] composition artifacts: enhancement slice routes/ are forbidden at ${sliceRoutes}`
|
|
6329
6322
|
);
|
|
6330
6323
|
}
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: ${redactSecrets(detail)}`
|
|
6335
|
-
);
|
|
6336
|
-
}
|
|
6324
|
+
throw new Error(
|
|
6325
|
+
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: entry-kind:${kind ?? "missing"}`
|
|
6326
|
+
);
|
|
6337
6327
|
}
|
|
6338
6328
|
}
|
|
6339
|
-
async function
|
|
6329
|
+
async function discoverRootRouteEntries(context, moduleName) {
|
|
6340
6330
|
const modulePath = `${MODULES_SANDBOX_DIR}/${moduleName}`;
|
|
6341
6331
|
const routesDir = `${modulePath}/routes`;
|
|
6342
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
6343
6332
|
const quotedRoutes = shellQuote(routesDir);
|
|
6344
6333
|
const listResult = await context.sandbox.exec(
|
|
6345
6334
|
[
|
|
@@ -6793,7 +6782,8 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6793
6782
|
`# Leaf is missing. A missing intermediate parent means the path is simply`,
|
|
6794
6783
|
`# absent (new nested writes). Only an existing-but-inaccessible parent`,
|
|
6795
6784
|
`# is unreadable \u2014 never conflate ENOENT parents with EACCES.`,
|
|
6796
|
-
|
|
6785
|
+
// Probe paths are nested repo-relative paths; do not spawn dirname per missing file.
|
|
6786
|
+
`parent=\${p%/*}`,
|
|
6797
6787
|
`if test ! -e "$parent" && test ! -L "$parent"; then`,
|
|
6798
6788
|
` printf 'entry-kind:absent\\t%s\\n' "$p"`,
|
|
6799
6789
|
`elif ls -ld -- "$parent" >/dev/null 2>&1; then`,
|
|
@@ -7338,14 +7328,21 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7338
7328
|
manifestsByName
|
|
7339
7329
|
);
|
|
7340
7330
|
const routeEntriesByOwner = {};
|
|
7331
|
+
await assertNoEnhancementRoutePaths(
|
|
7332
|
+
context,
|
|
7333
|
+
moduleRuntimeFacts.flatMap(
|
|
7334
|
+
({ moduleName, onDiskEnhancementDirs }) => onDiskEnhancementDirs.map(
|
|
7335
|
+
(peer) => `${MODULES_SANDBOX_DIR}/${moduleName}/enhancements/${peer}/routes`
|
|
7336
|
+
)
|
|
7337
|
+
)
|
|
7338
|
+
);
|
|
7341
7339
|
for (const moduleName of composedNames) {
|
|
7342
7340
|
const manifest = manifestsByName[moduleName];
|
|
7343
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
7344
7341
|
if (manifest.mounts.length === 0) {
|
|
7345
7342
|
await assertNoRootRoutesWhenMountsEmpty(context, moduleName);
|
|
7346
7343
|
routeEntriesByOwner[moduleName] = [];
|
|
7347
7344
|
} else {
|
|
7348
|
-
routeEntriesByOwner[moduleName] = await
|
|
7345
|
+
routeEntriesByOwner[moduleName] = await discoverRootRouteEntries(context, moduleName);
|
|
7349
7346
|
}
|
|
7350
7347
|
}
|
|
7351
7348
|
const existingGeneratedPaths = await listExistingGeneratedStubPaths(context);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/compose",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Regenerates a Stardeck app's Module-rail artifacts — the six src/*.gen.ts registries and every route/endpoint stub — from src/modules/*/module.json",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"build": "rm -rf dist && tsup",
|
|
35
35
|
"dev": "tsup --watch",
|
|
36
36
|
"format": "prettier --write . && oxlint . --fix",
|
|
37
|
-
"typecheck": "
|
|
37
|
+
"typecheck": "node ../../scripts/typecheck.mjs -p tsconfig.test.json",
|
|
38
38
|
"lint": "oxlint src/",
|
|
39
39
|
"test": "vitest run",
|
|
40
40
|
"test:watch": "vitest"
|