Skip to content

Commit 54b9ded

Browse files
ephyscorevo
andauthored
feat: add support for ESM config files (#987)
Co-authored-by: Tomer <[email protected]>
1 parent 36f41f1 commit 54b9ded

File tree

7 files changed

+146
-35
lines changed

7 files changed

+146
-35
lines changed

.babelrc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,17 @@
1010
],
1111
"ignore": [
1212
"src/assets"
13-
]
13+
],
14+
"overrides": [{
15+
"test": "./src/helpers/import-helper.js",
16+
"presets": [
17+
["@babel/env", {
18+
"targets": {
19+
"node": "10.0"
20+
},
21+
"shippedProposals": true,
22+
"exclude": ["proposal-dynamic-import"],
23+
}]
24+
],
25+
}]
1426
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pids
99

1010
# Editor files
1111
.vscode
12+
.idea
1213

1314
# Directory for instrumented libs generated by jscoverage/JSCover
1415
lib-cov

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"pg": "latest",
3939
"pg-hstore": "latest",
4040
"prettier": "^2.4.1",
41+
"semver": "^7.3.5",
4142
"sequelize": "^6.9.0",
4243
"sqlite3": "latest",
4344
"through2": "^4.0.2"

src/helpers/config-helper.js

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,41 @@ import _ from 'lodash';
55
import { promisify } from 'util';
66
import helpers from './index';
77
import getYArgs from '../core/yargs';
8+
import importHelper from './import-helper';
89

910
const args = getYArgs().argv;
1011

1112
const api = {
1213
config: undefined,
1314
rawConfig: undefined,
1415
error: undefined,
15-
init() {
16-
return Promise.resolve()
17-
.then(() => {
18-
let config;
19-
20-
if (args.url) {
21-
config = api.parseDbUrl(args.url);
22-
} else {
23-
try {
24-
config = require(api.getConfigFile());
25-
} catch (e) {
26-
api.error = e;
27-
}
28-
}
29-
return config;
30-
})
31-
.then((config) => {
32-
if (typeof config === 'object' || config === undefined) {
33-
return config;
34-
} else if (config.length === 1) {
35-
return promisify(config)();
36-
} else {
37-
return config();
38-
}
39-
})
40-
.then((config) => {
41-
api.rawConfig = config;
42-
})
43-
.then(() => {
44-
// Always return the full config api
45-
return api;
46-
});
16+
async init() {
17+
let config;
18+
19+
try {
20+
if (args.url) {
21+
config = api.parseDbUrl(args.url);
22+
} else {
23+
const module = await importHelper.importModule(api.getConfigFile());
24+
config = await module.default;
25+
}
26+
} catch (e) {
27+
api.error = e;
28+
}
29+
30+
if (typeof config === 'function') {
31+
// accepts callback parameter
32+
if (config.length === 1) {
33+
config = await promisify(config)();
34+
} else {
35+
// returns a promise.
36+
config = await config();
37+
}
38+
}
39+
40+
api.rawConfig = config;
41+
42+
return api;
4743
},
4844
getConfigFile() {
4945
if (args.config) {

src/helpers/dummy-file.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// this file is imported by import-helper to detect whether dynamic imports are supported.

src/helpers/import-helper.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
async function supportsDynamicImport() {
2+
try {
3+
// imports are cached.
4+
// no need to worry about perf here.
5+
// Don't remove .js: extension must be included for ESM imports!
6+
await import('./dummy-file.js');
7+
return true;
8+
} catch (e) {
9+
return false;
10+
}
11+
}
12+
13+
/**
14+
* Imports a JSON, CommonJS or ESM module
15+
* based on feature detection.
16+
*
17+
* @param modulePath path to the module to import
18+
* @returns {Promise<unknown>} the imported module.
19+
*/
20+
async function importModule(modulePath) {
21+
// JSON modules are still behind a flag. Fallback to require for now.
22+
// https://nodejs.org/api/esm.html#json-modules
23+
if (!modulePath.endsWith('.json') && (await supportsDynamicImport())) {
24+
return import(modulePath);
25+
}
26+
27+
// mimics what `import()` would return for
28+
// cjs modules.
29+
return { default: require(modulePath) };
30+
}
31+
32+
module.exports = {
33+
supportsDynamicImport,
34+
importModule,
35+
};

test/db/migrate.test.js

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const expect = require('expect.js');
33
const Support = require(__dirname + '/../support');
44
const helpers = require(__dirname + '/../support/helpers');
55
const gulp = require('gulp');
6+
const semver = require('semver');
67
const _ = require('lodash');
78

89
[
@@ -25,7 +26,7 @@ const _ = require('lodash');
2526
let configPath = 'config/';
2627
let migrationFile = options.migrationFile || 'createPerson';
2728
const config = _.assign({}, helpers.getTestConfig(), options.config);
28-
let configContent = JSON.stringify(config);
29+
let configContent = JSON.stringify(config, null, 2);
2930

3031
if (!migrationFile.match(/\.(cjs|ts)$/)) {
3132
migrationFile = migrationFile + '.js';
@@ -70,7 +71,11 @@ const _ = require('lodash');
7071
it('creates a SequelizeMeta table', function (done) {
7172
const self = this;
7273

73-
prepare(() => {
74+
prepare((e) => {
75+
if (e) {
76+
return done(e);
77+
}
78+
7479
helpers.readTables(self.sequelize, (tables) => {
7580
expect(tables).to.have.length(2);
7681
expect(tables).to.contain('SequelizeMeta');
@@ -293,6 +298,58 @@ describe(Support.getTestDialectTeaser('db:migrate'), () => {
293298
});
294299
});
295300

301+
describeOnlyForESM(Support.getTestDialectTeaser('db:migrate'), () => {
302+
describe('with config.mjs', () => {
303+
const prepare = function (callback) {
304+
const config = helpers.getTestConfig();
305+
const configContent = 'export default ' + JSON.stringify(config);
306+
let result = '';
307+
308+
return gulp
309+
.src(Support.resolveSupportPath('tmp'))
310+
.pipe(helpers.clearDirectory())
311+
.pipe(helpers.runCli('init'))
312+
.pipe(helpers.removeFile('config/config.json'))
313+
.pipe(helpers.copyMigration('createPerson.js'))
314+
.pipe(helpers.overwriteFile(configContent, 'config/config.mjs'))
315+
.pipe(helpers.runCli('db:migrate --config config/config.mjs'))
316+
.on('error', (e) => {
317+
callback(e);
318+
})
319+
.on('data', (data) => {
320+
result += data.toString();
321+
})
322+
.on('end', () => {
323+
callback(null, result);
324+
});
325+
};
326+
327+
it('creates a SequelizeMeta table', function (done) {
328+
prepare((e) => {
329+
if (e) {
330+
return done(e);
331+
}
332+
333+
helpers.readTables(this.sequelize, (tables) => {
334+
expect(tables).to.have.length(2);
335+
expect(tables).to.contain('SequelizeMeta');
336+
done();
337+
});
338+
});
339+
});
340+
341+
it('creates the respective table', function (done) {
342+
prepare(() => {
343+
helpers.readTables(this.sequelize, (tables) => {
344+
expect(tables).to.have.length(2);
345+
expect(tables).to.contain('Person');
346+
done();
347+
});
348+
});
349+
});
350+
});
351+
});
352+
296353
describe(Support.getTestDialectTeaser('db:migrate'), () => {
297354
describe('with config.json and url option', () => {
298355
const prepare = function (callback) {
@@ -525,3 +582,11 @@ describe(Support.getTestDialectTeaser('db:migrate'), () => {
525582
});
526583
});
527584
});
585+
586+
function describeOnlyForESM(title, fn) {
587+
if (semver.satisfies(process.version, '^12.20.0 || ^14.13.1 || >=16.0.0')) {
588+
describe(title, fn);
589+
} else {
590+
describe.skip(title, fn);
591+
}
592+
}

0 commit comments

Comments
 (0)