@open-mercato/cli 0.6.6-develop.6094.1.28b081ea16 → 0.6.6-develop.6126.1.0b9392fdae
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/lib/generators/extensions/injection-widgets.js +144 -5
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/generators/__tests__/structural-contracts.test.ts +4 -0
- package/src/lib/generators/extensions/injection-widgets.ts +173 -4
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { VariableDeclarationKind } from "ts-morph";
|
|
2
|
-
import
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { resolveStandaloneSourceMirrorBase, scanModuleDir, SCAN_CONFIGS } from "../scanner.js";
|
|
3
6
|
import {
|
|
4
7
|
arrayLiteral,
|
|
5
8
|
arrowFunction,
|
|
@@ -11,6 +14,132 @@ import {
|
|
|
11
14
|
writeValue
|
|
12
15
|
} from "../ast/index.js";
|
|
13
16
|
import { dynamicImportExpression, emptyObject, namespaceFallback, namespaceImportSpec, renderGeneratedTsSource } from "./shared.js";
|
|
17
|
+
function unwrapExpression(expression) {
|
|
18
|
+
let current = expression;
|
|
19
|
+
while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isTypeAssertionExpression(current) || ts.isSatisfiesExpression(current)) {
|
|
20
|
+
current = current.expression;
|
|
21
|
+
}
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
24
|
+
function getPropertyNameText(name) {
|
|
25
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)) {
|
|
26
|
+
return name.text;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function collectObjectDeclarations(sourceFile) {
|
|
31
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const statement of sourceFile.statements) {
|
|
33
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
34
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
35
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
36
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
37
|
+
if (ts.isObjectLiteralExpression(initializer)) {
|
|
38
|
+
declarations.set(declaration.name.text, initializer);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return declarations;
|
|
43
|
+
}
|
|
44
|
+
function resolveObjectExpression(expression, declarations) {
|
|
45
|
+
const unwrapped = unwrapExpression(expression);
|
|
46
|
+
if (ts.isObjectLiteralExpression(unwrapped)) return unwrapped;
|
|
47
|
+
if (ts.isIdentifier(unwrapped)) return declarations.get(unwrapped.text) ?? null;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
function getObjectPropertyExpression(object, propertyName) {
|
|
51
|
+
for (const property of object.properties) {
|
|
52
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
53
|
+
if (getPropertyNameText(property.name) === propertyName) return property.initializer;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function getStringPropertyValue(object, propertyName) {
|
|
58
|
+
const expression = getObjectPropertyExpression(object, propertyName);
|
|
59
|
+
if (!expression) return null;
|
|
60
|
+
const unwrapped = unwrapExpression(expression);
|
|
61
|
+
if (ts.isStringLiteral(unwrapped) || ts.isNoSubstitutionTemplateLiteral(unwrapped)) return unwrapped.text;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function extractMetadataId(object) {
|
|
65
|
+
const metadata = getObjectPropertyExpression(object, "metadata");
|
|
66
|
+
if (!metadata) return null;
|
|
67
|
+
const unwrapped = unwrapExpression(metadata);
|
|
68
|
+
if (!ts.isObjectLiteralExpression(unwrapped)) return null;
|
|
69
|
+
return getStringPropertyValue(unwrapped, "id");
|
|
70
|
+
}
|
|
71
|
+
function extractWidgetMetadataId(absolutePath) {
|
|
72
|
+
try {
|
|
73
|
+
const source = fs.readFileSync(absolutePath, "utf8");
|
|
74
|
+
const sourceFile = ts.createSourceFile(absolutePath, source, ts.ScriptTarget.Latest, true);
|
|
75
|
+
const declarations = collectObjectDeclarations(sourceFile);
|
|
76
|
+
for (const statement of sourceFile.statements) {
|
|
77
|
+
if (!ts.isExportAssignment(statement) || statement.isExportEquals) continue;
|
|
78
|
+
const object = resolveObjectExpression(statement.expression, declarations);
|
|
79
|
+
if (!object) continue;
|
|
80
|
+
const id = extractMetadataId(object);
|
|
81
|
+
if (id) return id;
|
|
82
|
+
}
|
|
83
|
+
for (const object of declarations.values()) {
|
|
84
|
+
const id = extractMetadataId(object);
|
|
85
|
+
if (id) return id;
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function collectWidgetIdsFromTableValue(value, declarations, ids) {
|
|
93
|
+
const expression = unwrapExpression(value);
|
|
94
|
+
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
|
|
95
|
+
ids.add(expression.text);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (ts.isArrayLiteralExpression(expression)) {
|
|
99
|
+
for (const element of expression.elements) {
|
|
100
|
+
if (ts.isExpression(element)) collectWidgetIdsFromTableValue(element, declarations, ids);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const object = resolveObjectExpression(expression, declarations);
|
|
105
|
+
if (!object) return;
|
|
106
|
+
const widgetId = getStringPropertyValue(object, "widgetId");
|
|
107
|
+
if (widgetId) ids.add(widgetId);
|
|
108
|
+
}
|
|
109
|
+
function extractInjectionTableWidgetIds(absolutePath) {
|
|
110
|
+
try {
|
|
111
|
+
const source = fs.readFileSync(absolutePath, "utf8");
|
|
112
|
+
const sourceFile = ts.createSourceFile(absolutePath, source, ts.ScriptTarget.Latest, true);
|
|
113
|
+
const declarations = collectObjectDeclarations(sourceFile);
|
|
114
|
+
let tableObject = null;
|
|
115
|
+
for (const statement of sourceFile.statements) {
|
|
116
|
+
if (ts.isVariableStatement(statement)) {
|
|
117
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
118
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== "injectionTable" || !declaration.initializer) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
tableObject = resolveObjectExpression(declaration.initializer, declarations);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
125
|
+
tableObject ??= resolveObjectExpression(statement.expression, declarations);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!tableObject) return [];
|
|
129
|
+
const ids = /* @__PURE__ */ new Set();
|
|
130
|
+
for (const property of tableObject.properties) {
|
|
131
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
132
|
+
collectWidgetIdsFromTableValue(property.initializer, declarations, ids);
|
|
133
|
+
}
|
|
134
|
+
return Array.from(ids);
|
|
135
|
+
} catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function resolveScannedWidgetPath(roots, fromApp, relPath) {
|
|
140
|
+
const base = fromApp ? roots.appBase : resolveStandaloneSourceMirrorBase(roots.pkgBase) ?? roots.pkgBase;
|
|
141
|
+
return path.join(base, "widgets", "injection", ...relPath.split("/"));
|
|
142
|
+
}
|
|
14
143
|
function createInjectionWidgetsExtension() {
|
|
15
144
|
const widgetEntries = /* @__PURE__ */ new Map();
|
|
16
145
|
const tableEntries = [];
|
|
@@ -19,6 +148,7 @@ function createInjectionWidgetsExtension() {
|
|
|
19
148
|
outputFiles: ["injection-widgets.generated.ts", "injection-tables.generated.ts"],
|
|
20
149
|
scanModule(ctx) {
|
|
21
150
|
const files = scanModuleDir(ctx.roots, SCAN_CONFIGS.injectionWidgets);
|
|
151
|
+
const moduleWidgetEntries = [];
|
|
22
152
|
for (const { relPath, fromApp } of files) {
|
|
23
153
|
const segments = relPath.split("/");
|
|
24
154
|
const file = segments.pop() ?? "";
|
|
@@ -31,14 +161,22 @@ function createInjectionWidgetsExtension() {
|
|
|
31
161
|
moduleId: ctx.moduleId,
|
|
32
162
|
key,
|
|
33
163
|
source: fromApp ? "app" : "package",
|
|
34
|
-
importPath
|
|
164
|
+
importPath,
|
|
165
|
+
widgetId: extractWidgetMetadataId(resolveScannedWidgetPath(ctx.roots, fromApp, relPath)) ?? void 0
|
|
35
166
|
};
|
|
36
|
-
|
|
167
|
+
moduleWidgetEntries.push(entry);
|
|
168
|
+
}
|
|
169
|
+
const table = ctx.resolveModuleFile(ctx.roots, ctx.imps, "widgets/injection-table.ts");
|
|
170
|
+
const tableWidgetIds = table ? extractInjectionTableWidgetIds(table.absolutePath) : [];
|
|
171
|
+
if (moduleWidgetEntries.length === 1 && tableWidgetIds.length === 1 && !moduleWidgetEntries[0].widgetId) {
|
|
172
|
+
moduleWidgetEntries[0].widgetId = tableWidgetIds[0];
|
|
173
|
+
}
|
|
174
|
+
for (const entry of moduleWidgetEntries) {
|
|
175
|
+
const existing = widgetEntries.get(entry.key);
|
|
37
176
|
if (!existing || existing.source !== "app" && entry.source === "app") {
|
|
38
|
-
widgetEntries.set(key, entry);
|
|
177
|
+
widgetEntries.set(entry.key, entry);
|
|
39
178
|
}
|
|
40
179
|
}
|
|
41
|
-
const table = ctx.resolveModuleFile(ctx.roots, ctx.imps, "widgets/injection-table.ts");
|
|
42
180
|
if (table) {
|
|
43
181
|
tableEntries.push({
|
|
44
182
|
moduleId: ctx.moduleId,
|
|
@@ -53,6 +191,7 @@ function createInjectionWidgetsExtension() {
|
|
|
53
191
|
{ name: "moduleId", value: entry.moduleId },
|
|
54
192
|
{ name: "key", value: entry.key },
|
|
55
193
|
{ name: "source", value: entry.source },
|
|
194
|
+
...entry.widgetId ? [{ name: "widgetId", value: entry.widgetId }] : [],
|
|
56
195
|
{
|
|
57
196
|
name: "loader",
|
|
58
197
|
value: arrowFunction({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/generators/extensions/injection-widgets.ts"],
|
|
4
|
-
"sourcesContent": ["import { VariableDeclarationKind } from 'ts-morph'\nimport type { GeneratorExtension } from '../extension'\nimport { scanModuleDir, SCAN_CONFIGS } from '../scanner'\nimport {\n arrayLiteral,\n arrowFunction,\n identifier,\n methodCall,\n nullishCoalesce,\n objectLiteral,\n propertyAccess,\n writeValue,\n} from '../ast'\nimport { dynamicImportExpression, emptyObject, namespaceFallback, namespaceImportSpec, renderGeneratedTsSource } from './shared'\n\ntype InjectionWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n importPath: string\n}\n\ntype InjectionTableEntry = {\n moduleId: string\n importName: string\n importPath: string\n}\n\nexport function createInjectionWidgetsExtension(): GeneratorExtension {\n const widgetEntries = new Map<string, InjectionWidgetEntry>()\n const tableEntries: InjectionTableEntry[] = []\n\n return {\n id: 'registry.injection-widgets',\n outputFiles: ['injection-widgets.generated.ts', 'injection-tables.generated.ts'],\n scanModule(ctx) {\n const files = scanModuleDir(ctx.roots, SCAN_CONFIGS.injectionWidgets)\n for (const { relPath, fromApp } of files) {\n const segments = relPath.split('/')\n const file = segments.pop() ?? ''\n const base = file.replace(/\\.(t|j)sx?$/, '')\n const importPath = ctx.sanitizeGeneratedModuleSpecifier(\n `${fromApp ? ctx.imps.appBase : ctx.imps.pkgBase}/widgets/injection/${[...segments, base].join('/')}`\n )\n const key = [ctx.moduleId, ...segments, base].filter(Boolean).join(':')\n const entry: InjectionWidgetEntry = {\n moduleId: ctx.moduleId,\n key,\n source: fromApp ? 'app' : 'package',\n importPath,\n }\n const existing = widgetEntries.get(key)\n if (!existing || (existing.source !== 'app' && entry.source === 'app')) {\n widgetEntries.set(key, entry)\n }\n }\n\n const table = ctx.resolveModuleFile(ctx.roots, ctx.imps, 'widgets/injection-table.ts')\n if (table) {\n tableEntries.push({\n moduleId: ctx.moduleId,\n importName: `InjTable_${ctx.moduleId.replace(/[^a-zA-Z0-9_]/g, '_')}_${ctx.importIdRef.value++}`,\n importPath: ctx.sanitizeGeneratedModuleSpecifier(table.importPath),\n })\n }\n },\n generateOutput() {\n const widgetDecls = Array.from(widgetEntries.entries())\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([, entry]) =>\n objectLiteral([\n { name: 'moduleId', value: entry.moduleId },\n { name: 'key', value: entry.key },\n { name: 'source', value: entry.source },\n {\n name: 'loader',\n value: arrowFunction({\n body: methodCall(dynamicImportExpression(entry.importPath), 'then', [\n arrowFunction({\n parameters: ['mod'],\n body: nullishCoalesce([\n propertyAccess(identifier('mod'), 'default'),\n identifier('mod'),\n ]),\n }),\n ]),\n }),\n },\n ]),\n )\n\n const widgetsOutput = renderGeneratedTsSource({\n fileName: 'injection-widgets.generated.ts',\n imports: [\n { namedImports: [{ name: 'ModuleInjectionWidgetEntry', isTypeOnly: true }], moduleSpecifier: '@open-mercato/shared/modules/registry' },\n ],\n build(sourceFile) {\n sourceFile.addVariableStatement({\n declarationKind: VariableDeclarationKind.Const,\n isExported: true,\n declarations: [\n {\n name: 'injectionWidgetEntries',\n type: 'ModuleInjectionWidgetEntry[]',\n initializer: arrayLiteral(widgetDecls, writeValue),\n },\n ],\n })\n },\n })\n\n const tableImports = tableEntries.map((entry) => namespaceImportSpec(entry.importName, entry.importPath))\n const tableDecls = tableEntries.map((entry) =>\n objectLiteral([\n { name: 'moduleId', value: entry.moduleId },\n {\n name: 'table',\n value: namespaceFallback({\n importName: entry.importName,\n members: ['default', 'injectionTable'],\n fallback: emptyObject(),\n castType: 'ModuleInjectionTable',\n }),\n },\n ]),\n )\n const tablesOutput = renderGeneratedTsSource({\n fileName: 'injection-tables.generated.ts',\n imports: [\n { namedImports: [{ name: 'ModuleInjectionTable', isTypeOnly: true }], moduleSpecifier: '@open-mercato/shared/modules/widgets/injection' },\n ...tableImports,\n ],\n build(sourceFile) {\n sourceFile.addVariableStatement({\n declarationKind: VariableDeclarationKind.Const,\n isExported: true,\n declarations: [\n {\n name: 'injectionTables',\n type: 'Array<{ moduleId: string; table: ModuleInjectionTable }>',\n initializer: arrayLiteral(tableDecls, writeValue),\n },\n ],\n })\n },\n })\n\n return new Map([\n ['injection-widgets.generated.ts', widgetsOutput],\n ['injection-tables.generated.ts', tablesOutput],\n ])\n },\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,+BAA+B;
|
|
4
|
+
"sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport { VariableDeclarationKind } from 'ts-morph'\nimport ts from 'typescript'\nimport type { GeneratorExtension } from '../extension'\nimport { resolveStandaloneSourceMirrorBase, scanModuleDir, SCAN_CONFIGS, type ModuleRoots } from '../scanner'\nimport {\n arrayLiteral,\n arrowFunction,\n identifier,\n methodCall,\n nullishCoalesce,\n objectLiteral,\n propertyAccess,\n writeValue,\n} from '../ast'\nimport { dynamicImportExpression, emptyObject, namespaceFallback, namespaceImportSpec, renderGeneratedTsSource } from './shared'\n\ntype InjectionWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n importPath: string\n widgetId?: string\n}\n\ntype InjectionTableEntry = {\n moduleId: string\n importName: string\n importPath: string\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression\n while (\n ts.isParenthesizedExpression(current)\n || ts.isAsExpression(current)\n || ts.isTypeAssertionExpression(current)\n || ts.isSatisfiesExpression(current)\n ) {\n current = current.expression\n }\n return current\n}\n\nfunction getPropertyNameText(name: ts.PropertyName): string | null {\n if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)) {\n return name.text\n }\n return null\n}\n\nfunction collectObjectDeclarations(sourceFile: ts.SourceFile): Map<string, ts.ObjectLiteralExpression> {\n const declarations = new Map<string, ts.ObjectLiteralExpression>()\n for (const statement of sourceFile.statements) {\n if (!ts.isVariableStatement(statement)) continue\n for (const declaration of statement.declarationList.declarations) {\n if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue\n const initializer = unwrapExpression(declaration.initializer)\n if (ts.isObjectLiteralExpression(initializer)) {\n declarations.set(declaration.name.text, initializer)\n }\n }\n }\n return declarations\n}\n\nfunction resolveObjectExpression(\n expression: ts.Expression,\n declarations: Map<string, ts.ObjectLiteralExpression>,\n): ts.ObjectLiteralExpression | null {\n const unwrapped = unwrapExpression(expression)\n if (ts.isObjectLiteralExpression(unwrapped)) return unwrapped\n if (ts.isIdentifier(unwrapped)) return declarations.get(unwrapped.text) ?? null\n return null\n}\n\nfunction getObjectPropertyExpression(object: ts.ObjectLiteralExpression, propertyName: string): ts.Expression | null {\n for (const property of object.properties) {\n if (!ts.isPropertyAssignment(property)) continue\n if (getPropertyNameText(property.name) === propertyName) return property.initializer\n }\n return null\n}\n\nfunction getStringPropertyValue(object: ts.ObjectLiteralExpression, propertyName: string): string | null {\n const expression = getObjectPropertyExpression(object, propertyName)\n if (!expression) return null\n const unwrapped = unwrapExpression(expression)\n if (ts.isStringLiteral(unwrapped) || ts.isNoSubstitutionTemplateLiteral(unwrapped)) return unwrapped.text\n return null\n}\n\nfunction extractMetadataId(object: ts.ObjectLiteralExpression): string | null {\n const metadata = getObjectPropertyExpression(object, 'metadata')\n if (!metadata) return null\n const unwrapped = unwrapExpression(metadata)\n if (!ts.isObjectLiteralExpression(unwrapped)) return null\n return getStringPropertyValue(unwrapped, 'id')\n}\n\nfunction extractWidgetMetadataId(absolutePath: string): string | null {\n try {\n const source = fs.readFileSync(absolutePath, 'utf8')\n const sourceFile = ts.createSourceFile(absolutePath, source, ts.ScriptTarget.Latest, true)\n const declarations = collectObjectDeclarations(sourceFile)\n\n for (const statement of sourceFile.statements) {\n if (!ts.isExportAssignment(statement) || statement.isExportEquals) continue\n const object = resolveObjectExpression(statement.expression, declarations)\n if (!object) continue\n const id = extractMetadataId(object)\n if (id) return id\n }\n\n for (const object of declarations.values()) {\n const id = extractMetadataId(object)\n if (id) return id\n }\n } catch {\n return null\n }\n return null\n}\n\nfunction collectWidgetIdsFromTableValue(\n value: ts.Expression,\n declarations: Map<string, ts.ObjectLiteralExpression>,\n ids: Set<string>,\n) {\n const expression = unwrapExpression(value)\n if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {\n ids.add(expression.text)\n return\n }\n if (ts.isArrayLiteralExpression(expression)) {\n for (const element of expression.elements) {\n if (ts.isExpression(element)) collectWidgetIdsFromTableValue(element, declarations, ids)\n }\n return\n }\n const object = resolveObjectExpression(expression, declarations)\n if (!object) return\n const widgetId = getStringPropertyValue(object, 'widgetId')\n if (widgetId) ids.add(widgetId)\n}\n\nfunction extractInjectionTableWidgetIds(absolutePath: string): string[] {\n try {\n const source = fs.readFileSync(absolutePath, 'utf8')\n const sourceFile = ts.createSourceFile(absolutePath, source, ts.ScriptTarget.Latest, true)\n const declarations = collectObjectDeclarations(sourceFile)\n let tableObject: ts.ObjectLiteralExpression | null = null\n\n for (const statement of sourceFile.statements) {\n if (ts.isVariableStatement(statement)) {\n for (const declaration of statement.declarationList.declarations) {\n if (!ts.isIdentifier(declaration.name) || declaration.name.text !== 'injectionTable' || !declaration.initializer) {\n continue\n }\n tableObject = resolveObjectExpression(declaration.initializer, declarations)\n }\n }\n if (ts.isExportAssignment(statement) && !statement.isExportEquals) {\n tableObject ??= resolveObjectExpression(statement.expression, declarations)\n }\n }\n\n if (!tableObject) return []\n const ids = new Set<string>()\n for (const property of tableObject.properties) {\n if (!ts.isPropertyAssignment(property)) continue\n collectWidgetIdsFromTableValue(property.initializer, declarations, ids)\n }\n return Array.from(ids)\n } catch {\n return []\n }\n}\n\nfunction resolveScannedWidgetPath(roots: ModuleRoots, fromApp: boolean, relPath: string): string {\n const base = fromApp ? roots.appBase : (resolveStandaloneSourceMirrorBase(roots.pkgBase) ?? roots.pkgBase)\n return path.join(base, 'widgets', 'injection', ...relPath.split('/'))\n}\n\nexport function createInjectionWidgetsExtension(): GeneratorExtension {\n const widgetEntries = new Map<string, InjectionWidgetEntry>()\n const tableEntries: InjectionTableEntry[] = []\n\n return {\n id: 'registry.injection-widgets',\n outputFiles: ['injection-widgets.generated.ts', 'injection-tables.generated.ts'],\n scanModule(ctx) {\n const files = scanModuleDir(ctx.roots, SCAN_CONFIGS.injectionWidgets)\n const moduleWidgetEntries: InjectionWidgetEntry[] = []\n for (const { relPath, fromApp } of files) {\n const segments = relPath.split('/')\n const file = segments.pop() ?? ''\n const base = file.replace(/\\.(t|j)sx?$/, '')\n const importPath = ctx.sanitizeGeneratedModuleSpecifier(\n `${fromApp ? ctx.imps.appBase : ctx.imps.pkgBase}/widgets/injection/${[...segments, base].join('/')}`\n )\n const key = [ctx.moduleId, ...segments, base].filter(Boolean).join(':')\n const entry: InjectionWidgetEntry = {\n moduleId: ctx.moduleId,\n key,\n source: fromApp ? 'app' : 'package',\n importPath,\n widgetId: extractWidgetMetadataId(resolveScannedWidgetPath(ctx.roots, fromApp, relPath)) ?? undefined,\n }\n moduleWidgetEntries.push(entry)\n }\n\n const table = ctx.resolveModuleFile(ctx.roots, ctx.imps, 'widgets/injection-table.ts')\n const tableWidgetIds = table ? extractInjectionTableWidgetIds(table.absolutePath) : []\n if (moduleWidgetEntries.length === 1 && tableWidgetIds.length === 1 && !moduleWidgetEntries[0].widgetId) {\n moduleWidgetEntries[0].widgetId = tableWidgetIds[0]\n }\n\n for (const entry of moduleWidgetEntries) {\n const existing = widgetEntries.get(entry.key)\n if (!existing || (existing.source !== 'app' && entry.source === 'app')) {\n widgetEntries.set(entry.key, entry)\n }\n }\n\n if (table) {\n tableEntries.push({\n moduleId: ctx.moduleId,\n importName: `InjTable_${ctx.moduleId.replace(/[^a-zA-Z0-9_]/g, '_')}_${ctx.importIdRef.value++}`,\n importPath: ctx.sanitizeGeneratedModuleSpecifier(table.importPath),\n })\n }\n },\n generateOutput() {\n const widgetDecls = Array.from(widgetEntries.entries())\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([, entry]) =>\n objectLiteral([\n { name: 'moduleId', value: entry.moduleId },\n { name: 'key', value: entry.key },\n { name: 'source', value: entry.source },\n ...(entry.widgetId ? [{ name: 'widgetId', value: entry.widgetId }] : []),\n {\n name: 'loader',\n value: arrowFunction({\n body: methodCall(dynamicImportExpression(entry.importPath), 'then', [\n arrowFunction({\n parameters: ['mod'],\n body: nullishCoalesce([\n propertyAccess(identifier('mod'), 'default'),\n identifier('mod'),\n ]),\n }),\n ]),\n }),\n },\n ]),\n )\n\n const widgetsOutput = renderGeneratedTsSource({\n fileName: 'injection-widgets.generated.ts',\n imports: [\n { namedImports: [{ name: 'ModuleInjectionWidgetEntry', isTypeOnly: true }], moduleSpecifier: '@open-mercato/shared/modules/registry' },\n ],\n build(sourceFile) {\n sourceFile.addVariableStatement({\n declarationKind: VariableDeclarationKind.Const,\n isExported: true,\n declarations: [\n {\n name: 'injectionWidgetEntries',\n type: 'ModuleInjectionWidgetEntry[]',\n initializer: arrayLiteral(widgetDecls, writeValue),\n },\n ],\n })\n },\n })\n\n const tableImports = tableEntries.map((entry) => namespaceImportSpec(entry.importName, entry.importPath))\n const tableDecls = tableEntries.map((entry) =>\n objectLiteral([\n { name: 'moduleId', value: entry.moduleId },\n {\n name: 'table',\n value: namespaceFallback({\n importName: entry.importName,\n members: ['default', 'injectionTable'],\n fallback: emptyObject(),\n castType: 'ModuleInjectionTable',\n }),\n },\n ]),\n )\n const tablesOutput = renderGeneratedTsSource({\n fileName: 'injection-tables.generated.ts',\n imports: [\n { namedImports: [{ name: 'ModuleInjectionTable', isTypeOnly: true }], moduleSpecifier: '@open-mercato/shared/modules/widgets/injection' },\n ...tableImports,\n ],\n build(sourceFile) {\n sourceFile.addVariableStatement({\n declarationKind: VariableDeclarationKind.Const,\n isExported: true,\n declarations: [\n {\n name: 'injectionTables',\n type: 'Array<{ moduleId: string; table: ModuleInjectionTable }>',\n initializer: arrayLiteral(tableDecls, writeValue),\n },\n ],\n })\n },\n })\n\n return new Map([\n ['injection-widgets.generated.ts', widgetsOutput],\n ['injection-tables.generated.ts', tablesOutput],\n ])\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,+BAA+B;AACxC,OAAO,QAAQ;AAEf,SAAS,mCAAmC,eAAe,oBAAsC;AACjG;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB,aAAa,mBAAmB,qBAAqB,+BAA+B;AAgBtH,SAAS,iBAAiB,YAA0C;AAClE,MAAI,UAAU;AACd,SACE,GAAG,0BAA0B,OAAO,KACjC,GAAG,eAAe,OAAO,KACzB,GAAG,0BAA0B,OAAO,KACpC,GAAG,sBAAsB,OAAO,GACnC;AACA,cAAU,QAAQ;AAAA,EACpB;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAsC;AACjE,MAAI,GAAG,aAAa,IAAI,KAAK,GAAG,gBAAgB,IAAI,KAAK,GAAG,gCAAgC,IAAI,GAAG;AACjG,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAEA,SAAS,0BAA0B,YAAoE;AACrG,QAAM,eAAe,oBAAI,IAAwC;AACjE,aAAW,aAAa,WAAW,YAAY;AAC7C,QAAI,CAAC,GAAG,oBAAoB,SAAS,EAAG;AACxC,eAAW,eAAe,UAAU,gBAAgB,cAAc;AAChE,UAAI,CAAC,GAAG,aAAa,YAAY,IAAI,KAAK,CAAC,YAAY,YAAa;AACpE,YAAM,cAAc,iBAAiB,YAAY,WAAW;AAC5D,UAAI,GAAG,0BAA0B,WAAW,GAAG;AAC7C,qBAAa,IAAI,YAAY,KAAK,MAAM,WAAW;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,wBACP,YACA,cACmC;AACnC,QAAM,YAAY,iBAAiB,UAAU;AAC7C,MAAI,GAAG,0BAA0B,SAAS,EAAG,QAAO;AACpD,MAAI,GAAG,aAAa,SAAS,EAAG,QAAO,aAAa,IAAI,UAAU,IAAI,KAAK;AAC3E,SAAO;AACT;AAEA,SAAS,4BAA4B,QAAoC,cAA4C;AACnH,aAAW,YAAY,OAAO,YAAY;AACxC,QAAI,CAAC,GAAG,qBAAqB,QAAQ,EAAG;AACxC,QAAI,oBAAoB,SAAS,IAAI,MAAM,aAAc,QAAO,SAAS;AAAA,EAC3E;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,QAAoC,cAAqC;AACvG,QAAM,aAAa,4BAA4B,QAAQ,YAAY;AACnE,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,YAAY,iBAAiB,UAAU;AAC7C,MAAI,GAAG,gBAAgB,SAAS,KAAK,GAAG,gCAAgC,SAAS,EAAG,QAAO,UAAU;AACrG,SAAO;AACT;AAEA,SAAS,kBAAkB,QAAmD;AAC5E,QAAM,WAAW,4BAA4B,QAAQ,UAAU;AAC/D,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,YAAY,iBAAiB,QAAQ;AAC3C,MAAI,CAAC,GAAG,0BAA0B,SAAS,EAAG,QAAO;AACrD,SAAO,uBAAuB,WAAW,IAAI;AAC/C;AAEA,SAAS,wBAAwB,cAAqC;AACpE,MAAI;AACF,UAAM,SAAS,GAAG,aAAa,cAAc,MAAM;AACnD,UAAM,aAAa,GAAG,iBAAiB,cAAc,QAAQ,GAAG,aAAa,QAAQ,IAAI;AACzF,UAAM,eAAe,0BAA0B,UAAU;AAEzD,eAAW,aAAa,WAAW,YAAY;AAC7C,UAAI,CAAC,GAAG,mBAAmB,SAAS,KAAK,UAAU,eAAgB;AACnE,YAAM,SAAS,wBAAwB,UAAU,YAAY,YAAY;AACzE,UAAI,CAAC,OAAQ;AACb,YAAM,KAAK,kBAAkB,MAAM;AACnC,UAAI,GAAI,QAAO;AAAA,IACjB;AAEA,eAAW,UAAU,aAAa,OAAO,GAAG;AAC1C,YAAM,KAAK,kBAAkB,MAAM;AACnC,UAAI,GAAI,QAAO;AAAA,IACjB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,+BACP,OACA,cACA,KACA;AACA,QAAM,aAAa,iBAAiB,KAAK;AACzC,MAAI,GAAG,gBAAgB,UAAU,KAAK,GAAG,gCAAgC,UAAU,GAAG;AACpF,QAAI,IAAI,WAAW,IAAI;AACvB;AAAA,EACF;AACA,MAAI,GAAG,yBAAyB,UAAU,GAAG;AAC3C,eAAW,WAAW,WAAW,UAAU;AACzC,UAAI,GAAG,aAAa,OAAO,EAAG,gCAA+B,SAAS,cAAc,GAAG;AAAA,IACzF;AACA;AAAA,EACF;AACA,QAAM,SAAS,wBAAwB,YAAY,YAAY;AAC/D,MAAI,CAAC,OAAQ;AACb,QAAM,WAAW,uBAAuB,QAAQ,UAAU;AAC1D,MAAI,SAAU,KAAI,IAAI,QAAQ;AAChC;AAEA,SAAS,+BAA+B,cAAgC;AACtE,MAAI;AACF,UAAM,SAAS,GAAG,aAAa,cAAc,MAAM;AACnD,UAAM,aAAa,GAAG,iBAAiB,cAAc,QAAQ,GAAG,aAAa,QAAQ,IAAI;AACzF,UAAM,eAAe,0BAA0B,UAAU;AACzD,QAAI,cAAiD;AAErD,eAAW,aAAa,WAAW,YAAY;AAC7C,UAAI,GAAG,oBAAoB,SAAS,GAAG;AACrC,mBAAW,eAAe,UAAU,gBAAgB,cAAc;AAChE,cAAI,CAAC,GAAG,aAAa,YAAY,IAAI,KAAK,YAAY,KAAK,SAAS,oBAAoB,CAAC,YAAY,aAAa;AAChH;AAAA,UACF;AACA,wBAAc,wBAAwB,YAAY,aAAa,YAAY;AAAA,QAC7E;AAAA,MACF;AACA,UAAI,GAAG,mBAAmB,SAAS,KAAK,CAAC,UAAU,gBAAgB;AACjE,wBAAgB,wBAAwB,UAAU,YAAY,YAAY;AAAA,MAC5E;AAAA,IACF;AAEA,QAAI,CAAC,YAAa,QAAO,CAAC;AAC1B,UAAM,MAAM,oBAAI,IAAY;AAC5B,eAAW,YAAY,YAAY,YAAY;AAC7C,UAAI,CAAC,GAAG,qBAAqB,QAAQ,EAAG;AACxC,qCAA+B,SAAS,aAAa,cAAc,GAAG;AAAA,IACxE;AACA,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,yBAAyB,OAAoB,SAAkB,SAAyB;AAC/F,QAAM,OAAO,UAAU,MAAM,UAAW,kCAAkC,MAAM,OAAO,KAAK,MAAM;AAClG,SAAO,KAAK,KAAK,MAAM,WAAW,aAAa,GAAG,QAAQ,MAAM,GAAG,CAAC;AACtE;AAEO,SAAS,kCAAsD;AACpE,QAAM,gBAAgB,oBAAI,IAAkC;AAC5D,QAAM,eAAsC,CAAC;AAE7C,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,aAAa,CAAC,kCAAkC,+BAA+B;AAAA,IAC/E,WAAW,KAAK;AACd,YAAM,QAAQ,cAAc,IAAI,OAAO,aAAa,gBAAgB;AACpE,YAAM,sBAA8C,CAAC;AACrD,iBAAW,EAAE,SAAS,QAAQ,KAAK,OAAO;AACxC,cAAM,WAAW,QAAQ,MAAM,GAAG;AAClC,cAAM,OAAO,SAAS,IAAI,KAAK;AAC/B,cAAM,OAAO,KAAK,QAAQ,eAAe,EAAE;AAC3C,cAAM,aAAa,IAAI;AAAA,UACrB,GAAG,UAAU,IAAI,KAAK,UAAU,IAAI,KAAK,OAAO,sBAAsB,CAAC,GAAG,UAAU,IAAI,EAAE,KAAK,GAAG,CAAC;AAAA,QACrG;AACA,cAAM,MAAM,CAAC,IAAI,UAAU,GAAG,UAAU,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACtE,cAAM,QAA8B;AAAA,UAClC,UAAU,IAAI;AAAA,UACd;AAAA,UACA,QAAQ,UAAU,QAAQ;AAAA,UAC1B;AAAA,UACA,UAAU,wBAAwB,yBAAyB,IAAI,OAAO,SAAS,OAAO,CAAC,KAAK;AAAA,QAC9F;AACA,4BAAoB,KAAK,KAAK;AAAA,MAChC;AAEA,YAAM,QAAQ,IAAI,kBAAkB,IAAI,OAAO,IAAI,MAAM,4BAA4B;AACrF,YAAM,iBAAiB,QAAQ,+BAA+B,MAAM,YAAY,IAAI,CAAC;AACrF,UAAI,oBAAoB,WAAW,KAAK,eAAe,WAAW,KAAK,CAAC,oBAAoB,CAAC,EAAE,UAAU;AACvG,4BAAoB,CAAC,EAAE,WAAW,eAAe,CAAC;AAAA,MACpD;AAEA,iBAAW,SAAS,qBAAqB;AACvC,cAAM,WAAW,cAAc,IAAI,MAAM,GAAG;AAC5C,YAAI,CAAC,YAAa,SAAS,WAAW,SAAS,MAAM,WAAW,OAAQ;AACtE,wBAAc,IAAI,MAAM,KAAK,KAAK;AAAA,QACpC;AAAA,MACF;AAEA,UAAI,OAAO;AACT,qBAAa,KAAK;AAAA,UAChB,UAAU,IAAI;AAAA,UACd,YAAY,YAAY,IAAI,SAAS,QAAQ,kBAAkB,GAAG,CAAC,IAAI,IAAI,YAAY,OAAO;AAAA,UAC9F,YAAY,IAAI,iCAAiC,MAAM,UAAU;AAAA,QACnE,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,iBAAiB;AACf,YAAM,cAAc,MAAM,KAAK,cAAc,QAAQ,CAAC,EACnD,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,MAAM,KAAK,cAAc,KAAK,CAAC,EACnD;AAAA,QAAI,CAAC,CAAC,EAAE,KAAK,MACZ,cAAc;AAAA,UACZ,EAAE,MAAM,YAAY,OAAO,MAAM,SAAS;AAAA,UAC1C,EAAE,MAAM,OAAO,OAAO,MAAM,IAAI;AAAA,UAChC,EAAE,MAAM,UAAU,OAAO,MAAM,OAAO;AAAA,UACtC,GAAI,MAAM,WAAW,CAAC,EAAE,MAAM,YAAY,OAAO,MAAM,SAAS,CAAC,IAAI,CAAC;AAAA,UACtE;AAAA,YACE,MAAM;AAAA,YACN,OAAO,cAAc;AAAA,cACnB,MAAM,WAAW,wBAAwB,MAAM,UAAU,GAAG,QAAQ;AAAA,gBAClE,cAAc;AAAA,kBACZ,YAAY,CAAC,KAAK;AAAA,kBAClB,MAAM,gBAAgB;AAAA,oBACpB,eAAe,WAAW,KAAK,GAAG,SAAS;AAAA,oBAC3C,WAAW,KAAK;AAAA,kBAClB,CAAC;AAAA,gBACH,CAAC;AAAA,cACH,CAAC;AAAA,YACH,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AAEF,YAAM,gBAAgB,wBAAwB;AAAA,QAC5C,UAAU;AAAA,QACV,SAAS;AAAA,UACP,EAAE,cAAc,CAAC,EAAE,MAAM,8BAA8B,YAAY,KAAK,CAAC,GAAG,iBAAiB,wCAAwC;AAAA,QACvI;AAAA,QACA,MAAM,YAAY;AAChB,qBAAW,qBAAqB;AAAA,YAC9B,iBAAiB,wBAAwB;AAAA,YACzC,YAAY;AAAA,YACZ,cAAc;AAAA,cACZ;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM;AAAA,gBACN,aAAa,aAAa,aAAa,UAAU;AAAA,cACnD;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAED,YAAM,eAAe,aAAa,IAAI,CAAC,UAAU,oBAAoB,MAAM,YAAY,MAAM,UAAU,CAAC;AACxG,YAAM,aAAa,aAAa;AAAA,QAAI,CAAC,UACnC,cAAc;AAAA,UACZ,EAAE,MAAM,YAAY,OAAO,MAAM,SAAS;AAAA,UAC1C;AAAA,YACE,MAAM;AAAA,YACN,OAAO,kBAAkB;AAAA,cACvB,YAAY,MAAM;AAAA,cAClB,SAAS,CAAC,WAAW,gBAAgB;AAAA,cACrC,UAAU,YAAY;AAAA,cACtB,UAAU;AAAA,YACZ,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AACA,YAAM,eAAe,wBAAwB;AAAA,QAC3C,UAAU;AAAA,QACV,SAAS;AAAA,UACP,EAAE,cAAc,CAAC,EAAE,MAAM,wBAAwB,YAAY,KAAK,CAAC,GAAG,iBAAiB,iDAAiD;AAAA,UACxI,GAAG;AAAA,QACL;AAAA,QACA,MAAM,YAAY;AAChB,qBAAW,qBAAqB;AAAA,YAC9B,iBAAiB,wBAAwB;AAAA,YACzC,YAAY;AAAA,YACZ,cAAc;AAAA,cACZ;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM;AAAA,gBACN,aAAa,aAAa,YAAY,UAAU;AAAA,cAClD;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAED,aAAO,oBAAI,IAAI;AAAA,QACb,CAAC,kCAAkC,aAAa;AAAA,QAChD,CAAC,iCAAiC,YAAY;AAAA,MAChD,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6126.1.0b9392fdae",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@mikro-orm/decorators": "^7.1.4",
|
|
61
61
|
"@mikro-orm/migrations": "^7.1.4",
|
|
62
62
|
"@mikro-orm/postgresql": "^7.1.4",
|
|
63
|
-
"@open-mercato/queue": "0.6.6-develop.
|
|
64
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
63
|
+
"@open-mercato/queue": "0.6.6-develop.6126.1.0b9392fdae",
|
|
64
|
+
"@open-mercato/shared": "0.6.6-develop.6126.1.0b9392fdae",
|
|
65
65
|
"cross-spawn": "^7.0.6",
|
|
66
66
|
"pg": "8.21.0",
|
|
67
67
|
"semver": "^7.8.4",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"typescript": "^6.0.3"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
74
|
+
"@open-mercato/shared": "0.6.6-develop.6126.1.0b9392fdae"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
77
|
+
"@open-mercato/shared": "0.6.6-develop.6126.1.0b9392fdae",
|
|
78
78
|
"@types/jest": "^30.0.0",
|
|
79
79
|
"jest": "^30.4.2",
|
|
80
80
|
"ts-jest": "^29.4.11"
|
|
@@ -1091,6 +1091,10 @@ describe('injection-widgets.generated.ts', () => {
|
|
|
1091
1091
|
expect(content).toContain('moduleId: "orders"')
|
|
1092
1092
|
expect(content).toContain('key: "orders:sidebar:widget"')
|
|
1093
1093
|
})
|
|
1094
|
+
|
|
1095
|
+
it('emits the injection table widgetId as an optional lookup hint', () => {
|
|
1096
|
+
expect(content).toContain('widgetId: "orders.sidebar"')
|
|
1097
|
+
})
|
|
1094
1098
|
})
|
|
1095
1099
|
|
|
1096
1100
|
describe('injection-tables.generated.ts', () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
1
3
|
import { VariableDeclarationKind } from 'ts-morph'
|
|
4
|
+
import ts from 'typescript'
|
|
2
5
|
import type { GeneratorExtension } from '../extension'
|
|
3
|
-
import { scanModuleDir, SCAN_CONFIGS } from '../scanner'
|
|
6
|
+
import { resolveStandaloneSourceMirrorBase, scanModuleDir, SCAN_CONFIGS, type ModuleRoots } from '../scanner'
|
|
4
7
|
import {
|
|
5
8
|
arrayLiteral,
|
|
6
9
|
arrowFunction,
|
|
@@ -18,6 +21,7 @@ type InjectionWidgetEntry = {
|
|
|
18
21
|
key: string
|
|
19
22
|
source: 'app' | 'package'
|
|
20
23
|
importPath: string
|
|
24
|
+
widgetId?: string
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
type InjectionTableEntry = {
|
|
@@ -26,6 +30,159 @@ type InjectionTableEntry = {
|
|
|
26
30
|
importPath: string
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
function unwrapExpression(expression: ts.Expression): ts.Expression {
|
|
34
|
+
let current = expression
|
|
35
|
+
while (
|
|
36
|
+
ts.isParenthesizedExpression(current)
|
|
37
|
+
|| ts.isAsExpression(current)
|
|
38
|
+
|| ts.isTypeAssertionExpression(current)
|
|
39
|
+
|| ts.isSatisfiesExpression(current)
|
|
40
|
+
) {
|
|
41
|
+
current = current.expression
|
|
42
|
+
}
|
|
43
|
+
return current
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getPropertyNameText(name: ts.PropertyName): string | null {
|
|
47
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)) {
|
|
48
|
+
return name.text
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function collectObjectDeclarations(sourceFile: ts.SourceFile): Map<string, ts.ObjectLiteralExpression> {
|
|
54
|
+
const declarations = new Map<string, ts.ObjectLiteralExpression>()
|
|
55
|
+
for (const statement of sourceFile.statements) {
|
|
56
|
+
if (!ts.isVariableStatement(statement)) continue
|
|
57
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
58
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue
|
|
59
|
+
const initializer = unwrapExpression(declaration.initializer)
|
|
60
|
+
if (ts.isObjectLiteralExpression(initializer)) {
|
|
61
|
+
declarations.set(declaration.name.text, initializer)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return declarations
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveObjectExpression(
|
|
69
|
+
expression: ts.Expression,
|
|
70
|
+
declarations: Map<string, ts.ObjectLiteralExpression>,
|
|
71
|
+
): ts.ObjectLiteralExpression | null {
|
|
72
|
+
const unwrapped = unwrapExpression(expression)
|
|
73
|
+
if (ts.isObjectLiteralExpression(unwrapped)) return unwrapped
|
|
74
|
+
if (ts.isIdentifier(unwrapped)) return declarations.get(unwrapped.text) ?? null
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getObjectPropertyExpression(object: ts.ObjectLiteralExpression, propertyName: string): ts.Expression | null {
|
|
79
|
+
for (const property of object.properties) {
|
|
80
|
+
if (!ts.isPropertyAssignment(property)) continue
|
|
81
|
+
if (getPropertyNameText(property.name) === propertyName) return property.initializer
|
|
82
|
+
}
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getStringPropertyValue(object: ts.ObjectLiteralExpression, propertyName: string): string | null {
|
|
87
|
+
const expression = getObjectPropertyExpression(object, propertyName)
|
|
88
|
+
if (!expression) return null
|
|
89
|
+
const unwrapped = unwrapExpression(expression)
|
|
90
|
+
if (ts.isStringLiteral(unwrapped) || ts.isNoSubstitutionTemplateLiteral(unwrapped)) return unwrapped.text
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function extractMetadataId(object: ts.ObjectLiteralExpression): string | null {
|
|
95
|
+
const metadata = getObjectPropertyExpression(object, 'metadata')
|
|
96
|
+
if (!metadata) return null
|
|
97
|
+
const unwrapped = unwrapExpression(metadata)
|
|
98
|
+
if (!ts.isObjectLiteralExpression(unwrapped)) return null
|
|
99
|
+
return getStringPropertyValue(unwrapped, 'id')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function extractWidgetMetadataId(absolutePath: string): string | null {
|
|
103
|
+
try {
|
|
104
|
+
const source = fs.readFileSync(absolutePath, 'utf8')
|
|
105
|
+
const sourceFile = ts.createSourceFile(absolutePath, source, ts.ScriptTarget.Latest, true)
|
|
106
|
+
const declarations = collectObjectDeclarations(sourceFile)
|
|
107
|
+
|
|
108
|
+
for (const statement of sourceFile.statements) {
|
|
109
|
+
if (!ts.isExportAssignment(statement) || statement.isExportEquals) continue
|
|
110
|
+
const object = resolveObjectExpression(statement.expression, declarations)
|
|
111
|
+
if (!object) continue
|
|
112
|
+
const id = extractMetadataId(object)
|
|
113
|
+
if (id) return id
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const object of declarations.values()) {
|
|
117
|
+
const id = extractMetadataId(object)
|
|
118
|
+
if (id) return id
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function collectWidgetIdsFromTableValue(
|
|
127
|
+
value: ts.Expression,
|
|
128
|
+
declarations: Map<string, ts.ObjectLiteralExpression>,
|
|
129
|
+
ids: Set<string>,
|
|
130
|
+
) {
|
|
131
|
+
const expression = unwrapExpression(value)
|
|
132
|
+
if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
|
|
133
|
+
ids.add(expression.text)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
if (ts.isArrayLiteralExpression(expression)) {
|
|
137
|
+
for (const element of expression.elements) {
|
|
138
|
+
if (ts.isExpression(element)) collectWidgetIdsFromTableValue(element, declarations, ids)
|
|
139
|
+
}
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const object = resolveObjectExpression(expression, declarations)
|
|
143
|
+
if (!object) return
|
|
144
|
+
const widgetId = getStringPropertyValue(object, 'widgetId')
|
|
145
|
+
if (widgetId) ids.add(widgetId)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function extractInjectionTableWidgetIds(absolutePath: string): string[] {
|
|
149
|
+
try {
|
|
150
|
+
const source = fs.readFileSync(absolutePath, 'utf8')
|
|
151
|
+
const sourceFile = ts.createSourceFile(absolutePath, source, ts.ScriptTarget.Latest, true)
|
|
152
|
+
const declarations = collectObjectDeclarations(sourceFile)
|
|
153
|
+
let tableObject: ts.ObjectLiteralExpression | null = null
|
|
154
|
+
|
|
155
|
+
for (const statement of sourceFile.statements) {
|
|
156
|
+
if (ts.isVariableStatement(statement)) {
|
|
157
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
158
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== 'injectionTable' || !declaration.initializer) {
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
tableObject = resolveObjectExpression(declaration.initializer, declarations)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
165
|
+
tableObject ??= resolveObjectExpression(statement.expression, declarations)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!tableObject) return []
|
|
170
|
+
const ids = new Set<string>()
|
|
171
|
+
for (const property of tableObject.properties) {
|
|
172
|
+
if (!ts.isPropertyAssignment(property)) continue
|
|
173
|
+
collectWidgetIdsFromTableValue(property.initializer, declarations, ids)
|
|
174
|
+
}
|
|
175
|
+
return Array.from(ids)
|
|
176
|
+
} catch {
|
|
177
|
+
return []
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resolveScannedWidgetPath(roots: ModuleRoots, fromApp: boolean, relPath: string): string {
|
|
182
|
+
const base = fromApp ? roots.appBase : (resolveStandaloneSourceMirrorBase(roots.pkgBase) ?? roots.pkgBase)
|
|
183
|
+
return path.join(base, 'widgets', 'injection', ...relPath.split('/'))
|
|
184
|
+
}
|
|
185
|
+
|
|
29
186
|
export function createInjectionWidgetsExtension(): GeneratorExtension {
|
|
30
187
|
const widgetEntries = new Map<string, InjectionWidgetEntry>()
|
|
31
188
|
const tableEntries: InjectionTableEntry[] = []
|
|
@@ -35,6 +192,7 @@ export function createInjectionWidgetsExtension(): GeneratorExtension {
|
|
|
35
192
|
outputFiles: ['injection-widgets.generated.ts', 'injection-tables.generated.ts'],
|
|
36
193
|
scanModule(ctx) {
|
|
37
194
|
const files = scanModuleDir(ctx.roots, SCAN_CONFIGS.injectionWidgets)
|
|
195
|
+
const moduleWidgetEntries: InjectionWidgetEntry[] = []
|
|
38
196
|
for (const { relPath, fromApp } of files) {
|
|
39
197
|
const segments = relPath.split('/')
|
|
40
198
|
const file = segments.pop() ?? ''
|
|
@@ -48,14 +206,24 @@ export function createInjectionWidgetsExtension(): GeneratorExtension {
|
|
|
48
206
|
key,
|
|
49
207
|
source: fromApp ? 'app' : 'package',
|
|
50
208
|
importPath,
|
|
209
|
+
widgetId: extractWidgetMetadataId(resolveScannedWidgetPath(ctx.roots, fromApp, relPath)) ?? undefined,
|
|
51
210
|
}
|
|
52
|
-
|
|
211
|
+
moduleWidgetEntries.push(entry)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const table = ctx.resolveModuleFile(ctx.roots, ctx.imps, 'widgets/injection-table.ts')
|
|
215
|
+
const tableWidgetIds = table ? extractInjectionTableWidgetIds(table.absolutePath) : []
|
|
216
|
+
if (moduleWidgetEntries.length === 1 && tableWidgetIds.length === 1 && !moduleWidgetEntries[0].widgetId) {
|
|
217
|
+
moduleWidgetEntries[0].widgetId = tableWidgetIds[0]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const entry of moduleWidgetEntries) {
|
|
221
|
+
const existing = widgetEntries.get(entry.key)
|
|
53
222
|
if (!existing || (existing.source !== 'app' && entry.source === 'app')) {
|
|
54
|
-
widgetEntries.set(key, entry)
|
|
223
|
+
widgetEntries.set(entry.key, entry)
|
|
55
224
|
}
|
|
56
225
|
}
|
|
57
226
|
|
|
58
|
-
const table = ctx.resolveModuleFile(ctx.roots, ctx.imps, 'widgets/injection-table.ts')
|
|
59
227
|
if (table) {
|
|
60
228
|
tableEntries.push({
|
|
61
229
|
moduleId: ctx.moduleId,
|
|
@@ -72,6 +240,7 @@ export function createInjectionWidgetsExtension(): GeneratorExtension {
|
|
|
72
240
|
{ name: 'moduleId', value: entry.moduleId },
|
|
73
241
|
{ name: 'key', value: entry.key },
|
|
74
242
|
{ name: 'source', value: entry.source },
|
|
243
|
+
...(entry.widgetId ? [{ name: 'widgetId', value: entry.widgetId }] : []),
|
|
75
244
|
{
|
|
76
245
|
name: 'loader',
|
|
77
246
|
value: arrowFunction({
|