Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESM Support #351

Merged
merged 6 commits into from Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion lib/env/config.js
@@ -1,6 +1,8 @@
const fs = require("fs-extra");
const path = require("path");
const url = require("url");
const { get } = require("lodash");
const moduleLoader = require('../utils/module-loader');

const DEFAULT_CONFIG_FILE_NAME = "migrate-mongo-config.js";

Expand Down Expand Up @@ -60,6 +62,14 @@ module.exports = {
return customConfigContent;
}
const configPath = getConfigPath();
return Promise.resolve(require(configPath)); // eslint-disable-line
try {
return Promise.resolve(moduleLoader.require(configPath));
nathan-knight marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) {
if (e.code === 'ERR_REQUIRE_ESM') {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nathan-knight
I observe instability here (potentially depending on the Node version used ???)
This code used to work for me, I used it some weeks ago but now for the same usage I no longer have this error code thrown by the Node's require so it seems unstable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error only occurs on newer versions of Node that are configured to use ES modules

const loadedImport = await moduleLoader.import(url.pathToFileURL(configPath));
return loadedImport.default
}
throw e;
}
}
};
14 changes: 12 additions & 2 deletions lib/env/migrationsDir.js
@@ -1,7 +1,9 @@
const fs = require("fs-extra");
const path = require("path");
const url = require("url");
const crypto = require("crypto");
const config = require("./config");
const moduleLoader = require('../utils/module-loader');

const DEFAULT_MIGRATIONS_DIR_NAME = "migrations";
const DEFAULT_MIGRATION_EXT = ".js";
Expand Down Expand Up @@ -94,11 +96,19 @@ module.exports = {

async loadMigration(fileName) {
const migrationsDir = await resolveMigrationsDirPath();
return require(path.join(migrationsDir, fileName)); // eslint-disable-line
const migrationPath = path.join(migrationsDir, fileName);
try {
return moduleLoader.require(migrationPath);
} catch (e) {
if (e.code === 'ERR_REQUIRE_ESM') {
return moduleLoader.import(url.pathToFileURL(migrationPath));
}
throw e;
}
},

async loadFileHash(fileName) {
const migrationsDir = await resolveMigrationsDirPath();
const migrationsDir = await resolveMigrationsDirPath();
const filePath = path.join(migrationsDir, fileName)
const hash = crypto.createHash('sha256');
const input = await fs.readFile(filePath);
Expand Down
10 changes: 10 additions & 0 deletions lib/utils/module-loader.js
@@ -0,0 +1,10 @@
module.exports = {
require(requirePath) {
return require(requirePath); // eslint-disable-line
},

/* istanbul ignore next */
import(importPath) {
return import(importPath); // eslint-disable-line
},
};
32 changes: 25 additions & 7 deletions test/env/config.test.js
Expand Up @@ -7,16 +7,27 @@ const path = require("path");
describe("config", () => {
let config; // module under test
let fs; // mocked dependencies
let moduleLoader;

function mockFs() {
return {
stat: sinon.stub()
};
}

function mockModuleLoader() {
return {
import: sinon.stub(),
};
}

beforeEach(() => {
fs = mockFs();
config = proxyquire("../../lib/env/config", { "fs-extra": fs });
moduleLoader = mockModuleLoader();
config = proxyquire("../../lib/env/config", {
"fs-extra": fs,
"../utils/module-loader": moduleLoader
});
});

describe("shouldExist()", () => {
Expand Down Expand Up @@ -98,19 +109,17 @@ describe("config", () => {
await config.read();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.match(new RegExp(`Cannot find module '${configPath}'`));
expect(err.message).to.have.string(`Cannot find module '${configPath}'`);
}
});

it("should be possible to read a custom, absolute config file path", async () => {
global.options = { file: "/some/absoluete/path/to/a-config-file.js" };
global.options = { file: "/some/absolute/path/to/a-config-file.js" };
try {
await config.read();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.match(
new RegExp(`Cannot find module '${global.options.file}'`)
);
expect(err.message).to.have.string(`Cannot find module '${global.options.file}'`);
}
});

Expand All @@ -121,8 +130,17 @@ describe("config", () => {
await config.read();
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.match(new RegExp(`Cannot find module '${configPath}'`));
expect(err.message).to.have.string(`Cannot find module '${configPath}'`);
}
});

it("should fall back to using 'import' if Node requires the use of ESM", async () => {
const error = new Error('ESM required');
error.code = 'ERR_REQUIRE_ESM';
moduleLoader.require = sinon.stub().throws(error);
moduleLoader.import.returns({});
await config.read();
expect(moduleLoader.import.called).to.equal(true);
});
});
});
21 changes: 19 additions & 2 deletions test/env/migrationsDir.test.js
Expand Up @@ -8,6 +8,7 @@ describe("migrationsDir", () => {
let migrationsDir;
let fs;
let config;
let moduleLoader;

function mockFs() {
return {
Expand All @@ -26,12 +27,20 @@ describe("migrationsDir", () => {
};
}

function mockModuleLoader() {
return {
import: sinon.stub(),
};
}

beforeEach(() => {
fs = mockFs();
config = mockConfig();
moduleLoader = mockModuleLoader();
migrationsDir = proxyquire("../../lib/env/migrationsDir", {
"fs-extra": fs,
"./config": config
"./config": config,
"../utils/module-loader": moduleLoader
});
});

Expand Down Expand Up @@ -165,9 +174,17 @@ describe("migrationsDir", () => {
await migrationsDir.loadMigration("someFile.js");
expect.fail("Error was not thrown");
} catch (err) {
expect(err.message).to.match(new RegExp(`Cannot find module '${pathToMigration}'`));
expect(err.message).to.have.string(`Cannot find module '${pathToMigration}'`);
}
});

it("should fall back to using 'import' if Node requires the use of ESM", async () => {
const error = new Error('ESM required');
error.code = 'ERR_REQUIRE_ESM';
moduleLoader.require = sinon.stub().throws(error);
await migrationsDir.loadMigration("someFile.js");
expect(moduleLoader.import.called).to.equal(true);
});
});

describe("resolveMigrationFileExtension()", () => {
Expand Down