@stardeck-customer-apps/compose 0.13.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/SKILL.md +35 -1
- package/dist/cli.js +102 -58
- package/dist/index.js +102 -58
- package/dist/index.mjs +102 -58
- package/package.json +2 -2
package/SKILL.md
CHANGED
|
@@ -12,7 +12,9 @@ Compose turns that declaration into the code Next.js actually builds:
|
|
|
12
12
|
- `apps/web/src/modules.gen.ts` — the Module registry, `isModuleEnabled` and `isMountHeadless`
|
|
13
13
|
- `apps/web/src/module-contributions.gen.ts`
|
|
14
14
|
- `apps/web/src/module-i18n.gen.ts`
|
|
15
|
-
- `apps/web/src/module-init.server.gen.ts`
|
|
15
|
+
- `apps/web/src/module-init.server.gen.ts` — memoized `initializeActiveEnhancements()`:
|
|
16
|
+
each enabled Module's root `initializeModule`, then each active slice's
|
|
17
|
+
`initializeEnhancement`
|
|
16
18
|
- `apps/web/src/module-datastores.gen.ts`
|
|
17
19
|
- `apps/web/src/module-server.gen.ts` — `MODULE_SERVER_REGISTRATIONS`, one entry
|
|
18
20
|
per composed Module that ships `server/registrations.ts`
|
|
@@ -149,6 +151,38 @@ a silent fallback. It holds deployment facts: gitignore it (`apps/web/.gitignore
|
|
|
149
151
|
the bundle. Compose does not check the export names or shapes. The file
|
|
150
152
|
counts as `server/**/*.ts`, so the Module also needs `server/index.ts`.
|
|
151
153
|
Needs compose 0.8.0 or later.
|
|
154
|
+
- **Module initializers.** Server registrations a Module makes into shared
|
|
155
|
+
registries (observers, resolvers, facets, drains) go in a root
|
|
156
|
+
`initialize.server.ts`, any Module kind:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// src/modules/guest-ordering/initialize.server.ts — server-only, idempotent
|
|
160
|
+
import { registerPosCloseObserver } from "@/modules/pos/server";
|
|
161
|
+
|
|
162
|
+
import { wakeReconcileForClosedOrder } from "./server/reconcile";
|
|
163
|
+
|
|
164
|
+
export async function initializeModule() {
|
|
165
|
+
registerPosCloseObserver(wakeReconcileForClosedOrder);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`initializeModule` must be a direct named function declaration callable with
|
|
170
|
+
zero required arguments (no arrow function, default export, re-export or
|
|
171
|
+
generator); compose fails otherwise. Compose imports it into
|
|
172
|
+
`module-init.server.gen.ts`, guarded by `isModuleEnabled("<module>")`, and
|
|
173
|
+
runs every Module initializer before any enhancement initializer, once per
|
|
174
|
+
instance. Every generated page stub (its render and its `generateMetadata`)
|
|
175
|
+
and every generated API route stub awaits `initializeActiveEnhancements()`
|
|
176
|
+
before its enablement guards, so the registrations exist in the route's own
|
|
177
|
+
module graph. Page stubs await it from compose 0.14.0; before that only API
|
|
178
|
+
stubs did. An `initialize()` export
|
|
179
|
+
run only from `instrumentation.ts` does not give you that: Next.js loads
|
|
180
|
+
instrumentation in a separate module graph, so on a cold instance the route
|
|
181
|
+
handlers and server components never see what it registered. A Module left
|
|
182
|
+
out by `--enabled` is not imported. Needs compose 0.14.0 or later (and
|
|
183
|
+
`@stardeck-customer-apps/eslint-plugin` 1.8.0 or later, whose
|
|
184
|
+
`no-module-internal-import` lets the generated file import it).
|
|
185
|
+
|
|
152
186
|
- `@stardeck-customer-apps/compose/runtime` exports `isModuleEnabledByList`,
|
|
153
187
|
the single enablement rule, and imports nothing — it is safe in client
|
|
154
188
|
components.
|
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();
|
|
@@ -3560,17 +3562,20 @@ var import_typescript3 = __toESM(require("typescript"), 1);
|
|
|
3560
3562
|
var CONVENTIONAL_ENTRY_EXPORT_NAME = {
|
|
3561
3563
|
contributions: "contributions",
|
|
3562
3564
|
slotCatalogs: "slotCatalogs",
|
|
3563
|
-
initializeEnhancement: "initializeEnhancement"
|
|
3565
|
+
initializeEnhancement: "initializeEnhancement",
|
|
3566
|
+
initializeModule: "initializeModule"
|
|
3564
3567
|
};
|
|
3565
3568
|
var CONVENTIONAL_ENTRY_DECLARATION_KIND = {
|
|
3566
3569
|
contributions: "variable",
|
|
3567
3570
|
slotCatalogs: "variable",
|
|
3568
|
-
initializeEnhancement: "function"
|
|
3571
|
+
initializeEnhancement: "function",
|
|
3572
|
+
initializeModule: "function"
|
|
3569
3573
|
};
|
|
3570
3574
|
var CONVENTIONAL_ENTRY_FILENAME = {
|
|
3571
3575
|
contributions: "contributions.ts",
|
|
3572
3576
|
slotCatalogs: "slots.ts",
|
|
3573
|
-
initializeEnhancement: "initialize.server.ts"
|
|
3577
|
+
initializeEnhancement: "initialize.server.ts",
|
|
3578
|
+
initializeModule: "initialize.server.ts"
|
|
3574
3579
|
};
|
|
3575
3580
|
function hasModifierKind(modifiers, kind) {
|
|
3576
3581
|
return Boolean(modifiers?.some((m) => m.kind === kind));
|
|
@@ -3669,7 +3674,7 @@ function analyzeConventionalEntryExportWithTs(typescript, sourceText, kind, file
|
|
|
3669
3674
|
}
|
|
3670
3675
|
if (!isZeroArgumentInvocable(typescript, stmt.parameters)) {
|
|
3671
3676
|
errors.push(
|
|
3672
|
-
`"${entryName}" must be invocable with zero arguments (generated caller uses
|
|
3677
|
+
`"${entryName}" must be invocable with zero arguments (generated caller uses ${entryName}()); required parameters are forbidden \u2014 use optional, default, rest, or a this-parameter only`
|
|
3673
3678
|
);
|
|
3674
3679
|
continue;
|
|
3675
3680
|
}
|
|
@@ -3979,6 +3984,7 @@ var JSON_SECRET_PAIR = new RegExp(
|
|
|
3979
3984
|
"g"
|
|
3980
3985
|
);
|
|
3981
3986
|
var SECRET_QUOTE_OPENER = new RegExp(String.raw`\b${SECRET_NAME}\s*=\s*(['"])`, "g");
|
|
3987
|
+
var SHELL_QUOTE_CONTINUATION = String.raw`\''`;
|
|
3982
3988
|
function redactSecrets(text) {
|
|
3983
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***");
|
|
3984
3990
|
}
|
|
@@ -4150,6 +4156,9 @@ function assertSlotCatalogsExport(source, pathLabel) {
|
|
|
4150
4156
|
function assertInitializeEnhancementExport(source, pathLabel) {
|
|
4151
4157
|
assertConventionalEntryExport(source, pathLabel, "initializeEnhancement");
|
|
4152
4158
|
}
|
|
4159
|
+
function assertInitializeModuleExport(source, pathLabel) {
|
|
4160
|
+
assertConventionalEntryExport(source, pathLabel, "initializeModule");
|
|
4161
|
+
}
|
|
4153
4162
|
function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
4154
4163
|
const { analysisErrors } = analyzeClientSafeEntryImports(
|
|
4155
4164
|
source,
|
|
@@ -4669,30 +4678,47 @@ function deriveModuleLocaleLayers(registry, facts) {
|
|
|
4669
4678
|
}
|
|
4670
4679
|
return layers;
|
|
4671
4680
|
}
|
|
4672
|
-
function
|
|
4681
|
+
function moduleInitializerCalls(registry, facts) {
|
|
4682
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4683
|
+
return facts.filter((f) => f.hasRootInitializeServer && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames).map((name, index) => {
|
|
4684
|
+
if (!isValidModuleName(name)) {
|
|
4685
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4686
|
+
}
|
|
4687
|
+
return {
|
|
4688
|
+
exportName: "initializeModule",
|
|
4689
|
+
guard: [name],
|
|
4690
|
+
ident: `initializeModule${index}`,
|
|
4691
|
+
importPath: `@/modules/${name}/initialize.server`
|
|
4692
|
+
};
|
|
4693
|
+
});
|
|
4694
|
+
}
|
|
4695
|
+
function enhancementInitializerCalls(registry, facts) {
|
|
4673
4696
|
const calls = [];
|
|
4674
|
-
const installed = new Set(
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
const
|
|
4679
|
-
for (const enh of [...facts.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4697
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4698
|
+
for (const f of [...facts].sort((a, b) => compareNames(a.moduleName, b.moduleName))) {
|
|
4699
|
+
if (!installed.has(f.moduleName)) continue;
|
|
4700
|
+
const peers = activePeers(registry, f.moduleName);
|
|
4701
|
+
for (const enh of [...f.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4680
4702
|
if (!enh.active || !peers.has(enh.peer)) continue;
|
|
4681
4703
|
if (!enh.hasInitializeServer) continue;
|
|
4682
|
-
const ident = `initializeEnhancement${initializerIndex++}`;
|
|
4683
4704
|
calls.push({
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
ident
|
|
4687
|
-
importPath: `@/modules/${
|
|
4705
|
+
exportName: "initializeEnhancement",
|
|
4706
|
+
guard: [f.moduleName, enh.peer],
|
|
4707
|
+
ident: `initializeEnhancement${calls.length}`,
|
|
4708
|
+
importPath: `@/modules/${f.moduleName}/enhancements/${enh.peer}/initialize.server`
|
|
4688
4709
|
});
|
|
4689
4710
|
}
|
|
4690
4711
|
}
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4712
|
+
return calls;
|
|
4713
|
+
}
|
|
4714
|
+
function emitModuleInitServerGenTs(input) {
|
|
4715
|
+
const moduleCalls = moduleInitializerCalls(input.registry, input.facts);
|
|
4716
|
+
const enhancementCalls = enhancementInitializerCalls(input.registry, input.facts);
|
|
4717
|
+
const calls = [...moduleCalls, ...enhancementCalls];
|
|
4718
|
+
const importFor = (c) => `import { ${c.exportName} as ${c.ident} } from ${JSON.stringify(c.importPath)};`;
|
|
4719
|
+
const importLines = moduleCalls.length === 0 ? calls.map(importFor) : [...calls].sort((a, b) => compareImportSpecifiers(a.importPath, b.importPath)).map(importFor);
|
|
4694
4720
|
const callLines = calls.length === 0 ? [" // Empty composition \u2014 no active enhancement initializers."] : calls.map(
|
|
4695
|
-
(c) => ` if (
|
|
4721
|
+
(c) => ` if (${c.guard.map((name) => `enabled(${JSON.stringify(name)})`).join(" && ")}) await ${c.ident}();`
|
|
4696
4722
|
);
|
|
4697
4723
|
return [
|
|
4698
4724
|
formatInitMarker(),
|
|
@@ -4804,6 +4830,9 @@ function enhancementImport(owner, peer) {
|
|
|
4804
4830
|
function scopeLabel(scope) {
|
|
4805
4831
|
return scope.kind === "root" ? "root" : `enhancement:${scope.peer}`;
|
|
4806
4832
|
}
|
|
4833
|
+
function endpointMarkerOwnsStub(marker, stub) {
|
|
4834
|
+
return stub != null && marker?.kind === "endpoint" && marker.owner === stub.owner;
|
|
4835
|
+
}
|
|
4807
4836
|
function formatEndpointMarker(owner, scope) {
|
|
4808
4837
|
return `// ${MODULE_STUB_MARKER} owner=${owner} scope=${scopeLabel(scope)}`;
|
|
4809
4838
|
}
|
|
@@ -5142,6 +5171,7 @@ function buildRouteStubContent(input) {
|
|
|
5142
5171
|
'import { notFound } from "next/navigation";',
|
|
5143
5172
|
// simple-import-sort puts packages and "@/" aliases in separate groups.
|
|
5144
5173
|
"",
|
|
5174
|
+
'import { initializeActiveEnhancements } from "@/module-init.server.gen";',
|
|
5145
5175
|
'import { isModuleEnabled, isMountHeadless } from "@/modules.gen";',
|
|
5146
5176
|
`import Page from ${emitTsString(input.importModule)};`
|
|
5147
5177
|
];
|
|
@@ -5150,6 +5180,7 @@ function buildRouteStubContent(input) {
|
|
|
5150
5180
|
`import { generateMetadata as sourceGenerateMetadata } from ${emitTsString(input.importModule)};`,
|
|
5151
5181
|
"",
|
|
5152
5182
|
"export async function generateMetadata(...args: Parameters<typeof sourceGenerateMetadata>) {",
|
|
5183
|
+
" await initializeActiveEnhancements();",
|
|
5153
5184
|
...emitGuardLines(guards, "return {};"),
|
|
5154
5185
|
" return sourceGenerateMetadata(...args);",
|
|
5155
5186
|
"}"
|
|
@@ -5160,7 +5191,10 @@ function buildRouteStubContent(input) {
|
|
|
5160
5191
|
"type PageProps =",
|
|
5161
5192
|
" Parameters<typeof Page> extends [infer P, ...unknown[]] ? NonNullable<P> : Record<never, never>;",
|
|
5162
5193
|
"",
|
|
5163
|
-
|
|
5194
|
+
// Module and enhancement initializers run in this route's own module graph
|
|
5195
|
+
// (memoized), so a cold server render sees their registrations.
|
|
5196
|
+
"export default async function ModulePage(props: PageProps) {",
|
|
5197
|
+
" await initializeActiveEnhancements();",
|
|
5164
5198
|
...emitGuardLines(guards, "notFound();"),
|
|
5165
5199
|
" return <Page {...props} />;",
|
|
5166
5200
|
"}",
|
|
@@ -5500,7 +5534,7 @@ function planCompositionArtifacts(input) {
|
|
|
5500
5534
|
const legacy = parseLegacyApiAdapter(pagePath, content);
|
|
5501
5535
|
const plannedRoute = plannedRouteByPath.get(pagePath);
|
|
5502
5536
|
const plannedEndpoint = plannedEndpointByPath.get(pagePath);
|
|
5503
|
-
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);
|
|
5504
5538
|
if (isExactPlannedMatch) {
|
|
5505
5539
|
continue;
|
|
5506
5540
|
}
|
|
@@ -5643,9 +5677,7 @@ function planCompositionArtifacts(input) {
|
|
|
5643
5677
|
}
|
|
5644
5678
|
if (marker.kind === "endpoint") {
|
|
5645
5679
|
const stub = endpointStubByPath.get(path5);
|
|
5646
|
-
|
|
5647
|
-
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5648
|
-
if (!ownerMatches || !scopeMatches) {
|
|
5680
|
+
if (!endpointMarkerOwnsStub(marker, stub)) {
|
|
5649
5681
|
return {
|
|
5650
5682
|
ok: false,
|
|
5651
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) : "?"})`,
|
|
@@ -6288,39 +6320,24 @@ async function assertNoRootRoutesWhenMountsEmpty(context, moduleName) {
|
|
|
6288
6320
|
);
|
|
6289
6321
|
}
|
|
6290
6322
|
}
|
|
6291
|
-
async function
|
|
6292
|
-
const
|
|
6293
|
-
const
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
for (const entry of enhancementEntries) {
|
|
6298
|
-
if (entry.kind !== "dir") continue;
|
|
6299
|
-
const sliceRoutes = `${enhancementsDir}/${entry.name}/routes`;
|
|
6300
|
-
const sliceProbe = await context.sandbox.exec(
|
|
6301
|
-
[
|
|
6302
|
-
`p=${shellQuote(sliceRoutes)}`,
|
|
6303
|
-
`if test -L "$p" || test -e "$p"; then exit 2; else exit 0; fi`
|
|
6304
|
-
].join("\n"),
|
|
6305
|
-
{ raiseOnError: false }
|
|
6306
|
-
);
|
|
6307
|
-
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) {
|
|
6308
6329
|
throw new Error(
|
|
6309
6330
|
`[ModuleRail] composition artifacts: enhancement slice routes/ are forbidden at ${sliceRoutes}`
|
|
6310
6331
|
);
|
|
6311
6332
|
}
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: ${redactSecrets(detail)}`
|
|
6316
|
-
);
|
|
6317
|
-
}
|
|
6333
|
+
throw new Error(
|
|
6334
|
+
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: entry-kind:${kind ?? "missing"}`
|
|
6335
|
+
);
|
|
6318
6336
|
}
|
|
6319
6337
|
}
|
|
6320
|
-
async function
|
|
6338
|
+
async function discoverRootRouteEntries(context, moduleName) {
|
|
6321
6339
|
const modulePath = `${MODULES_SANDBOX_DIR}/${moduleName}`;
|
|
6322
6340
|
const routesDir = `${modulePath}/routes`;
|
|
6323
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
6324
6341
|
const quotedRoutes = shellQuote(routesDir);
|
|
6325
6342
|
const listResult = await context.sandbox.exec(
|
|
6326
6343
|
[
|
|
@@ -6774,7 +6791,8 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6774
6791
|
`# Leaf is missing. A missing intermediate parent means the path is simply`,
|
|
6775
6792
|
`# absent (new nested writes). Only an existing-but-inaccessible parent`,
|
|
6776
6793
|
`# is unreadable \u2014 never conflate ENOENT parents with EACCES.`,
|
|
6777
|
-
|
|
6794
|
+
// Probe paths are nested repo-relative paths; do not spawn dirname per missing file.
|
|
6795
|
+
`parent=\${p%/*}`,
|
|
6778
6796
|
`if test ! -e "$parent" && test ! -L "$parent"; then`,
|
|
6779
6797
|
` printf 'entry-kind:absent\\t%s\\n' "$p"`,
|
|
6780
6798
|
`elif ls -ld -- "$parent" >/dev/null 2>&1; then`,
|
|
@@ -6958,6 +6976,9 @@ async function readConventionalEntryIfRealFile(path5, kind, presences, files) {
|
|
|
6958
6976
|
case "initializeEnhancement":
|
|
6959
6977
|
assertInitializeEnhancementExport(source, path5);
|
|
6960
6978
|
break;
|
|
6979
|
+
case "initializeModule":
|
|
6980
|
+
assertInitializeModuleExport(source, path5);
|
|
6981
|
+
break;
|
|
6961
6982
|
}
|
|
6962
6983
|
return source;
|
|
6963
6984
|
}
|
|
@@ -6994,7 +7015,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
6994
7015
|
for (const mod of registry.modules) {
|
|
6995
7016
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
6996
7017
|
localeDirs.push(`${modulePath}/i18n`);
|
|
6997
|
-
conventionalPaths.push(
|
|
7018
|
+
conventionalPaths.push(
|
|
7019
|
+
`${modulePath}/contributions.ts`,
|
|
7020
|
+
`${modulePath}/slots.ts`,
|
|
7021
|
+
`${modulePath}/initialize.server.ts`
|
|
7022
|
+
);
|
|
6998
7023
|
for (const enh of mod.enhancements) {
|
|
6999
7024
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7000
7025
|
localeDirs.push(`${peerDir}/i18n`);
|
|
@@ -7082,6 +7107,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7082
7107
|
}
|
|
7083
7108
|
}
|
|
7084
7109
|
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7110
|
+
const hasRootInitializeServer = await readConventionalEntryIfRealFile(
|
|
7111
|
+
`${modulePath}/initialize.server.ts`,
|
|
7112
|
+
"initializeModule",
|
|
7113
|
+
conventionalPresences,
|
|
7114
|
+
files
|
|
7115
|
+
) != null;
|
|
7116
|
+
if (hasRootInitializeServer) {
|
|
7117
|
+
console.log(
|
|
7118
|
+
`[ModuleRail] runId=${context.runId} module=${mod.name} has root initialize.server.ts (initializeModule)`
|
|
7119
|
+
);
|
|
7120
|
+
}
|
|
7085
7121
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7086
7122
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7087
7123
|
throw new Error(
|
|
@@ -7122,6 +7158,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7122
7158
|
hasRootContributions,
|
|
7123
7159
|
hasRootSlots,
|
|
7124
7160
|
hasServerRegistrations,
|
|
7161
|
+
hasRootInitializeServer,
|
|
7125
7162
|
rootLocales,
|
|
7126
7163
|
enhancements
|
|
7127
7164
|
});
|
|
@@ -7300,14 +7337,21 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7300
7337
|
manifestsByName
|
|
7301
7338
|
);
|
|
7302
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
|
+
);
|
|
7303
7348
|
for (const moduleName of composedNames) {
|
|
7304
7349
|
const manifest = manifestsByName[moduleName];
|
|
7305
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
7306
7350
|
if (manifest.mounts.length === 0) {
|
|
7307
7351
|
await assertNoRootRoutesWhenMountsEmpty(context, moduleName);
|
|
7308
7352
|
routeEntriesByOwner[moduleName] = [];
|
|
7309
7353
|
} else {
|
|
7310
|
-
routeEntriesByOwner[moduleName] = await
|
|
7354
|
+
routeEntriesByOwner[moduleName] = await discoverRootRouteEntries(context, moduleName);
|
|
7311
7355
|
}
|
|
7312
7356
|
}
|
|
7313
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();
|
|
@@ -3689,17 +3692,20 @@ var import_typescript3 = __toESM(require("typescript"), 1);
|
|
|
3689
3692
|
var CONVENTIONAL_ENTRY_EXPORT_NAME = {
|
|
3690
3693
|
contributions: "contributions",
|
|
3691
3694
|
slotCatalogs: "slotCatalogs",
|
|
3692
|
-
initializeEnhancement: "initializeEnhancement"
|
|
3695
|
+
initializeEnhancement: "initializeEnhancement",
|
|
3696
|
+
initializeModule: "initializeModule"
|
|
3693
3697
|
};
|
|
3694
3698
|
var CONVENTIONAL_ENTRY_DECLARATION_KIND = {
|
|
3695
3699
|
contributions: "variable",
|
|
3696
3700
|
slotCatalogs: "variable",
|
|
3697
|
-
initializeEnhancement: "function"
|
|
3701
|
+
initializeEnhancement: "function",
|
|
3702
|
+
initializeModule: "function"
|
|
3698
3703
|
};
|
|
3699
3704
|
var CONVENTIONAL_ENTRY_FILENAME = {
|
|
3700
3705
|
contributions: "contributions.ts",
|
|
3701
3706
|
slotCatalogs: "slots.ts",
|
|
3702
|
-
initializeEnhancement: "initialize.server.ts"
|
|
3707
|
+
initializeEnhancement: "initialize.server.ts",
|
|
3708
|
+
initializeModule: "initialize.server.ts"
|
|
3703
3709
|
};
|
|
3704
3710
|
function hasModifierKind(modifiers, kind) {
|
|
3705
3711
|
return Boolean(modifiers?.some((m) => m.kind === kind));
|
|
@@ -3798,7 +3804,7 @@ function analyzeConventionalEntryExportWithTs(typescript, sourceText, kind, file
|
|
|
3798
3804
|
}
|
|
3799
3805
|
if (!isZeroArgumentInvocable(typescript, stmt.parameters)) {
|
|
3800
3806
|
errors.push(
|
|
3801
|
-
`"${entryName}" must be invocable with zero arguments (generated caller uses
|
|
3807
|
+
`"${entryName}" must be invocable with zero arguments (generated caller uses ${entryName}()); required parameters are forbidden \u2014 use optional, default, rest, or a this-parameter only`
|
|
3802
3808
|
);
|
|
3803
3809
|
continue;
|
|
3804
3810
|
}
|
|
@@ -4160,6 +4166,9 @@ function assertSlotCatalogsExport(source, pathLabel) {
|
|
|
4160
4166
|
function assertInitializeEnhancementExport(source, pathLabel) {
|
|
4161
4167
|
assertConventionalEntryExport(source, pathLabel, "initializeEnhancement");
|
|
4162
4168
|
}
|
|
4169
|
+
function assertInitializeModuleExport(source, pathLabel) {
|
|
4170
|
+
assertConventionalEntryExport(source, pathLabel, "initializeModule");
|
|
4171
|
+
}
|
|
4163
4172
|
function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
4164
4173
|
const { analysisErrors } = analyzeClientSafeEntryImports(
|
|
4165
4174
|
source,
|
|
@@ -4679,30 +4688,47 @@ function deriveModuleLocaleLayers(registry, facts) {
|
|
|
4679
4688
|
}
|
|
4680
4689
|
return layers;
|
|
4681
4690
|
}
|
|
4682
|
-
function
|
|
4691
|
+
function moduleInitializerCalls(registry, facts) {
|
|
4692
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4693
|
+
return facts.filter((f) => f.hasRootInitializeServer && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames).map((name, index) => {
|
|
4694
|
+
if (!isValidModuleName(name)) {
|
|
4695
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4696
|
+
}
|
|
4697
|
+
return {
|
|
4698
|
+
exportName: "initializeModule",
|
|
4699
|
+
guard: [name],
|
|
4700
|
+
ident: `initializeModule${index}`,
|
|
4701
|
+
importPath: `@/modules/${name}/initialize.server`
|
|
4702
|
+
};
|
|
4703
|
+
});
|
|
4704
|
+
}
|
|
4705
|
+
function enhancementInitializerCalls(registry, facts) {
|
|
4683
4706
|
const calls = [];
|
|
4684
|
-
const installed = new Set(
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
const
|
|
4689
|
-
for (const enh of [...facts.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4707
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4708
|
+
for (const f of [...facts].sort((a, b) => compareNames(a.moduleName, b.moduleName))) {
|
|
4709
|
+
if (!installed.has(f.moduleName)) continue;
|
|
4710
|
+
const peers = activePeers(registry, f.moduleName);
|
|
4711
|
+
for (const enh of [...f.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4690
4712
|
if (!enh.active || !peers.has(enh.peer)) continue;
|
|
4691
4713
|
if (!enh.hasInitializeServer) continue;
|
|
4692
|
-
const ident = `initializeEnhancement${initializerIndex++}`;
|
|
4693
4714
|
calls.push({
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
ident
|
|
4697
|
-
importPath: `@/modules/${
|
|
4715
|
+
exportName: "initializeEnhancement",
|
|
4716
|
+
guard: [f.moduleName, enh.peer],
|
|
4717
|
+
ident: `initializeEnhancement${calls.length}`,
|
|
4718
|
+
importPath: `@/modules/${f.moduleName}/enhancements/${enh.peer}/initialize.server`
|
|
4698
4719
|
});
|
|
4699
4720
|
}
|
|
4700
4721
|
}
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4722
|
+
return calls;
|
|
4723
|
+
}
|
|
4724
|
+
function emitModuleInitServerGenTs(input) {
|
|
4725
|
+
const moduleCalls = moduleInitializerCalls(input.registry, input.facts);
|
|
4726
|
+
const enhancementCalls = enhancementInitializerCalls(input.registry, input.facts);
|
|
4727
|
+
const calls = [...moduleCalls, ...enhancementCalls];
|
|
4728
|
+
const importFor = (c) => `import { ${c.exportName} as ${c.ident} } from ${JSON.stringify(c.importPath)};`;
|
|
4729
|
+
const importLines = moduleCalls.length === 0 ? calls.map(importFor) : [...calls].sort((a, b) => compareImportSpecifiers(a.importPath, b.importPath)).map(importFor);
|
|
4704
4730
|
const callLines = calls.length === 0 ? [" // Empty composition \u2014 no active enhancement initializers."] : calls.map(
|
|
4705
|
-
(c) => ` if (
|
|
4731
|
+
(c) => ` if (${c.guard.map((name) => `enabled(${JSON.stringify(name)})`).join(" && ")}) await ${c.ident}();`
|
|
4706
4732
|
);
|
|
4707
4733
|
return [
|
|
4708
4734
|
formatInitMarker(),
|
|
@@ -4814,6 +4840,9 @@ function enhancementImport(owner, peer) {
|
|
|
4814
4840
|
function scopeLabel(scope) {
|
|
4815
4841
|
return scope.kind === "root" ? "root" : `enhancement:${scope.peer}`;
|
|
4816
4842
|
}
|
|
4843
|
+
function endpointMarkerOwnsStub(marker, stub) {
|
|
4844
|
+
return stub != null && marker?.kind === "endpoint" && marker.owner === stub.owner;
|
|
4845
|
+
}
|
|
4817
4846
|
function formatEndpointMarker(owner, scope) {
|
|
4818
4847
|
return `// ${MODULE_STUB_MARKER} owner=${owner} scope=${scopeLabel(scope)}`;
|
|
4819
4848
|
}
|
|
@@ -5152,6 +5181,7 @@ function buildRouteStubContent(input) {
|
|
|
5152
5181
|
'import { notFound } from "next/navigation";',
|
|
5153
5182
|
// simple-import-sort puts packages and "@/" aliases in separate groups.
|
|
5154
5183
|
"",
|
|
5184
|
+
'import { initializeActiveEnhancements } from "@/module-init.server.gen";',
|
|
5155
5185
|
'import { isModuleEnabled, isMountHeadless } from "@/modules.gen";',
|
|
5156
5186
|
`import Page from ${emitTsString(input.importModule)};`
|
|
5157
5187
|
];
|
|
@@ -5160,6 +5190,7 @@ function buildRouteStubContent(input) {
|
|
|
5160
5190
|
`import { generateMetadata as sourceGenerateMetadata } from ${emitTsString(input.importModule)};`,
|
|
5161
5191
|
"",
|
|
5162
5192
|
"export async function generateMetadata(...args: Parameters<typeof sourceGenerateMetadata>) {",
|
|
5193
|
+
" await initializeActiveEnhancements();",
|
|
5163
5194
|
...emitGuardLines(guards, "return {};"),
|
|
5164
5195
|
" return sourceGenerateMetadata(...args);",
|
|
5165
5196
|
"}"
|
|
@@ -5170,7 +5201,10 @@ function buildRouteStubContent(input) {
|
|
|
5170
5201
|
"type PageProps =",
|
|
5171
5202
|
" Parameters<typeof Page> extends [infer P, ...unknown[]] ? NonNullable<P> : Record<never, never>;",
|
|
5172
5203
|
"",
|
|
5173
|
-
|
|
5204
|
+
// Module and enhancement initializers run in this route's own module graph
|
|
5205
|
+
// (memoized), so a cold server render sees their registrations.
|
|
5206
|
+
"export default async function ModulePage(props: PageProps) {",
|
|
5207
|
+
" await initializeActiveEnhancements();",
|
|
5174
5208
|
...emitGuardLines(guards, "notFound();"),
|
|
5175
5209
|
" return <Page {...props} />;",
|
|
5176
5210
|
"}",
|
|
@@ -5510,7 +5544,7 @@ function planCompositionArtifacts(input) {
|
|
|
5510
5544
|
const legacy = parseLegacyApiAdapter(pagePath, content);
|
|
5511
5545
|
const plannedRoute = plannedRouteByPath.get(pagePath);
|
|
5512
5546
|
const plannedEndpoint = plannedEndpointByPath.get(pagePath);
|
|
5513
|
-
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);
|
|
5514
5548
|
if (isExactPlannedMatch) {
|
|
5515
5549
|
continue;
|
|
5516
5550
|
}
|
|
@@ -5653,9 +5687,7 @@ function planCompositionArtifacts(input) {
|
|
|
5653
5687
|
}
|
|
5654
5688
|
if (marker.kind === "endpoint") {
|
|
5655
5689
|
const stub = endpointStubByPath.get(path4);
|
|
5656
|
-
|
|
5657
|
-
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5658
|
-
if (!ownerMatches || !scopeMatches) {
|
|
5690
|
+
if (!endpointMarkerOwnsStub(marker, stub)) {
|
|
5659
5691
|
return {
|
|
5660
5692
|
ok: false,
|
|
5661
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) : "?"})`,
|
|
@@ -6298,39 +6330,24 @@ async function assertNoRootRoutesWhenMountsEmpty(context, moduleName) {
|
|
|
6298
6330
|
);
|
|
6299
6331
|
}
|
|
6300
6332
|
}
|
|
6301
|
-
async function
|
|
6302
|
-
const
|
|
6303
|
-
const
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
for (const entry of enhancementEntries) {
|
|
6308
|
-
if (entry.kind !== "dir") continue;
|
|
6309
|
-
const sliceRoutes = `${enhancementsDir}/${entry.name}/routes`;
|
|
6310
|
-
const sliceProbe = await context.sandbox.exec(
|
|
6311
|
-
[
|
|
6312
|
-
`p=${shellQuote(sliceRoutes)}`,
|
|
6313
|
-
`if test -L "$p" || test -e "$p"; then exit 2; else exit 0; fi`
|
|
6314
|
-
].join("\n"),
|
|
6315
|
-
{ raiseOnError: false }
|
|
6316
|
-
);
|
|
6317
|
-
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) {
|
|
6318
6339
|
throw new Error(
|
|
6319
6340
|
`[ModuleRail] composition artifacts: enhancement slice routes/ are forbidden at ${sliceRoutes}`
|
|
6320
6341
|
);
|
|
6321
6342
|
}
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: ${redactSecrets(detail)}`
|
|
6326
|
-
);
|
|
6327
|
-
}
|
|
6343
|
+
throw new Error(
|
|
6344
|
+
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: entry-kind:${kind ?? "missing"}`
|
|
6345
|
+
);
|
|
6328
6346
|
}
|
|
6329
6347
|
}
|
|
6330
|
-
async function
|
|
6348
|
+
async function discoverRootRouteEntries(context, moduleName) {
|
|
6331
6349
|
const modulePath = `${MODULES_SANDBOX_DIR}/${moduleName}`;
|
|
6332
6350
|
const routesDir = `${modulePath}/routes`;
|
|
6333
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
6334
6351
|
const quotedRoutes = shellQuote(routesDir);
|
|
6335
6352
|
const listResult = await context.sandbox.exec(
|
|
6336
6353
|
[
|
|
@@ -6784,7 +6801,8 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6784
6801
|
`# Leaf is missing. A missing intermediate parent means the path is simply`,
|
|
6785
6802
|
`# absent (new nested writes). Only an existing-but-inaccessible parent`,
|
|
6786
6803
|
`# is unreadable \u2014 never conflate ENOENT parents with EACCES.`,
|
|
6787
|
-
|
|
6804
|
+
// Probe paths are nested repo-relative paths; do not spawn dirname per missing file.
|
|
6805
|
+
`parent=\${p%/*}`,
|
|
6788
6806
|
`if test ! -e "$parent" && test ! -L "$parent"; then`,
|
|
6789
6807
|
` printf 'entry-kind:absent\\t%s\\n' "$p"`,
|
|
6790
6808
|
`elif ls -ld -- "$parent" >/dev/null 2>&1; then`,
|
|
@@ -6968,6 +6986,9 @@ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
|
6968
6986
|
case "initializeEnhancement":
|
|
6969
6987
|
assertInitializeEnhancementExport(source, path4);
|
|
6970
6988
|
break;
|
|
6989
|
+
case "initializeModule":
|
|
6990
|
+
assertInitializeModuleExport(source, path4);
|
|
6991
|
+
break;
|
|
6971
6992
|
}
|
|
6972
6993
|
return source;
|
|
6973
6994
|
}
|
|
@@ -7004,7 +7025,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7004
7025
|
for (const mod of registry.modules) {
|
|
7005
7026
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
7006
7027
|
localeDirs.push(`${modulePath}/i18n`);
|
|
7007
|
-
conventionalPaths.push(
|
|
7028
|
+
conventionalPaths.push(
|
|
7029
|
+
`${modulePath}/contributions.ts`,
|
|
7030
|
+
`${modulePath}/slots.ts`,
|
|
7031
|
+
`${modulePath}/initialize.server.ts`
|
|
7032
|
+
);
|
|
7008
7033
|
for (const enh of mod.enhancements) {
|
|
7009
7034
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7010
7035
|
localeDirs.push(`${peerDir}/i18n`);
|
|
@@ -7092,6 +7117,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7092
7117
|
}
|
|
7093
7118
|
}
|
|
7094
7119
|
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7120
|
+
const hasRootInitializeServer = await readConventionalEntryIfRealFile(
|
|
7121
|
+
`${modulePath}/initialize.server.ts`,
|
|
7122
|
+
"initializeModule",
|
|
7123
|
+
conventionalPresences,
|
|
7124
|
+
files
|
|
7125
|
+
) != null;
|
|
7126
|
+
if (hasRootInitializeServer) {
|
|
7127
|
+
console.log(
|
|
7128
|
+
`[ModuleRail] runId=${context.runId} module=${mod.name} has root initialize.server.ts (initializeModule)`
|
|
7129
|
+
);
|
|
7130
|
+
}
|
|
7095
7131
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7096
7132
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7097
7133
|
throw new Error(
|
|
@@ -7132,6 +7168,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7132
7168
|
hasRootContributions,
|
|
7133
7169
|
hasRootSlots,
|
|
7134
7170
|
hasServerRegistrations,
|
|
7171
|
+
hasRootInitializeServer,
|
|
7135
7172
|
rootLocales,
|
|
7136
7173
|
enhancements
|
|
7137
7174
|
});
|
|
@@ -7310,14 +7347,21 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7310
7347
|
manifestsByName
|
|
7311
7348
|
);
|
|
7312
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
|
+
);
|
|
7313
7358
|
for (const moduleName of composedNames) {
|
|
7314
7359
|
const manifest = manifestsByName[moduleName];
|
|
7315
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
7316
7360
|
if (manifest.mounts.length === 0) {
|
|
7317
7361
|
await assertNoRootRoutesWhenMountsEmpty(context, moduleName);
|
|
7318
7362
|
routeEntriesByOwner[moduleName] = [];
|
|
7319
7363
|
} else {
|
|
7320
|
-
routeEntriesByOwner[moduleName] = await
|
|
7364
|
+
routeEntriesByOwner[moduleName] = await discoverRootRouteEntries(context, moduleName);
|
|
7321
7365
|
}
|
|
7322
7366
|
}
|
|
7323
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();
|
|
@@ -3670,17 +3673,20 @@ import ts3 from "typescript";
|
|
|
3670
3673
|
var CONVENTIONAL_ENTRY_EXPORT_NAME = {
|
|
3671
3674
|
contributions: "contributions",
|
|
3672
3675
|
slotCatalogs: "slotCatalogs",
|
|
3673
|
-
initializeEnhancement: "initializeEnhancement"
|
|
3676
|
+
initializeEnhancement: "initializeEnhancement",
|
|
3677
|
+
initializeModule: "initializeModule"
|
|
3674
3678
|
};
|
|
3675
3679
|
var CONVENTIONAL_ENTRY_DECLARATION_KIND = {
|
|
3676
3680
|
contributions: "variable",
|
|
3677
3681
|
slotCatalogs: "variable",
|
|
3678
|
-
initializeEnhancement: "function"
|
|
3682
|
+
initializeEnhancement: "function",
|
|
3683
|
+
initializeModule: "function"
|
|
3679
3684
|
};
|
|
3680
3685
|
var CONVENTIONAL_ENTRY_FILENAME = {
|
|
3681
3686
|
contributions: "contributions.ts",
|
|
3682
3687
|
slotCatalogs: "slots.ts",
|
|
3683
|
-
initializeEnhancement: "initialize.server.ts"
|
|
3688
|
+
initializeEnhancement: "initialize.server.ts",
|
|
3689
|
+
initializeModule: "initialize.server.ts"
|
|
3684
3690
|
};
|
|
3685
3691
|
function hasModifierKind(modifiers, kind) {
|
|
3686
3692
|
return Boolean(modifiers?.some((m) => m.kind === kind));
|
|
@@ -3779,7 +3785,7 @@ function analyzeConventionalEntryExportWithTs(typescript, sourceText, kind, file
|
|
|
3779
3785
|
}
|
|
3780
3786
|
if (!isZeroArgumentInvocable(typescript, stmt.parameters)) {
|
|
3781
3787
|
errors.push(
|
|
3782
|
-
`"${entryName}" must be invocable with zero arguments (generated caller uses
|
|
3788
|
+
`"${entryName}" must be invocable with zero arguments (generated caller uses ${entryName}()); required parameters are forbidden \u2014 use optional, default, rest, or a this-parameter only`
|
|
3783
3789
|
);
|
|
3784
3790
|
continue;
|
|
3785
3791
|
}
|
|
@@ -4141,6 +4147,9 @@ function assertSlotCatalogsExport(source, pathLabel) {
|
|
|
4141
4147
|
function assertInitializeEnhancementExport(source, pathLabel) {
|
|
4142
4148
|
assertConventionalEntryExport(source, pathLabel, "initializeEnhancement");
|
|
4143
4149
|
}
|
|
4150
|
+
function assertInitializeModuleExport(source, pathLabel) {
|
|
4151
|
+
assertConventionalEntryExport(source, pathLabel, "initializeModule");
|
|
4152
|
+
}
|
|
4144
4153
|
function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
4145
4154
|
const { analysisErrors } = analyzeClientSafeEntryImports(
|
|
4146
4155
|
source,
|
|
@@ -4660,30 +4669,47 @@ function deriveModuleLocaleLayers(registry, facts) {
|
|
|
4660
4669
|
}
|
|
4661
4670
|
return layers;
|
|
4662
4671
|
}
|
|
4663
|
-
function
|
|
4672
|
+
function moduleInitializerCalls(registry, facts) {
|
|
4673
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4674
|
+
return facts.filter((f) => f.hasRootInitializeServer && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames).map((name, index) => {
|
|
4675
|
+
if (!isValidModuleName(name)) {
|
|
4676
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4677
|
+
}
|
|
4678
|
+
return {
|
|
4679
|
+
exportName: "initializeModule",
|
|
4680
|
+
guard: [name],
|
|
4681
|
+
ident: `initializeModule${index}`,
|
|
4682
|
+
importPath: `@/modules/${name}/initialize.server`
|
|
4683
|
+
};
|
|
4684
|
+
});
|
|
4685
|
+
}
|
|
4686
|
+
function enhancementInitializerCalls(registry, facts) {
|
|
4664
4687
|
const calls = [];
|
|
4665
|
-
const installed = new Set(
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
const
|
|
4670
|
-
for (const enh of [...facts.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4688
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4689
|
+
for (const f of [...facts].sort((a, b) => compareNames(a.moduleName, b.moduleName))) {
|
|
4690
|
+
if (!installed.has(f.moduleName)) continue;
|
|
4691
|
+
const peers = activePeers(registry, f.moduleName);
|
|
4692
|
+
for (const enh of [...f.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4671
4693
|
if (!enh.active || !peers.has(enh.peer)) continue;
|
|
4672
4694
|
if (!enh.hasInitializeServer) continue;
|
|
4673
|
-
const ident = `initializeEnhancement${initializerIndex++}`;
|
|
4674
4695
|
calls.push({
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
ident
|
|
4678
|
-
importPath: `@/modules/${
|
|
4696
|
+
exportName: "initializeEnhancement",
|
|
4697
|
+
guard: [f.moduleName, enh.peer],
|
|
4698
|
+
ident: `initializeEnhancement${calls.length}`,
|
|
4699
|
+
importPath: `@/modules/${f.moduleName}/enhancements/${enh.peer}/initialize.server`
|
|
4679
4700
|
});
|
|
4680
4701
|
}
|
|
4681
4702
|
}
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4703
|
+
return calls;
|
|
4704
|
+
}
|
|
4705
|
+
function emitModuleInitServerGenTs(input) {
|
|
4706
|
+
const moduleCalls = moduleInitializerCalls(input.registry, input.facts);
|
|
4707
|
+
const enhancementCalls = enhancementInitializerCalls(input.registry, input.facts);
|
|
4708
|
+
const calls = [...moduleCalls, ...enhancementCalls];
|
|
4709
|
+
const importFor = (c) => `import { ${c.exportName} as ${c.ident} } from ${JSON.stringify(c.importPath)};`;
|
|
4710
|
+
const importLines = moduleCalls.length === 0 ? calls.map(importFor) : [...calls].sort((a, b) => compareImportSpecifiers(a.importPath, b.importPath)).map(importFor);
|
|
4685
4711
|
const callLines = calls.length === 0 ? [" // Empty composition \u2014 no active enhancement initializers."] : calls.map(
|
|
4686
|
-
(c) => ` if (
|
|
4712
|
+
(c) => ` if (${c.guard.map((name) => `enabled(${JSON.stringify(name)})`).join(" && ")}) await ${c.ident}();`
|
|
4687
4713
|
);
|
|
4688
4714
|
return [
|
|
4689
4715
|
formatInitMarker(),
|
|
@@ -4795,6 +4821,9 @@ function enhancementImport(owner, peer) {
|
|
|
4795
4821
|
function scopeLabel(scope) {
|
|
4796
4822
|
return scope.kind === "root" ? "root" : `enhancement:${scope.peer}`;
|
|
4797
4823
|
}
|
|
4824
|
+
function endpointMarkerOwnsStub(marker, stub) {
|
|
4825
|
+
return stub != null && marker?.kind === "endpoint" && marker.owner === stub.owner;
|
|
4826
|
+
}
|
|
4798
4827
|
function formatEndpointMarker(owner, scope) {
|
|
4799
4828
|
return `// ${MODULE_STUB_MARKER} owner=${owner} scope=${scopeLabel(scope)}`;
|
|
4800
4829
|
}
|
|
@@ -5133,6 +5162,7 @@ function buildRouteStubContent(input) {
|
|
|
5133
5162
|
'import { notFound } from "next/navigation";',
|
|
5134
5163
|
// simple-import-sort puts packages and "@/" aliases in separate groups.
|
|
5135
5164
|
"",
|
|
5165
|
+
'import { initializeActiveEnhancements } from "@/module-init.server.gen";',
|
|
5136
5166
|
'import { isModuleEnabled, isMountHeadless } from "@/modules.gen";',
|
|
5137
5167
|
`import Page from ${emitTsString(input.importModule)};`
|
|
5138
5168
|
];
|
|
@@ -5141,6 +5171,7 @@ function buildRouteStubContent(input) {
|
|
|
5141
5171
|
`import { generateMetadata as sourceGenerateMetadata } from ${emitTsString(input.importModule)};`,
|
|
5142
5172
|
"",
|
|
5143
5173
|
"export async function generateMetadata(...args: Parameters<typeof sourceGenerateMetadata>) {",
|
|
5174
|
+
" await initializeActiveEnhancements();",
|
|
5144
5175
|
...emitGuardLines(guards, "return {};"),
|
|
5145
5176
|
" return sourceGenerateMetadata(...args);",
|
|
5146
5177
|
"}"
|
|
@@ -5151,7 +5182,10 @@ function buildRouteStubContent(input) {
|
|
|
5151
5182
|
"type PageProps =",
|
|
5152
5183
|
" Parameters<typeof Page> extends [infer P, ...unknown[]] ? NonNullable<P> : Record<never, never>;",
|
|
5153
5184
|
"",
|
|
5154
|
-
|
|
5185
|
+
// Module and enhancement initializers run in this route's own module graph
|
|
5186
|
+
// (memoized), so a cold server render sees their registrations.
|
|
5187
|
+
"export default async function ModulePage(props: PageProps) {",
|
|
5188
|
+
" await initializeActiveEnhancements();",
|
|
5155
5189
|
...emitGuardLines(guards, "notFound();"),
|
|
5156
5190
|
" return <Page {...props} />;",
|
|
5157
5191
|
"}",
|
|
@@ -5491,7 +5525,7 @@ function planCompositionArtifacts(input) {
|
|
|
5491
5525
|
const legacy = parseLegacyApiAdapter(pagePath, content);
|
|
5492
5526
|
const plannedRoute = plannedRouteByPath.get(pagePath);
|
|
5493
5527
|
const plannedEndpoint = plannedEndpointByPath.get(pagePath);
|
|
5494
|
-
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);
|
|
5495
5529
|
if (isExactPlannedMatch) {
|
|
5496
5530
|
continue;
|
|
5497
5531
|
}
|
|
@@ -5634,9 +5668,7 @@ function planCompositionArtifacts(input) {
|
|
|
5634
5668
|
}
|
|
5635
5669
|
if (marker.kind === "endpoint") {
|
|
5636
5670
|
const stub = endpointStubByPath.get(path4);
|
|
5637
|
-
|
|
5638
|
-
const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
|
|
5639
|
-
if (!ownerMatches || !scopeMatches) {
|
|
5671
|
+
if (!endpointMarkerOwnsStub(marker, stub)) {
|
|
5640
5672
|
return {
|
|
5641
5673
|
ok: false,
|
|
5642
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) : "?"})`,
|
|
@@ -6279,39 +6311,24 @@ async function assertNoRootRoutesWhenMountsEmpty(context, moduleName) {
|
|
|
6279
6311
|
);
|
|
6280
6312
|
}
|
|
6281
6313
|
}
|
|
6282
|
-
async function
|
|
6283
|
-
const
|
|
6284
|
-
const
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
for (const entry of enhancementEntries) {
|
|
6289
|
-
if (entry.kind !== "dir") continue;
|
|
6290
|
-
const sliceRoutes = `${enhancementsDir}/${entry.name}/routes`;
|
|
6291
|
-
const sliceProbe = await context.sandbox.exec(
|
|
6292
|
-
[
|
|
6293
|
-
`p=${shellQuote(sliceRoutes)}`,
|
|
6294
|
-
`if test -L "$p" || test -e "$p"; then exit 2; else exit 0; fi`
|
|
6295
|
-
].join("\n"),
|
|
6296
|
-
{ raiseOnError: false }
|
|
6297
|
-
);
|
|
6298
|
-
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) {
|
|
6299
6320
|
throw new Error(
|
|
6300
6321
|
`[ModuleRail] composition artifacts: enhancement slice routes/ are forbidden at ${sliceRoutes}`
|
|
6301
6322
|
);
|
|
6302
6323
|
}
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: ${redactSecrets(detail)}`
|
|
6307
|
-
);
|
|
6308
|
-
}
|
|
6324
|
+
throw new Error(
|
|
6325
|
+
`[ModuleRail] composition artifacts: cannot probe ${sliceRoutes}: entry-kind:${kind ?? "missing"}`
|
|
6326
|
+
);
|
|
6309
6327
|
}
|
|
6310
6328
|
}
|
|
6311
|
-
async function
|
|
6329
|
+
async function discoverRootRouteEntries(context, moduleName) {
|
|
6312
6330
|
const modulePath = `${MODULES_SANDBOX_DIR}/${moduleName}`;
|
|
6313
6331
|
const routesDir = `${modulePath}/routes`;
|
|
6314
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
6315
6332
|
const quotedRoutes = shellQuote(routesDir);
|
|
6316
6333
|
const listResult = await context.sandbox.exec(
|
|
6317
6334
|
[
|
|
@@ -6765,7 +6782,8 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
|
|
|
6765
6782
|
`# Leaf is missing. A missing intermediate parent means the path is simply`,
|
|
6766
6783
|
`# absent (new nested writes). Only an existing-but-inaccessible parent`,
|
|
6767
6784
|
`# is unreadable \u2014 never conflate ENOENT parents with EACCES.`,
|
|
6768
|
-
|
|
6785
|
+
// Probe paths are nested repo-relative paths; do not spawn dirname per missing file.
|
|
6786
|
+
`parent=\${p%/*}`,
|
|
6769
6787
|
`if test ! -e "$parent" && test ! -L "$parent"; then`,
|
|
6770
6788
|
` printf 'entry-kind:absent\\t%s\\n' "$p"`,
|
|
6771
6789
|
`elif ls -ld -- "$parent" >/dev/null 2>&1; then`,
|
|
@@ -6949,6 +6967,9 @@ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
|
6949
6967
|
case "initializeEnhancement":
|
|
6950
6968
|
assertInitializeEnhancementExport(source, path4);
|
|
6951
6969
|
break;
|
|
6970
|
+
case "initializeModule":
|
|
6971
|
+
assertInitializeModuleExport(source, path4);
|
|
6972
|
+
break;
|
|
6952
6973
|
}
|
|
6953
6974
|
return source;
|
|
6954
6975
|
}
|
|
@@ -6985,7 +7006,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
6985
7006
|
for (const mod of registry.modules) {
|
|
6986
7007
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
6987
7008
|
localeDirs.push(`${modulePath}/i18n`);
|
|
6988
|
-
conventionalPaths.push(
|
|
7009
|
+
conventionalPaths.push(
|
|
7010
|
+
`${modulePath}/contributions.ts`,
|
|
7011
|
+
`${modulePath}/slots.ts`,
|
|
7012
|
+
`${modulePath}/initialize.server.ts`
|
|
7013
|
+
);
|
|
6989
7014
|
for (const enh of mod.enhancements) {
|
|
6990
7015
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
6991
7016
|
localeDirs.push(`${peerDir}/i18n`);
|
|
@@ -7073,6 +7098,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7073
7098
|
}
|
|
7074
7099
|
}
|
|
7075
7100
|
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7101
|
+
const hasRootInitializeServer = await readConventionalEntryIfRealFile(
|
|
7102
|
+
`${modulePath}/initialize.server.ts`,
|
|
7103
|
+
"initializeModule",
|
|
7104
|
+
conventionalPresences,
|
|
7105
|
+
files
|
|
7106
|
+
) != null;
|
|
7107
|
+
if (hasRootInitializeServer) {
|
|
7108
|
+
console.log(
|
|
7109
|
+
`[ModuleRail] runId=${context.runId} module=${mod.name} has root initialize.server.ts (initializeModule)`
|
|
7110
|
+
);
|
|
7111
|
+
}
|
|
7076
7112
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7077
7113
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7078
7114
|
throw new Error(
|
|
@@ -7113,6 +7149,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7113
7149
|
hasRootContributions,
|
|
7114
7150
|
hasRootSlots,
|
|
7115
7151
|
hasServerRegistrations,
|
|
7152
|
+
hasRootInitializeServer,
|
|
7116
7153
|
rootLocales,
|
|
7117
7154
|
enhancements
|
|
7118
7155
|
});
|
|
@@ -7291,14 +7328,21 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
7291
7328
|
manifestsByName
|
|
7292
7329
|
);
|
|
7293
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
|
+
);
|
|
7294
7339
|
for (const moduleName of composedNames) {
|
|
7295
7340
|
const manifest = manifestsByName[moduleName];
|
|
7296
|
-
await assertNoEnhancementRoutes(context, moduleName);
|
|
7297
7341
|
if (manifest.mounts.length === 0) {
|
|
7298
7342
|
await assertNoRootRoutesWhenMountsEmpty(context, moduleName);
|
|
7299
7343
|
routeEntriesByOwner[moduleName] = [];
|
|
7300
7344
|
} else {
|
|
7301
|
-
routeEntriesByOwner[moduleName] = await
|
|
7345
|
+
routeEntriesByOwner[moduleName] = await discoverRootRouteEntries(context, moduleName);
|
|
7302
7346
|
}
|
|
7303
7347
|
}
|
|
7304
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.
|
|
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"
|