@open-mercato/cli 0.7.1-develop.7123.1.25365e718d → 0.7.1-develop.7131.1.20abf9e885
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/.turbo/turbo-build.log +1 -1
- package/dist/agentic/guides/module-facts.json +144 -144
- package/dist/agentic/guides/module-facts.v2.json +144 -144
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/index.md +1 -1
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customers/active-extension-bindings.md +9 -9
- package/dist/agentic/guides/modules/customers/index.md +1 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/active-extension-bindings.md +2 -2
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/translations/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +4 -2
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/lib/generators/entity-class-declarations.js +186 -0
- package/dist/lib/generators/entity-class-declarations.js.map +7 -0
- package/dist/lib/generators/module-entities.js +28 -0
- package/dist/lib/generators/module-entities.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/generators/__tests__/entity-class-declarations.test.ts +287 -0
- package/src/lib/generators/__tests__/module-entities.test.ts +219 -0
- package/src/lib/generators/entity-class-declarations.ts +255 -0
- package/src/lib/generators/module-entities.ts +54 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import ts from 'typescript-js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Names of the `@Entity()`-decorated classes a module's entity file exports.
|
|
6
|
+
*
|
|
7
|
+
* Only decorated classes count: an entity file may also export plain helper classes,
|
|
8
|
+
* and reporting those as entities would raise false collisions. Only a decorator bound
|
|
9
|
+
* to a `@mikro-orm/*` import counts, so an unrelated `Entity` decorator from another
|
|
10
|
+
* library never registers as one.
|
|
11
|
+
*
|
|
12
|
+
* Parsing is used rather than importing because the caller runs during generation,
|
|
13
|
+
* before the module graph exists. A monorepo checkout and a standalone install that
|
|
14
|
+
* ships `src/modules` both hand this TypeScript source; a package that ships compiled
|
|
15
|
+
* output only hands it the built file instead, where a decorated class has become a
|
|
16
|
+
* class expression plus a decorator-helper call, so both shapes are recognised.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const MIKRO_ORM_MODULE_PREFIX = '@mikro-orm/'
|
|
20
|
+
|
|
21
|
+
type EntityDecoratorBindings = {
|
|
22
|
+
/** Local names bound to MikroORM's `Entity` export, including aliased imports. */
|
|
23
|
+
named: Set<string>
|
|
24
|
+
/** Local names bound to a whole MikroORM module, for `@orm.Entity()`. */
|
|
25
|
+
namespaces: Set<string>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function inferScriptKind(filePath: string): ts.ScriptKind {
|
|
29
|
+
if (filePath.endsWith('.tsx')) return ts.ScriptKind.TSX
|
|
30
|
+
if (filePath.endsWith('.jsx')) return ts.ScriptKind.JSX
|
|
31
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.cjs') || filePath.endsWith('.mjs')) return ts.ScriptKind.JS
|
|
32
|
+
return ts.ScriptKind.TS
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isMikroOrmModule(specifier: string): boolean {
|
|
36
|
+
return specifier.startsWith(MIKRO_ORM_MODULE_PREFIX)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** `require('@mikro-orm/core')` as it survives into compiled CommonJS output. */
|
|
40
|
+
function readRequiredModuleSpecifier(expression: ts.Expression | undefined): string | undefined {
|
|
41
|
+
if (!expression || !ts.isCallExpression(expression)) return undefined
|
|
42
|
+
if (!ts.isIdentifier(expression.expression) || expression.expression.text !== 'require') return undefined
|
|
43
|
+
const [argument] = expression.arguments
|
|
44
|
+
return argument && ts.isStringLiteralLike(argument) ? argument.text : undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function collectEntityDecoratorBindings(sourceFile: ts.SourceFile): EntityDecoratorBindings {
|
|
48
|
+
const named = new Set<string>()
|
|
49
|
+
const namespaces = new Set<string>()
|
|
50
|
+
|
|
51
|
+
const addFromNamedBindings = (bindings: ts.NamedImportBindings | undefined): void => {
|
|
52
|
+
if (!bindings) return
|
|
53
|
+
if (ts.isNamespaceImport(bindings)) {
|
|
54
|
+
namespaces.add(bindings.name.text)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
for (const element of bindings.elements) {
|
|
58
|
+
if ((element.propertyName ?? element.name).text === 'Entity') named.add(element.name.text)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
sourceFile.forEachChild((node) => {
|
|
63
|
+
if (ts.isImportDeclaration(node)) {
|
|
64
|
+
if (!ts.isStringLiteralLike(node.moduleSpecifier) || !isMikroOrmModule(node.moduleSpecifier.text)) return
|
|
65
|
+
addFromNamedBindings(node.importClause?.namedBindings)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
if (!ts.isVariableStatement(node)) return
|
|
69
|
+
for (const declaration of node.declarationList.declarations) {
|
|
70
|
+
const specifier = readRequiredModuleSpecifier(declaration.initializer)
|
|
71
|
+
if (!specifier || !isMikroOrmModule(specifier)) continue
|
|
72
|
+
if (ts.isIdentifier(declaration.name)) {
|
|
73
|
+
namespaces.add(declaration.name.text)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (!ts.isObjectBindingPattern(declaration.name)) continue
|
|
77
|
+
for (const element of declaration.name.elements) {
|
|
78
|
+
const sourceName = element.propertyName ?? element.name
|
|
79
|
+
if (ts.isIdentifier(sourceName) && sourceName.text === 'Entity' && ts.isIdentifier(element.name)) {
|
|
80
|
+
named.add(element.name.text)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return { named, namespaces }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Unwraps the `(0, core_1.Entity)` form a CommonJS emit produces. */
|
|
90
|
+
function unwrapReference(expression: ts.Expression): ts.Expression {
|
|
91
|
+
if (ts.isParenthesizedExpression(expression)) return unwrapReference(expression.expression)
|
|
92
|
+
if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.CommaToken) {
|
|
93
|
+
return unwrapReference(expression.right)
|
|
94
|
+
}
|
|
95
|
+
return expression
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Accepts `Entity`, an aliased local name, and `<mikroOrmNamespace>.Entity`. */
|
|
99
|
+
function isEntityReference(expression: ts.Expression, bindings: EntityDecoratorBindings): boolean {
|
|
100
|
+
const reference = unwrapReference(expression)
|
|
101
|
+
if (ts.isIdentifier(reference)) return bindings.named.has(reference.text)
|
|
102
|
+
if (!ts.isPropertyAccessExpression(reference)) return false
|
|
103
|
+
if (reference.name.text !== 'Entity') return false
|
|
104
|
+
return ts.isIdentifier(reference.expression) && bindings.namespaces.has(reference.expression.text)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Accepts `@Entity`, `@Entity(...)` and the aliased or namespace-qualified forms. */
|
|
108
|
+
function isEntityDecorator(decorator: ts.Decorator, bindings: EntityDecoratorBindings): boolean {
|
|
109
|
+
const expression = ts.isCallExpression(decorator.expression)
|
|
110
|
+
? decorator.expression.expression
|
|
111
|
+
: decorator.expression
|
|
112
|
+
return isEntityReference(expression, bindings)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function hasEntityDecorator(node: ts.ClassDeclaration, bindings: EntityDecoratorBindings): boolean {
|
|
116
|
+
if (!ts.canHaveDecorators(node)) return false
|
|
117
|
+
return (ts.getDecorators(node) ?? []).some((decorator) => isEntityDecorator(decorator, bindings))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isExportedDeclaration(node: ts.Node): boolean {
|
|
121
|
+
return ts.canHaveModifiers(node)
|
|
122
|
+
? (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
|
123
|
+
: false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Names the file exports without redeclaring them: `export { X }`, the CommonJS
|
|
128
|
+
* `exports.X = X`, and the `__export(exports, { X: () => X })` table a bundler emits.
|
|
129
|
+
*/
|
|
130
|
+
function collectIndirectExportNames(sourceFile: ts.SourceFile): Set<string> {
|
|
131
|
+
const names = new Set<string>()
|
|
132
|
+
|
|
133
|
+
const addExportTable = (expression: ts.Expression): void => {
|
|
134
|
+
if (!ts.isObjectLiteralExpression(expression)) return
|
|
135
|
+
for (const property of expression.properties) {
|
|
136
|
+
if (!property.name) continue
|
|
137
|
+
const name = ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)
|
|
138
|
+
? property.name.text
|
|
139
|
+
: undefined
|
|
140
|
+
if (name) names.add(name)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
sourceFile.forEachChild((node) => {
|
|
145
|
+
if (ts.isExportDeclaration(node) && !node.moduleSpecifier) {
|
|
146
|
+
const clause = node.exportClause
|
|
147
|
+
if (clause && ts.isNamedExports(clause)) {
|
|
148
|
+
for (const element of clause.elements) names.add((element.propertyName ?? element.name).text)
|
|
149
|
+
}
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
if (!ts.isExpressionStatement(node)) return
|
|
153
|
+
const expression = node.expression
|
|
154
|
+
if (ts.isCallExpression(expression)) {
|
|
155
|
+
const callee = expression.expression
|
|
156
|
+
// esbuild emits `__export(ns, {...})`, SWC `_export(exports, {...})`.
|
|
157
|
+
if (ts.isIdentifier(callee) && /^_*export$/.test(callee.text) && expression.arguments.length >= 2) {
|
|
158
|
+
addExportTable(expression.arguments[1])
|
|
159
|
+
}
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
if (!ts.isBinaryExpression(expression) || expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return
|
|
163
|
+
const target = expression.left
|
|
164
|
+
if (!ts.isPropertyAccessExpression(target)) return
|
|
165
|
+
if (!ts.isIdentifier(target.expression) || target.expression.text !== 'exports') return
|
|
166
|
+
names.add(target.name.text)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
return names
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The compiled shape of a decorated class: the class becomes an expression assigned to a
|
|
174
|
+
* variable, and the decorators move into a helper call — `X = __decorateClass([...], X)`
|
|
175
|
+
* for esbuild, `X = __decorate([...], X)` for tsc, `_ts_decorate` for SWC. The helper's
|
|
176
|
+
* name is not matched, since it varies by toolchain; the shape is: a call whose first
|
|
177
|
+
* argument is an array literal and whose result is assigned back to the class binding.
|
|
178
|
+
*/
|
|
179
|
+
function collectCompiledDecoratedClassNames(
|
|
180
|
+
sourceFile: ts.SourceFile,
|
|
181
|
+
bindings: EntityDecoratorBindings,
|
|
182
|
+
): string[] {
|
|
183
|
+
const names: string[] = []
|
|
184
|
+
sourceFile.forEachChild((node) => {
|
|
185
|
+
if (!ts.isExpressionStatement(node)) return
|
|
186
|
+
const assignment = node.expression
|
|
187
|
+
if (!ts.isBinaryExpression(assignment) || assignment.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return
|
|
188
|
+
if (!ts.isIdentifier(assignment.left)) return
|
|
189
|
+
const call = assignment.right
|
|
190
|
+
if (!ts.isCallExpression(call) || call.arguments.length === 0) return
|
|
191
|
+
const [decorators] = call.arguments
|
|
192
|
+
if (!ts.isArrayLiteralExpression(decorators)) return
|
|
193
|
+
const target = call.arguments[call.arguments.length - 1]
|
|
194
|
+
if (!target || !ts.isIdentifier(target) || target.text !== assignment.left.text) return
|
|
195
|
+
const decoratesEntity = decorators.elements.some((element) =>
|
|
196
|
+
isEntityReference(ts.isCallExpression(element) ? element.expression : element, bindings))
|
|
197
|
+
if (decoratesEntity) names.push(assignment.left.text)
|
|
198
|
+
})
|
|
199
|
+
return names
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Class-expression bindings a compiled file declares, so `let X = class {}` is seen. */
|
|
203
|
+
function collectVariableClassNames(sourceFile: ts.SourceFile): Map<string, boolean> {
|
|
204
|
+
const declared = new Map<string, boolean>()
|
|
205
|
+
sourceFile.forEachChild((node) => {
|
|
206
|
+
if (!ts.isVariableStatement(node)) return
|
|
207
|
+
const exported = isExportedDeclaration(node)
|
|
208
|
+
for (const declaration of node.declarationList.declarations) {
|
|
209
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue
|
|
210
|
+
if (!ts.isClassExpression(declaration.initializer)) continue
|
|
211
|
+
declared.set(declaration.name.text, exported)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
return declared
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function parseEntityClassNames(filePath: string): string[] {
|
|
218
|
+
let source: string
|
|
219
|
+
try {
|
|
220
|
+
source = fs.readFileSync(filePath, 'utf8')
|
|
221
|
+
} catch {
|
|
222
|
+
// A warning path must never break generation.
|
|
223
|
+
return []
|
|
224
|
+
}
|
|
225
|
+
const sourceFile = ts.createSourceFile(
|
|
226
|
+
filePath,
|
|
227
|
+
source,
|
|
228
|
+
ts.ScriptTarget.ES2020,
|
|
229
|
+
true,
|
|
230
|
+
inferScriptKind(filePath),
|
|
231
|
+
)
|
|
232
|
+
const bindings = collectEntityDecoratorBindings(sourceFile)
|
|
233
|
+
const indirectExports = collectIndirectExportNames(sourceFile)
|
|
234
|
+
const classNames: string[] = []
|
|
235
|
+
const add = (className: string): void => {
|
|
236
|
+
if (!classNames.includes(className)) classNames.push(className)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
sourceFile.forEachChild((node) => {
|
|
240
|
+
if (!ts.isClassDeclaration(node) || !node.name) return
|
|
241
|
+
if (!hasEntityDecorator(node, bindings)) return
|
|
242
|
+
const className = node.name.text
|
|
243
|
+
if (!isExportedDeclaration(node) && !indirectExports.has(className)) return
|
|
244
|
+
add(className)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const variableClasses = collectVariableClassNames(sourceFile)
|
|
248
|
+
for (const className of collectCompiledDecoratedClassNames(sourceFile, bindings)) {
|
|
249
|
+
if (!variableClasses.has(className)) continue
|
|
250
|
+
if (!variableClasses.get(className) && !indirectExports.has(className)) continue
|
|
251
|
+
add(className)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return classNames
|
|
255
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { StructureKind, VariableDeclarationKind } from 'ts-morph'
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
findDuplicateEntityClassNames,
|
|
6
|
+
formatDuplicateEntityClassNamesWarning,
|
|
7
|
+
type EntityClassNameEntry,
|
|
8
|
+
} from '@open-mercato/shared/lib/db/duplicateEntityClassNames'
|
|
9
|
+
import type { ModuleEntry, PackageResolver } from '../resolver'
|
|
10
|
+
import { parseEntityClassNames } from './entity-class-declarations'
|
|
5
11
|
import { MODULE_CODE_EXTENSIONS, stripModuleCodeExtension } from './scanner'
|
|
6
12
|
import {
|
|
7
13
|
toVar,
|
|
@@ -50,6 +56,7 @@ export async function generateModuleEntities(options: ModuleEntitiesOptions): Pr
|
|
|
50
56
|
const mods = resolver.loadEnabledModules()
|
|
51
57
|
const imports: Array<{ name: string; moduleSpecifier: string }> = []
|
|
52
58
|
const entitySources: Array<{ importName: string; moduleId: string }> = []
|
|
59
|
+
const classNameEntries: EntityClassNameEntry[] = []
|
|
53
60
|
let n = 0
|
|
54
61
|
|
|
55
62
|
for (const entry of mods) {
|
|
@@ -94,6 +101,11 @@ export async function generateModuleEntities(options: ModuleEntitiesOptions): Pr
|
|
|
94
101
|
}
|
|
95
102
|
imports.push({ name: importName, moduleSpecifier: relImport })
|
|
96
103
|
entitySources.push({ importName, moduleId: modId })
|
|
104
|
+
|
|
105
|
+
const entityFilePath = resolveEntitySourceFile(resolver, entry, found, fromApp)
|
|
106
|
+
for (const className of parseEntityClassNames(entityFilePath)) {
|
|
107
|
+
classNameEntries.push({ className, moduleId: modId, sourcePath: entityFilePath })
|
|
108
|
+
}
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
const sourceFile = createGeneratedSourceFile('entities.generated.ts')
|
|
@@ -211,9 +223,50 @@ export async function generateModuleEntities(options: ModuleEntitiesOptions): Pr
|
|
|
211
223
|
quiet,
|
|
212
224
|
})
|
|
213
225
|
|
|
226
|
+
warnOnDuplicateEntityClassNames(classNameEntries)
|
|
227
|
+
|
|
214
228
|
return result
|
|
215
229
|
}
|
|
216
230
|
|
|
231
|
+
/**
|
|
232
|
+
* A duplicate entity class name across modules silently corrupts entity resolution at
|
|
233
|
+
* runtime and no build step otherwise catches it, so surface it here — where the
|
|
234
|
+
* colliding registry is produced. Reported as a warning; generation still succeeds.
|
|
235
|
+
*/
|
|
236
|
+
function warnOnDuplicateEntityClassNames(entries: readonly EntityClassNameEntry[]): void {
|
|
237
|
+
try {
|
|
238
|
+
const duplicates = findDuplicateEntityClassNames(entries)
|
|
239
|
+
if (duplicates.length === 0) return
|
|
240
|
+
console.warn(`\x1b[33m[Entities Warning]\x1b[0m ${formatDuplicateEntityClassNamesWarning(duplicates)}`)
|
|
241
|
+
} catch {
|
|
242
|
+
// This check is a diagnostic. It must never be the reason generation fails.
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The file to read class names out of, which is not always the file the runtime imports.
|
|
248
|
+
* A standalone install resolves a package module to its compiled `dist/modules` tree, and
|
|
249
|
+
* published packages ship `src/modules` alongside it — so prefer the shipped source, the
|
|
250
|
+
* way `eject` already does, and leave the runtime import pointing at `dist`. A package
|
|
251
|
+
* that ships compiled output only falls back to the resolved base, which the parser also
|
|
252
|
+
* understands. An app override wins over the package tree entirely: the registry imports
|
|
253
|
+
* the app file, so that is the file whose class names are registered.
|
|
254
|
+
*/
|
|
255
|
+
function resolveEntitySourceFile(
|
|
256
|
+
resolver: PackageResolver,
|
|
257
|
+
entry: ModuleEntry,
|
|
258
|
+
found: { base: string; file: string },
|
|
259
|
+
fromApp: boolean,
|
|
260
|
+
): string {
|
|
261
|
+
const resolved = path.join(found.base, found.file)
|
|
262
|
+
const from = entry.from
|
|
263
|
+
if (fromApp || !from || from === '@app') return resolved
|
|
264
|
+
const sourceBase = path.join(resolver.getPackageRoot(from), 'src', 'modules', entry.id, path.basename(found.base))
|
|
265
|
+
if (path.resolve(sourceBase) === path.resolve(found.base)) return resolved
|
|
266
|
+
const sourceFile = resolveConventionFile(sourceBase, stripModuleCodeExtension(found.file))
|
|
267
|
+
return sourceFile ?? resolved
|
|
268
|
+
}
|
|
269
|
+
|
|
217
270
|
function resolveConventionFile(baseDir: string, basename: string): string | null {
|
|
218
271
|
for (const extension of MODULE_CODE_EXTENSIONS) {
|
|
219
272
|
const candidate = path.join(baseDir, `${basename}${extension}`)
|