diff --git a/packages/macros/src/macros-config.ts b/packages/macros/src/macros-config.ts index f153b5138..1fa291429 100644 --- a/packages/macros/src/macros-config.ts +++ b/packages/macros/src/macros-config.ts @@ -193,6 +193,12 @@ export default class MacrosConfig { ); } + if (!isSerializable(config)) { + throw new Error( + `[Embroider:MacrosConfig] the given config from '${fromPath}' for packageName '${packageName}' is not JSON serializable.` + ); + } + let targetPackage = this.resolvePackage(fromPath, packageName); let peers = getOrCreate(this.configs, targetPackage.root, () => []); peers.push(config); @@ -421,3 +427,40 @@ function defaultMergerFor(pkgRoot: string) { return Object.assign({}, ...ownConfigs, ...otherConfigs); }; } + +function isSerializable(obj: object): boolean { + if (isScalar(obj)) { + return true; + } + + if (Array.isArray(obj)) { + return !obj.some((arrayItem: any) => !isSerializable(arrayItem)); + } + + if (isPlainObject(obj)) { + for (let property in obj) { + let value = obj[property] as any; + if (!isSerializable(value)) { + return false; + } + } + + return true; + } + + return false; +} + +function isScalar(val: any): boolean { + return ( + typeof val === 'undefined' || + typeof val === 'string' || + typeof val === 'boolean' || + typeof val === 'number' || + val === null + ); +} + +function isPlainObject(obj: any): obj is Record { + return typeof obj === 'object' && obj.constructor === Object && obj.toString() === '[object Object]'; +} diff --git a/packages/macros/tests/babel/set-config.test.ts b/packages/macros/tests/babel/set-config.test.ts new file mode 100644 index 000000000..3856a6583 --- /dev/null +++ b/packages/macros/tests/babel/set-config.test.ts @@ -0,0 +1,42 @@ +import { MacrosConfig } from '../../src/node'; +import { allBabelVersions } from './helpers'; + +describe(`setConfig`, function () { + allBabelVersions(function () { + test('works with empty config', () => { + let config = {}; + + let macroConfig = MacrosConfig.for({}, __dirname); + macroConfig.setConfig(__filename, 'qunit', config); + }); + + test('works with POJO config', () => { + let config = { + str: 'yes', + num: 10, + bool: true, + undef: undefined, + nil: null, + arr: ['yes', 10, true, undefined, null, { inArr: true }], + obj: { nested: true }, + }; + + let macroConfig = MacrosConfig.for({}, __dirname); + macroConfig.setConfig(__filename, 'qunit', config); + }); + + test('throws for non-serializable config', () => { + let config = { + obj: { + regex: /regex/, + }, + }; + + let macroConfig = MacrosConfig.for({}, __dirname); + + expect(() => macroConfig.setConfig(__filename, 'qunit', config)).toThrow( + `[Embroider:MacrosConfig] the given config from '${__filename}' for packageName 'qunit' is not JSON serializable.` + ); + }); + }); +});