@stardeck-customer-apps/compose 0.13.0 → 0.14.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 +35 -1
- package/dist/cli.js +69 -22
- package/dist/index.js +69 -22
- package/dist/index.mjs +69 -22
- package/package.json +1 -1
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
|
@@ -3560,17 +3560,20 @@ var import_typescript3 = __toESM(require("typescript"), 1);
|
|
|
3560
3560
|
var CONVENTIONAL_ENTRY_EXPORT_NAME = {
|
|
3561
3561
|
contributions: "contributions",
|
|
3562
3562
|
slotCatalogs: "slotCatalogs",
|
|
3563
|
-
initializeEnhancement: "initializeEnhancement"
|
|
3563
|
+
initializeEnhancement: "initializeEnhancement",
|
|
3564
|
+
initializeModule: "initializeModule"
|
|
3564
3565
|
};
|
|
3565
3566
|
var CONVENTIONAL_ENTRY_DECLARATION_KIND = {
|
|
3566
3567
|
contributions: "variable",
|
|
3567
3568
|
slotCatalogs: "variable",
|
|
3568
|
-
initializeEnhancement: "function"
|
|
3569
|
+
initializeEnhancement: "function",
|
|
3570
|
+
initializeModule: "function"
|
|
3569
3571
|
};
|
|
3570
3572
|
var CONVENTIONAL_ENTRY_FILENAME = {
|
|
3571
3573
|
contributions: "contributions.ts",
|
|
3572
3574
|
slotCatalogs: "slots.ts",
|
|
3573
|
-
initializeEnhancement: "initialize.server.ts"
|
|
3575
|
+
initializeEnhancement: "initialize.server.ts",
|
|
3576
|
+
initializeModule: "initialize.server.ts"
|
|
3574
3577
|
};
|
|
3575
3578
|
function hasModifierKind(modifiers, kind) {
|
|
3576
3579
|
return Boolean(modifiers?.some((m) => m.kind === kind));
|
|
@@ -3669,7 +3672,7 @@ function analyzeConventionalEntryExportWithTs(typescript, sourceText, kind, file
|
|
|
3669
3672
|
}
|
|
3670
3673
|
if (!isZeroArgumentInvocable(typescript, stmt.parameters)) {
|
|
3671
3674
|
errors.push(
|
|
3672
|
-
`"${entryName}" must be invocable with zero arguments (generated caller uses
|
|
3675
|
+
`"${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
3676
|
);
|
|
3674
3677
|
continue;
|
|
3675
3678
|
}
|
|
@@ -4150,6 +4153,9 @@ function assertSlotCatalogsExport(source, pathLabel) {
|
|
|
4150
4153
|
function assertInitializeEnhancementExport(source, pathLabel) {
|
|
4151
4154
|
assertConventionalEntryExport(source, pathLabel, "initializeEnhancement");
|
|
4152
4155
|
}
|
|
4156
|
+
function assertInitializeModuleExport(source, pathLabel) {
|
|
4157
|
+
assertConventionalEntryExport(source, pathLabel, "initializeModule");
|
|
4158
|
+
}
|
|
4153
4159
|
function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
4154
4160
|
const { analysisErrors } = analyzeClientSafeEntryImports(
|
|
4155
4161
|
source,
|
|
@@ -4669,30 +4675,47 @@ function deriveModuleLocaleLayers(registry, facts) {
|
|
|
4669
4675
|
}
|
|
4670
4676
|
return layers;
|
|
4671
4677
|
}
|
|
4672
|
-
function
|
|
4678
|
+
function moduleInitializerCalls(registry, facts) {
|
|
4679
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4680
|
+
return facts.filter((f) => f.hasRootInitializeServer && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames).map((name, index) => {
|
|
4681
|
+
if (!isValidModuleName(name)) {
|
|
4682
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4683
|
+
}
|
|
4684
|
+
return {
|
|
4685
|
+
exportName: "initializeModule",
|
|
4686
|
+
guard: [name],
|
|
4687
|
+
ident: `initializeModule${index}`,
|
|
4688
|
+
importPath: `@/modules/${name}/initialize.server`
|
|
4689
|
+
};
|
|
4690
|
+
});
|
|
4691
|
+
}
|
|
4692
|
+
function enhancementInitializerCalls(registry, facts) {
|
|
4673
4693
|
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))) {
|
|
4694
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4695
|
+
for (const f of [...facts].sort((a, b) => compareNames(a.moduleName, b.moduleName))) {
|
|
4696
|
+
if (!installed.has(f.moduleName)) continue;
|
|
4697
|
+
const peers = activePeers(registry, f.moduleName);
|
|
4698
|
+
for (const enh of [...f.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4680
4699
|
if (!enh.active || !peers.has(enh.peer)) continue;
|
|
4681
4700
|
if (!enh.hasInitializeServer) continue;
|
|
4682
|
-
const ident = `initializeEnhancement${initializerIndex++}`;
|
|
4683
4701
|
calls.push({
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
ident
|
|
4687
|
-
importPath: `@/modules/${
|
|
4702
|
+
exportName: "initializeEnhancement",
|
|
4703
|
+
guard: [f.moduleName, enh.peer],
|
|
4704
|
+
ident: `initializeEnhancement${calls.length}`,
|
|
4705
|
+
importPath: `@/modules/${f.moduleName}/enhancements/${enh.peer}/initialize.server`
|
|
4688
4706
|
});
|
|
4689
4707
|
}
|
|
4690
4708
|
}
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4709
|
+
return calls;
|
|
4710
|
+
}
|
|
4711
|
+
function emitModuleInitServerGenTs(input) {
|
|
4712
|
+
const moduleCalls = moduleInitializerCalls(input.registry, input.facts);
|
|
4713
|
+
const enhancementCalls = enhancementInitializerCalls(input.registry, input.facts);
|
|
4714
|
+
const calls = [...moduleCalls, ...enhancementCalls];
|
|
4715
|
+
const importFor = (c) => `import { ${c.exportName} as ${c.ident} } from ${JSON.stringify(c.importPath)};`;
|
|
4716
|
+
const importLines = moduleCalls.length === 0 ? calls.map(importFor) : [...calls].sort((a, b) => compareImportSpecifiers(a.importPath, b.importPath)).map(importFor);
|
|
4694
4717
|
const callLines = calls.length === 0 ? [" // Empty composition \u2014 no active enhancement initializers."] : calls.map(
|
|
4695
|
-
(c) => ` if (
|
|
4718
|
+
(c) => ` if (${c.guard.map((name) => `enabled(${JSON.stringify(name)})`).join(" && ")}) await ${c.ident}();`
|
|
4696
4719
|
);
|
|
4697
4720
|
return [
|
|
4698
4721
|
formatInitMarker(),
|
|
@@ -5142,6 +5165,7 @@ function buildRouteStubContent(input) {
|
|
|
5142
5165
|
'import { notFound } from "next/navigation";',
|
|
5143
5166
|
// simple-import-sort puts packages and "@/" aliases in separate groups.
|
|
5144
5167
|
"",
|
|
5168
|
+
'import { initializeActiveEnhancements } from "@/module-init.server.gen";',
|
|
5145
5169
|
'import { isModuleEnabled, isMountHeadless } from "@/modules.gen";',
|
|
5146
5170
|
`import Page from ${emitTsString(input.importModule)};`
|
|
5147
5171
|
];
|
|
@@ -5150,6 +5174,7 @@ function buildRouteStubContent(input) {
|
|
|
5150
5174
|
`import { generateMetadata as sourceGenerateMetadata } from ${emitTsString(input.importModule)};`,
|
|
5151
5175
|
"",
|
|
5152
5176
|
"export async function generateMetadata(...args: Parameters<typeof sourceGenerateMetadata>) {",
|
|
5177
|
+
" await initializeActiveEnhancements();",
|
|
5153
5178
|
...emitGuardLines(guards, "return {};"),
|
|
5154
5179
|
" return sourceGenerateMetadata(...args);",
|
|
5155
5180
|
"}"
|
|
@@ -5160,7 +5185,10 @@ function buildRouteStubContent(input) {
|
|
|
5160
5185
|
"type PageProps =",
|
|
5161
5186
|
" Parameters<typeof Page> extends [infer P, ...unknown[]] ? NonNullable<P> : Record<never, never>;",
|
|
5162
5187
|
"",
|
|
5163
|
-
|
|
5188
|
+
// Module and enhancement initializers run in this route's own module graph
|
|
5189
|
+
// (memoized), so a cold server render sees their registrations.
|
|
5190
|
+
"export default async function ModulePage(props: PageProps) {",
|
|
5191
|
+
" await initializeActiveEnhancements();",
|
|
5164
5192
|
...emitGuardLines(guards, "notFound();"),
|
|
5165
5193
|
" return <Page {...props} />;",
|
|
5166
5194
|
"}",
|
|
@@ -6958,6 +6986,9 @@ async function readConventionalEntryIfRealFile(path5, kind, presences, files) {
|
|
|
6958
6986
|
case "initializeEnhancement":
|
|
6959
6987
|
assertInitializeEnhancementExport(source, path5);
|
|
6960
6988
|
break;
|
|
6989
|
+
case "initializeModule":
|
|
6990
|
+
assertInitializeModuleExport(source, path5);
|
|
6991
|
+
break;
|
|
6961
6992
|
}
|
|
6962
6993
|
return source;
|
|
6963
6994
|
}
|
|
@@ -6994,7 +7025,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
6994
7025
|
for (const mod of registry.modules) {
|
|
6995
7026
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
6996
7027
|
localeDirs.push(`${modulePath}/i18n`);
|
|
6997
|
-
conventionalPaths.push(
|
|
7028
|
+
conventionalPaths.push(
|
|
7029
|
+
`${modulePath}/contributions.ts`,
|
|
7030
|
+
`${modulePath}/slots.ts`,
|
|
7031
|
+
`${modulePath}/initialize.server.ts`
|
|
7032
|
+
);
|
|
6998
7033
|
for (const enh of mod.enhancements) {
|
|
6999
7034
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7000
7035
|
localeDirs.push(`${peerDir}/i18n`);
|
|
@@ -7082,6 +7117,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7082
7117
|
}
|
|
7083
7118
|
}
|
|
7084
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
|
+
}
|
|
7085
7131
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7086
7132
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7087
7133
|
throw new Error(
|
|
@@ -7122,6 +7168,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7122
7168
|
hasRootContributions,
|
|
7123
7169
|
hasRootSlots,
|
|
7124
7170
|
hasServerRegistrations,
|
|
7171
|
+
hasRootInitializeServer,
|
|
7125
7172
|
rootLocales,
|
|
7126
7173
|
enhancements
|
|
7127
7174
|
});
|
package/dist/index.js
CHANGED
|
@@ -3689,17 +3689,20 @@ var import_typescript3 = __toESM(require("typescript"), 1);
|
|
|
3689
3689
|
var CONVENTIONAL_ENTRY_EXPORT_NAME = {
|
|
3690
3690
|
contributions: "contributions",
|
|
3691
3691
|
slotCatalogs: "slotCatalogs",
|
|
3692
|
-
initializeEnhancement: "initializeEnhancement"
|
|
3692
|
+
initializeEnhancement: "initializeEnhancement",
|
|
3693
|
+
initializeModule: "initializeModule"
|
|
3693
3694
|
};
|
|
3694
3695
|
var CONVENTIONAL_ENTRY_DECLARATION_KIND = {
|
|
3695
3696
|
contributions: "variable",
|
|
3696
3697
|
slotCatalogs: "variable",
|
|
3697
|
-
initializeEnhancement: "function"
|
|
3698
|
+
initializeEnhancement: "function",
|
|
3699
|
+
initializeModule: "function"
|
|
3698
3700
|
};
|
|
3699
3701
|
var CONVENTIONAL_ENTRY_FILENAME = {
|
|
3700
3702
|
contributions: "contributions.ts",
|
|
3701
3703
|
slotCatalogs: "slots.ts",
|
|
3702
|
-
initializeEnhancement: "initialize.server.ts"
|
|
3704
|
+
initializeEnhancement: "initialize.server.ts",
|
|
3705
|
+
initializeModule: "initialize.server.ts"
|
|
3703
3706
|
};
|
|
3704
3707
|
function hasModifierKind(modifiers, kind) {
|
|
3705
3708
|
return Boolean(modifiers?.some((m) => m.kind === kind));
|
|
@@ -3798,7 +3801,7 @@ function analyzeConventionalEntryExportWithTs(typescript, sourceText, kind, file
|
|
|
3798
3801
|
}
|
|
3799
3802
|
if (!isZeroArgumentInvocable(typescript, stmt.parameters)) {
|
|
3800
3803
|
errors.push(
|
|
3801
|
-
`"${entryName}" must be invocable with zero arguments (generated caller uses
|
|
3804
|
+
`"${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
3805
|
);
|
|
3803
3806
|
continue;
|
|
3804
3807
|
}
|
|
@@ -4160,6 +4163,9 @@ function assertSlotCatalogsExport(source, pathLabel) {
|
|
|
4160
4163
|
function assertInitializeEnhancementExport(source, pathLabel) {
|
|
4161
4164
|
assertConventionalEntryExport(source, pathLabel, "initializeEnhancement");
|
|
4162
4165
|
}
|
|
4166
|
+
function assertInitializeModuleExport(source, pathLabel) {
|
|
4167
|
+
assertConventionalEntryExport(source, pathLabel, "initializeModule");
|
|
4168
|
+
}
|
|
4163
4169
|
function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
4164
4170
|
const { analysisErrors } = analyzeClientSafeEntryImports(
|
|
4165
4171
|
source,
|
|
@@ -4679,30 +4685,47 @@ function deriveModuleLocaleLayers(registry, facts) {
|
|
|
4679
4685
|
}
|
|
4680
4686
|
return layers;
|
|
4681
4687
|
}
|
|
4682
|
-
function
|
|
4688
|
+
function moduleInitializerCalls(registry, facts) {
|
|
4689
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4690
|
+
return facts.filter((f) => f.hasRootInitializeServer && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames).map((name, index) => {
|
|
4691
|
+
if (!isValidModuleName(name)) {
|
|
4692
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4693
|
+
}
|
|
4694
|
+
return {
|
|
4695
|
+
exportName: "initializeModule",
|
|
4696
|
+
guard: [name],
|
|
4697
|
+
ident: `initializeModule${index}`,
|
|
4698
|
+
importPath: `@/modules/${name}/initialize.server`
|
|
4699
|
+
};
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4702
|
+
function enhancementInitializerCalls(registry, facts) {
|
|
4683
4703
|
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))) {
|
|
4704
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4705
|
+
for (const f of [...facts].sort((a, b) => compareNames(a.moduleName, b.moduleName))) {
|
|
4706
|
+
if (!installed.has(f.moduleName)) continue;
|
|
4707
|
+
const peers = activePeers(registry, f.moduleName);
|
|
4708
|
+
for (const enh of [...f.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4690
4709
|
if (!enh.active || !peers.has(enh.peer)) continue;
|
|
4691
4710
|
if (!enh.hasInitializeServer) continue;
|
|
4692
|
-
const ident = `initializeEnhancement${initializerIndex++}`;
|
|
4693
4711
|
calls.push({
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
ident
|
|
4697
|
-
importPath: `@/modules/${
|
|
4712
|
+
exportName: "initializeEnhancement",
|
|
4713
|
+
guard: [f.moduleName, enh.peer],
|
|
4714
|
+
ident: `initializeEnhancement${calls.length}`,
|
|
4715
|
+
importPath: `@/modules/${f.moduleName}/enhancements/${enh.peer}/initialize.server`
|
|
4698
4716
|
});
|
|
4699
4717
|
}
|
|
4700
4718
|
}
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4719
|
+
return calls;
|
|
4720
|
+
}
|
|
4721
|
+
function emitModuleInitServerGenTs(input) {
|
|
4722
|
+
const moduleCalls = moduleInitializerCalls(input.registry, input.facts);
|
|
4723
|
+
const enhancementCalls = enhancementInitializerCalls(input.registry, input.facts);
|
|
4724
|
+
const calls = [...moduleCalls, ...enhancementCalls];
|
|
4725
|
+
const importFor = (c) => `import { ${c.exportName} as ${c.ident} } from ${JSON.stringify(c.importPath)};`;
|
|
4726
|
+
const importLines = moduleCalls.length === 0 ? calls.map(importFor) : [...calls].sort((a, b) => compareImportSpecifiers(a.importPath, b.importPath)).map(importFor);
|
|
4704
4727
|
const callLines = calls.length === 0 ? [" // Empty composition \u2014 no active enhancement initializers."] : calls.map(
|
|
4705
|
-
(c) => ` if (
|
|
4728
|
+
(c) => ` if (${c.guard.map((name) => `enabled(${JSON.stringify(name)})`).join(" && ")}) await ${c.ident}();`
|
|
4706
4729
|
);
|
|
4707
4730
|
return [
|
|
4708
4731
|
formatInitMarker(),
|
|
@@ -5152,6 +5175,7 @@ function buildRouteStubContent(input) {
|
|
|
5152
5175
|
'import { notFound } from "next/navigation";',
|
|
5153
5176
|
// simple-import-sort puts packages and "@/" aliases in separate groups.
|
|
5154
5177
|
"",
|
|
5178
|
+
'import { initializeActiveEnhancements } from "@/module-init.server.gen";',
|
|
5155
5179
|
'import { isModuleEnabled, isMountHeadless } from "@/modules.gen";',
|
|
5156
5180
|
`import Page from ${emitTsString(input.importModule)};`
|
|
5157
5181
|
];
|
|
@@ -5160,6 +5184,7 @@ function buildRouteStubContent(input) {
|
|
|
5160
5184
|
`import { generateMetadata as sourceGenerateMetadata } from ${emitTsString(input.importModule)};`,
|
|
5161
5185
|
"",
|
|
5162
5186
|
"export async function generateMetadata(...args: Parameters<typeof sourceGenerateMetadata>) {",
|
|
5187
|
+
" await initializeActiveEnhancements();",
|
|
5163
5188
|
...emitGuardLines(guards, "return {};"),
|
|
5164
5189
|
" return sourceGenerateMetadata(...args);",
|
|
5165
5190
|
"}"
|
|
@@ -5170,7 +5195,10 @@ function buildRouteStubContent(input) {
|
|
|
5170
5195
|
"type PageProps =",
|
|
5171
5196
|
" Parameters<typeof Page> extends [infer P, ...unknown[]] ? NonNullable<P> : Record<never, never>;",
|
|
5172
5197
|
"",
|
|
5173
|
-
|
|
5198
|
+
// Module and enhancement initializers run in this route's own module graph
|
|
5199
|
+
// (memoized), so a cold server render sees their registrations.
|
|
5200
|
+
"export default async function ModulePage(props: PageProps) {",
|
|
5201
|
+
" await initializeActiveEnhancements();",
|
|
5174
5202
|
...emitGuardLines(guards, "notFound();"),
|
|
5175
5203
|
" return <Page {...props} />;",
|
|
5176
5204
|
"}",
|
|
@@ -6968,6 +6996,9 @@ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
|
6968
6996
|
case "initializeEnhancement":
|
|
6969
6997
|
assertInitializeEnhancementExport(source, path4);
|
|
6970
6998
|
break;
|
|
6999
|
+
case "initializeModule":
|
|
7000
|
+
assertInitializeModuleExport(source, path4);
|
|
7001
|
+
break;
|
|
6971
7002
|
}
|
|
6972
7003
|
return source;
|
|
6973
7004
|
}
|
|
@@ -7004,7 +7035,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7004
7035
|
for (const mod of registry.modules) {
|
|
7005
7036
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
7006
7037
|
localeDirs.push(`${modulePath}/i18n`);
|
|
7007
|
-
conventionalPaths.push(
|
|
7038
|
+
conventionalPaths.push(
|
|
7039
|
+
`${modulePath}/contributions.ts`,
|
|
7040
|
+
`${modulePath}/slots.ts`,
|
|
7041
|
+
`${modulePath}/initialize.server.ts`
|
|
7042
|
+
);
|
|
7008
7043
|
for (const enh of mod.enhancements) {
|
|
7009
7044
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
7010
7045
|
localeDirs.push(`${peerDir}/i18n`);
|
|
@@ -7092,6 +7127,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7092
7127
|
}
|
|
7093
7128
|
}
|
|
7094
7129
|
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7130
|
+
const hasRootInitializeServer = await readConventionalEntryIfRealFile(
|
|
7131
|
+
`${modulePath}/initialize.server.ts`,
|
|
7132
|
+
"initializeModule",
|
|
7133
|
+
conventionalPresences,
|
|
7134
|
+
files
|
|
7135
|
+
) != null;
|
|
7136
|
+
if (hasRootInitializeServer) {
|
|
7137
|
+
console.log(
|
|
7138
|
+
`[ModuleRail] runId=${context.runId} module=${mod.name} has root initialize.server.ts (initializeModule)`
|
|
7139
|
+
);
|
|
7140
|
+
}
|
|
7095
7141
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7096
7142
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7097
7143
|
throw new Error(
|
|
@@ -7132,6 +7178,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7132
7178
|
hasRootContributions,
|
|
7133
7179
|
hasRootSlots,
|
|
7134
7180
|
hasServerRegistrations,
|
|
7181
|
+
hasRootInitializeServer,
|
|
7135
7182
|
rootLocales,
|
|
7136
7183
|
enhancements
|
|
7137
7184
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -3670,17 +3670,20 @@ import ts3 from "typescript";
|
|
|
3670
3670
|
var CONVENTIONAL_ENTRY_EXPORT_NAME = {
|
|
3671
3671
|
contributions: "contributions",
|
|
3672
3672
|
slotCatalogs: "slotCatalogs",
|
|
3673
|
-
initializeEnhancement: "initializeEnhancement"
|
|
3673
|
+
initializeEnhancement: "initializeEnhancement",
|
|
3674
|
+
initializeModule: "initializeModule"
|
|
3674
3675
|
};
|
|
3675
3676
|
var CONVENTIONAL_ENTRY_DECLARATION_KIND = {
|
|
3676
3677
|
contributions: "variable",
|
|
3677
3678
|
slotCatalogs: "variable",
|
|
3678
|
-
initializeEnhancement: "function"
|
|
3679
|
+
initializeEnhancement: "function",
|
|
3680
|
+
initializeModule: "function"
|
|
3679
3681
|
};
|
|
3680
3682
|
var CONVENTIONAL_ENTRY_FILENAME = {
|
|
3681
3683
|
contributions: "contributions.ts",
|
|
3682
3684
|
slotCatalogs: "slots.ts",
|
|
3683
|
-
initializeEnhancement: "initialize.server.ts"
|
|
3685
|
+
initializeEnhancement: "initialize.server.ts",
|
|
3686
|
+
initializeModule: "initialize.server.ts"
|
|
3684
3687
|
};
|
|
3685
3688
|
function hasModifierKind(modifiers, kind) {
|
|
3686
3689
|
return Boolean(modifiers?.some((m) => m.kind === kind));
|
|
@@ -3779,7 +3782,7 @@ function analyzeConventionalEntryExportWithTs(typescript, sourceText, kind, file
|
|
|
3779
3782
|
}
|
|
3780
3783
|
if (!isZeroArgumentInvocable(typescript, stmt.parameters)) {
|
|
3781
3784
|
errors.push(
|
|
3782
|
-
`"${entryName}" must be invocable with zero arguments (generated caller uses
|
|
3785
|
+
`"${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
3786
|
);
|
|
3784
3787
|
continue;
|
|
3785
3788
|
}
|
|
@@ -4141,6 +4144,9 @@ function assertSlotCatalogsExport(source, pathLabel) {
|
|
|
4141
4144
|
function assertInitializeEnhancementExport(source, pathLabel) {
|
|
4142
4145
|
assertConventionalEntryExport(source, pathLabel, "initializeEnhancement");
|
|
4143
4146
|
}
|
|
4147
|
+
function assertInitializeModuleExport(source, pathLabel) {
|
|
4148
|
+
assertConventionalEntryExport(source, pathLabel, "initializeModule");
|
|
4149
|
+
}
|
|
4144
4150
|
function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
|
|
4145
4151
|
const { analysisErrors } = analyzeClientSafeEntryImports(
|
|
4146
4152
|
source,
|
|
@@ -4660,30 +4666,47 @@ function deriveModuleLocaleLayers(registry, facts) {
|
|
|
4660
4666
|
}
|
|
4661
4667
|
return layers;
|
|
4662
4668
|
}
|
|
4663
|
-
function
|
|
4669
|
+
function moduleInitializerCalls(registry, facts) {
|
|
4670
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4671
|
+
return facts.filter((f) => f.hasRootInitializeServer && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames).map((name, index) => {
|
|
4672
|
+
if (!isValidModuleName(name)) {
|
|
4673
|
+
throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
|
|
4674
|
+
}
|
|
4675
|
+
return {
|
|
4676
|
+
exportName: "initializeModule",
|
|
4677
|
+
guard: [name],
|
|
4678
|
+
ident: `initializeModule${index}`,
|
|
4679
|
+
importPath: `@/modules/${name}/initialize.server`
|
|
4680
|
+
};
|
|
4681
|
+
});
|
|
4682
|
+
}
|
|
4683
|
+
function enhancementInitializerCalls(registry, facts) {
|
|
4664
4684
|
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))) {
|
|
4685
|
+
const installed = new Set(registry.modules.map((m) => m.name));
|
|
4686
|
+
for (const f of [...facts].sort((a, b) => compareNames(a.moduleName, b.moduleName))) {
|
|
4687
|
+
if (!installed.has(f.moduleName)) continue;
|
|
4688
|
+
const peers = activePeers(registry, f.moduleName);
|
|
4689
|
+
for (const enh of [...f.enhancements].sort((a, b) => compareNames(a.peer, b.peer))) {
|
|
4671
4690
|
if (!enh.active || !peers.has(enh.peer)) continue;
|
|
4672
4691
|
if (!enh.hasInitializeServer) continue;
|
|
4673
|
-
const ident = `initializeEnhancement${initializerIndex++}`;
|
|
4674
4692
|
calls.push({
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
ident
|
|
4678
|
-
importPath: `@/modules/${
|
|
4693
|
+
exportName: "initializeEnhancement",
|
|
4694
|
+
guard: [f.moduleName, enh.peer],
|
|
4695
|
+
ident: `initializeEnhancement${calls.length}`,
|
|
4696
|
+
importPath: `@/modules/${f.moduleName}/enhancements/${enh.peer}/initialize.server`
|
|
4679
4697
|
});
|
|
4680
4698
|
}
|
|
4681
4699
|
}
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4700
|
+
return calls;
|
|
4701
|
+
}
|
|
4702
|
+
function emitModuleInitServerGenTs(input) {
|
|
4703
|
+
const moduleCalls = moduleInitializerCalls(input.registry, input.facts);
|
|
4704
|
+
const enhancementCalls = enhancementInitializerCalls(input.registry, input.facts);
|
|
4705
|
+
const calls = [...moduleCalls, ...enhancementCalls];
|
|
4706
|
+
const importFor = (c) => `import { ${c.exportName} as ${c.ident} } from ${JSON.stringify(c.importPath)};`;
|
|
4707
|
+
const importLines = moduleCalls.length === 0 ? calls.map(importFor) : [...calls].sort((a, b) => compareImportSpecifiers(a.importPath, b.importPath)).map(importFor);
|
|
4685
4708
|
const callLines = calls.length === 0 ? [" // Empty composition \u2014 no active enhancement initializers."] : calls.map(
|
|
4686
|
-
(c) => ` if (
|
|
4709
|
+
(c) => ` if (${c.guard.map((name) => `enabled(${JSON.stringify(name)})`).join(" && ")}) await ${c.ident}();`
|
|
4687
4710
|
);
|
|
4688
4711
|
return [
|
|
4689
4712
|
formatInitMarker(),
|
|
@@ -5133,6 +5156,7 @@ function buildRouteStubContent(input) {
|
|
|
5133
5156
|
'import { notFound } from "next/navigation";',
|
|
5134
5157
|
// simple-import-sort puts packages and "@/" aliases in separate groups.
|
|
5135
5158
|
"",
|
|
5159
|
+
'import { initializeActiveEnhancements } from "@/module-init.server.gen";',
|
|
5136
5160
|
'import { isModuleEnabled, isMountHeadless } from "@/modules.gen";',
|
|
5137
5161
|
`import Page from ${emitTsString(input.importModule)};`
|
|
5138
5162
|
];
|
|
@@ -5141,6 +5165,7 @@ function buildRouteStubContent(input) {
|
|
|
5141
5165
|
`import { generateMetadata as sourceGenerateMetadata } from ${emitTsString(input.importModule)};`,
|
|
5142
5166
|
"",
|
|
5143
5167
|
"export async function generateMetadata(...args: Parameters<typeof sourceGenerateMetadata>) {",
|
|
5168
|
+
" await initializeActiveEnhancements();",
|
|
5144
5169
|
...emitGuardLines(guards, "return {};"),
|
|
5145
5170
|
" return sourceGenerateMetadata(...args);",
|
|
5146
5171
|
"}"
|
|
@@ -5151,7 +5176,10 @@ function buildRouteStubContent(input) {
|
|
|
5151
5176
|
"type PageProps =",
|
|
5152
5177
|
" Parameters<typeof Page> extends [infer P, ...unknown[]] ? NonNullable<P> : Record<never, never>;",
|
|
5153
5178
|
"",
|
|
5154
|
-
|
|
5179
|
+
// Module and enhancement initializers run in this route's own module graph
|
|
5180
|
+
// (memoized), so a cold server render sees their registrations.
|
|
5181
|
+
"export default async function ModulePage(props: PageProps) {",
|
|
5182
|
+
" await initializeActiveEnhancements();",
|
|
5155
5183
|
...emitGuardLines(guards, "notFound();"),
|
|
5156
5184
|
" return <Page {...props} />;",
|
|
5157
5185
|
"}",
|
|
@@ -6949,6 +6977,9 @@ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
|
|
|
6949
6977
|
case "initializeEnhancement":
|
|
6950
6978
|
assertInitializeEnhancementExport(source, path4);
|
|
6951
6979
|
break;
|
|
6980
|
+
case "initializeModule":
|
|
6981
|
+
assertInitializeModuleExport(source, path4);
|
|
6982
|
+
break;
|
|
6952
6983
|
}
|
|
6953
6984
|
return source;
|
|
6954
6985
|
}
|
|
@@ -6985,7 +7016,11 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
6985
7016
|
for (const mod of registry.modules) {
|
|
6986
7017
|
const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
|
|
6987
7018
|
localeDirs.push(`${modulePath}/i18n`);
|
|
6988
|
-
conventionalPaths.push(
|
|
7019
|
+
conventionalPaths.push(
|
|
7020
|
+
`${modulePath}/contributions.ts`,
|
|
7021
|
+
`${modulePath}/slots.ts`,
|
|
7022
|
+
`${modulePath}/initialize.server.ts`
|
|
7023
|
+
);
|
|
6989
7024
|
for (const enh of mod.enhancements) {
|
|
6990
7025
|
const peerDir = `${modulePath}/enhancements/${enh.peer}`;
|
|
6991
7026
|
localeDirs.push(`${peerDir}/i18n`);
|
|
@@ -7073,6 +7108,17 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7073
7108
|
}
|
|
7074
7109
|
}
|
|
7075
7110
|
const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
|
|
7111
|
+
const hasRootInitializeServer = await readConventionalEntryIfRealFile(
|
|
7112
|
+
`${modulePath}/initialize.server.ts`,
|
|
7113
|
+
"initializeModule",
|
|
7114
|
+
conventionalPresences,
|
|
7115
|
+
files
|
|
7116
|
+
) != null;
|
|
7117
|
+
if (hasRootInitializeServer) {
|
|
7118
|
+
console.log(
|
|
7119
|
+
`[ModuleRail] runId=${context.runId} module=${mod.name} has root initialize.server.ts (initializeModule)`
|
|
7120
|
+
);
|
|
7121
|
+
}
|
|
7076
7122
|
const rootLocales = localesByDir.get(`${modulePath}/i18n`);
|
|
7077
7123
|
if (rootLocales.length > 0 && !rootLocales.includes("en")) {
|
|
7078
7124
|
throw new Error(
|
|
@@ -7113,6 +7159,7 @@ async function collectCompositionEntryFacts(context, registry) {
|
|
|
7113
7159
|
hasRootContributions,
|
|
7114
7160
|
hasRootSlots,
|
|
7115
7161
|
hasServerRegistrations,
|
|
7162
|
+
hasRootInitializeServer,
|
|
7116
7163
|
rootLocales,
|
|
7117
7164
|
enhancements
|
|
7118
7165
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/compose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
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",
|