Skip to content

Commit ffce859

Browse files
committed
fix(core): Guard bare-string translation target in registerCustomEntityFields
1 parent 006adf7 commit ffce859

2 files changed

Lines changed: 69 additions & 15 deletions

File tree

packages/core/src/bootstrap.spec.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ import { CustomFieldConfig } from './config/custom-field/custom-field-types';
66
import { RuntimeVendureConfig } from './config/vendure-config';
77
// Importing the core entities registers their `customFields` embedded columns in the
88
// TypeORM metadata, which is how getEntityNamesWithCustomFields() detects the entities
9-
// that support custom fields.
10-
import { coreEntitiesMap } from './entity/entities';
9+
// that support custom fields. Imported for its side effect only.
10+
import './entity/entities';
11+
import { registerCustomEntityFields } from './entity/register-custom-entity-fields';
1112
import { VendurePlugin } from './plugin/vendure-plugin';
1213

13-
void coreEntitiesMap;
14-
1514
/**
1615
* Registers a `translations` relation (and a matching `customFields` embedded on the
1716
* translation target) directly in the TypeORM metadata, so we can exercise the different
@@ -132,3 +131,52 @@ describe('runPluginConfigurations()', () => {
132131
expect(config.customFields.Product).toContainEqual({ name: 'fromPlugin', type: 'string' });
133132
});
134133
});
134+
135+
describe('registerCustomEntityFields()', () => {
136+
// OSS-408 / Michael's review: the translatable branch resolved the translation entity via
137+
// `(translationsMetadata.type as Function)()`, which threw `type is not a function` for a
138+
// bare-string relation target — the same crash class fixed in getEntityNamesWithCustomFields().
139+
// It now reuses getRelationTargetName(), so a translatable entity with a string translations
140+
// target and real custom fields registers without aborting bootstrap.
141+
it('does not throw when a translatable entity has a bare-string translations relation target', () => {
142+
const storage = getMetadataArgsStorage();
143+
class Oss408RegBase {}
144+
class Oss408RegBaseTranslation {}
145+
// Base entity declares a customFields embedded…
146+
storage.embeddeds.push({
147+
target: Oss408RegBase,
148+
propertyName: 'customFields',
149+
prefix: undefined,
150+
type: () => Oss408RegBase,
151+
} as any);
152+
// …a `translations` relation whose target is a BARE STRING (the crash case)…
153+
storage.relations.push({
154+
target: Oss408RegBase,
155+
propertyName: 'translations',
156+
relationType: 'one-to-many',
157+
type: 'Oss408RegBaseTranslation',
158+
isLazy: false,
159+
options: {},
160+
} as any);
161+
// …and the translation entity also declares a customFields embedded.
162+
storage.embeddeds.push({
163+
target: Oss408RegBaseTranslation,
164+
propertyName: 'customFields',
165+
prefix: undefined,
166+
type: () => Oss408RegBaseTranslation,
167+
} as any);
168+
169+
const config = {
170+
customFields: { Oss408RegBase: [{ name: 'foo', type: 'string' }] },
171+
dbConnectionOptions: { type: 'sqljs' },
172+
} as unknown as RuntimeVendureConfig;
173+
174+
try {
175+
expect(() => registerCustomEntityFields(config)).not.toThrow();
176+
} finally {
177+
storage.embeddeds.pop();
178+
storage.relations.pop();
179+
storage.embeddeds.pop();
180+
}
181+
});
182+
});

packages/core/src/entity/register-custom-entity-fields.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -325,17 +325,23 @@ export function registerCustomEntityFields(config: VendureConfig) {
325325
if (translationsMetadata) {
326326
// This entity is translatable, which means that we should
327327
// also register any localized custom fields on the related
328-
// EntityTranslation entity.
329-
const translationType: Function = (translationsMetadata.type as Function)();
330-
const customFieldsTranslationsMetadata = getCustomFieldsMetadata(translationType);
331-
const customFieldsTranslationClass = customFieldsTranslationsMetadata.type();
332-
if (customFieldsTranslationClass && typeof customFieldsTranslationClass !== 'string') {
333-
registerCustomFieldsForEntity(
334-
config,
335-
entityName,
336-
customFieldsTranslationClass as any,
337-
true,
338-
);
328+
// EntityTranslation entity. Resolve the target via the shared
329+
// helper so a bare-string or closure-returning-string relation
330+
// target (both legal, used to break circular imports) does not
331+
// throw `type is not a function` here — the same crash fixed in
332+
// getEntityNamesWithCustomFields().
333+
const translationEntityName = getRelationTargetName(translationsMetadata.type);
334+
if (translationEntityName != null) {
335+
const customFieldsTranslationsMetadata = getCustomFieldsMetadata(translationEntityName);
336+
const customFieldsTranslationClass = customFieldsTranslationsMetadata.type();
337+
if (customFieldsTranslationClass && typeof customFieldsTranslationClass !== 'string') {
338+
registerCustomFieldsForEntity(
339+
config,
340+
entityName,
341+
customFieldsTranslationClass as any,
342+
true,
343+
);
344+
}
339345
}
340346
} else {
341347
assertLocaleFieldsNotSpecified(config, entityName);

0 commit comments

Comments
 (0)