Skip to content

Commit 151c286

Browse files
authored
Convert package to ESM (#113)
Converts this package to an ESM module. Kudos to [this guide](https://tsmx.net/convert-existing-nodejs-project-from-commonjs-to-esm/) and this issue: TypeStrong/ts-node#1997 In our organization, this is only possible for this package because it is not used anywhere else, and we rely on CommonJS extensively. Changes in detail: - Update `package.json` for ESM compatibility - Replace all `require()` with `import` statements - Add `.js` file extension to all file imports in TypeScript source files - Update TypeScript, ESLint, and Jest configs for ESM compatibility and recommended best practices - TypeScript (`tsconfig.json`) - Update the `module`/`moduleResolution` fields to `Node16`. - I chose `Node16` over `NodeNext`, as the behavior of `NodeNext` may change in the future. See [here](https://www.typescriptlang.org/docs/handbook/modules/reference.html#node16-nodenext) for details. - ESLint (`.eslintrc.cjs`) - `parseOptions.sourceType = 'module'` - Jest (`jest.config.cjs`) - Update the `moduleNameMapper` to strip the `.js` from local file imports. See [here](https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/) for details. - Replace `ts-node` with `tsx`, which has better ESM compatibility. - Specifically, the former does not work with Node v20 at the moment: TypeStrong/ts-node#1997 - Update `jest-it-up` - This is so that we can use its `--config` option (thank you @PeterYinusa! rbardini/jest-it-up#12) to target our new `.cjs` config file.
1 parent 66c90b4 commit 151c286

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+577
-393
lines changed

.eslintrc.js renamed to .eslintrc.cjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ module.exports = {
33

44
extends: ['@metamask/eslint-config', '@metamask/eslint-config-nodejs'],
55

6+
parserOptions: {
7+
sourceType: 'module',
8+
},
9+
610
rules: {
711
// This makes integration tests easier to read by allowing setup code and
812
// assertions to be grouped together better
File renamed without changes.

bin/create-release-branch.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/usr/bin/env node
22

3-
/* eslint-disable-next-line import/no-unassigned-import,import/no-unresolved */
4-
require('../dist/cli');
3+
// eslint-disable-next-line import/no-unassigned-import,import/no-unresolved
4+
import '../dist/cli';

jest.config.js renamed to jest.config.cjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* https://jestjs.io/docs/configuration
44
*/
55

6+
// This file needs to be .cjs for compatibility with jest-it-up.
67
module.exports = {
78
// All imported modules in your tests should be mocked automatically
89
// automock: false,
@@ -87,7 +88,11 @@ module.exports = {
8788
// ],
8889

8990
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
90-
// moduleNameMapper: {},
91+
moduleNameMapper: {
92+
// Strip the file extension from imports, so that e.g. `import { foo } from './foo.js'`
93+
// becomes `import { foo } from './foo'`. This is for compatibility with ESM.
94+
'^(\\.\\.?\\/.+)\\.js$': '$1',
95+
},
9196

9297
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
9398
// modulePathIgnorePatterns: [],

package.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"type": "git",
77
"url": "https://github.com/MetaMask/create-release-branch.git"
88
},
9+
"type": "module",
10+
"exports": null,
911
"bin": "bin/create-release-branch.js",
1012
"files": [
1113
"bin/",
@@ -19,12 +21,12 @@
1921
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
2022
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
2123
"prepack": "./scripts/prepack.sh",
22-
"test": "jest && jest-it-up",
24+
"test": "jest && jest-it-up --config jest.config.cjs",
2325
"test:watch": "jest --watch"
2426
},
2527
"dependencies": {
2628
"@metamask/action-utils": "^1.0.0",
27-
"@metamask/utils": "^8.1.0",
29+
"@metamask/utils": "^8.2.1",
2830
"debug": "^4.3.4",
2931
"execa": "^5.1.1",
3032
"pony-cause": "^2.1.9",
@@ -58,15 +60,15 @@
5860
"eslint-plugin-node": "^11.1.0",
5961
"eslint-plugin-prettier": "^4.2.1",
6062
"jest": "^29.5.0",
61-
"jest-it-up": "^2.0.2",
63+
"jest-it-up": "^3.0.0",
6264
"jest-when": "^3.5.2",
6365
"nanoid": "^3.3.4",
6466
"prettier": "^2.2.1",
6567
"prettier-plugin-packagejson": "^2.3.0",
6668
"rimraf": "^4.0.5",
6769
"stdio-mock": "^1.2.0",
6870
"ts-jest": "^29.1.0",
69-
"ts-node": "^10.7.0",
71+
"tsx": "^4.6.1",
7072
"typescript": "~5.1.6"
7173
},
7274
"packageManager": "[email protected]",
@@ -79,7 +81,8 @@
7981
},
8082
"lavamoat": {
8183
"allowScripts": {
82-
"@lavamoat/preinstall-always-fail": false
84+
"@lavamoat/preinstall-always-fail": false,
85+
"tsx>esbuild": false
8386
}
8487
}
8588
}

src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { main } from './main';
1+
import { main } from './main.js';
22

33
/**
44
* The entrypoint to this tool.

src/editor.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { when } from 'jest-when';
2-
import { determineEditor } from './editor';
3-
import * as envModule from './env';
4-
import * as miscUtils from './misc-utils';
2+
import { determineEditor } from './editor.js';
3+
import * as envModule from './env.js';
4+
import * as miscUtils from './misc-utils.js';
55

66
jest.mock('./env');
77
jest.mock('./misc-utils');

src/editor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { getEnvironmentVariables } from './env';
2-
import { debug, resolveExecutable } from './misc-utils';
1+
import { getEnvironmentVariables } from './env.js';
2+
import { debug, resolveExecutable } from './misc-utils.js';
33

44
/**
55
* Information about the editor present on the user's computer.

src/env.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getEnvironmentVariables } from './env';
1+
import { getEnvironmentVariables } from './env.js';
22

33
describe('env', () => {
44
describe('getEnvironmentVariables', () => {

src/fs.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'path';
33
import { rimraf } from 'rimraf';
44
import { when } from 'jest-when';
55
import * as actionUtils from '@metamask/action-utils';
6-
import { withSandbox } from '../tests/helpers';
6+
import { withSandbox } from '../tests/helpers.js';
77
import {
88
readFile,
99
writeFile,
@@ -12,7 +12,7 @@ import {
1212
fileExists,
1313
ensureDirectoryPathExists,
1414
removeFile,
15-
} from './fs';
15+
} from './fs.js';
1616

1717
jest.mock('@metamask/action-utils');
1818

src/fs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
readJsonObjectFile as underlyingReadJsonObjectFile,
44
writeJsonFile as underlyingWriteJsonFile,
55
} from '@metamask/action-utils';
6-
import { wrapError, isErrorWithCode } from './misc-utils';
6+
import { wrapError, isErrorWithCode } from './misc-utils.js';
77

88
/**
99
* Represents a writeable stream, such as that represented by `process.stdout`

src/functional.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { withMonorepoProjectEnvironment } from '../tests/functional/helpers/with';
2-
import { buildChangelog } from '../tests/helpers';
1+
import { withMonorepoProjectEnvironment } from '../tests/functional/helpers/with.js';
2+
import { buildChangelog } from '../tests/helpers.js';
33

44
jest.setTimeout(10_000);
55

src/initial-parameters.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import {
55
buildMockProject,
66
buildMockPackage,
77
createNoopWriteStream,
8-
} from '../tests/unit/helpers';
9-
import { determineInitialParameters } from './initial-parameters';
10-
import * as commandLineArgumentsModule from './command-line-arguments';
11-
import * as envModule from './env';
12-
import * as projectModule from './project';
8+
} from '../tests/unit/helpers.js';
9+
import { determineInitialParameters } from './initial-parameters.js';
10+
import * as commandLineArgumentsModule from './command-line-arguments.js';
11+
import * as envModule from './env.js';
12+
import * as projectModule from './project.js';
1313

1414
jest.mock('./command-line-arguments');
1515
jest.mock('./env');

src/initial-parameters.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import os from 'os';
22
import path from 'path';
3-
import { readCommandLineArguments } from './command-line-arguments';
4-
import { WriteStreamLike } from './fs';
5-
import { readProject, Project } from './project';
3+
import { readCommandLineArguments } from './command-line-arguments.js';
4+
import { WriteStreamLike } from './fs.js';
5+
import { readProject, Project } from './project.js';
66

77
/**
88
* The type of release being created as determined by the parent release.

src/main.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import fs from 'fs';
2-
import { buildMockProject } from '../tests/unit/helpers';
3-
import { main } from './main';
4-
import * as initialParametersModule from './initial-parameters';
5-
import * as monorepoWorkflowOperations from './monorepo-workflow-operations';
2+
import { buildMockProject } from '../tests/unit/helpers.js';
3+
import { main } from './main.js';
4+
import * as initialParametersModule from './initial-parameters.js';
5+
import * as monorepoWorkflowOperations from './monorepo-workflow-operations.js';
66

77
jest.mock('./initial-parameters');
88
jest.mock('./monorepo-workflow-operations');

src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { WriteStream } from 'fs';
2-
import { determineInitialParameters } from './initial-parameters';
3-
import { followMonorepoWorkflow } from './monorepo-workflow-operations';
2+
import { determineInitialParameters } from './initial-parameters.js';
3+
import { followMonorepoWorkflow } from './monorepo-workflow-operations.js';
44

55
/**
66
* The main function for this tool. Designed to not access `process.argv`,

src/misc-utils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
runCommand,
1010
getStdoutFromCommand,
1111
getLinesFromCommand,
12-
} from './misc-utils';
12+
} from './misc-utils.js';
1313

1414
jest.mock('which');
1515
jest.mock('execa');

src/monorepo-workflow-operations.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import { when } from 'jest-when';
44
import { MockWritable } from 'stdio-mock';
55
import { withSandbox, Sandbox, isErrorWithCode } from '../tests/helpers';
66
import { buildMockProject, Require } from '../tests/unit/helpers';
7-
import { followMonorepoWorkflow } from './monorepo-workflow-operations';
8-
import * as editorModule from './editor';
9-
import type { Editor } from './editor';
10-
import * as releaseSpecificationModule from './release-specification';
11-
import type { ReleaseSpecification } from './release-specification';
12-
import * as releasePlanModule from './release-plan';
13-
import type { ReleasePlan } from './release-plan';
14-
import * as repoModule from './repo';
15-
import * as workflowOperations from './workflow-operations';
7+
import { followMonorepoWorkflow } from './monorepo-workflow-operations.js';
8+
import * as editorModule from './editor.js';
9+
import type { Editor } from './editor.js';
10+
import * as releaseSpecificationModule from './release-specification.js';
11+
import type { ReleaseSpecification } from './release-specification.js';
12+
import * as releasePlanModule from './release-plan.js';
13+
import type { ReleasePlan } from './release-plan.js';
14+
import * as repoModule from './repo.js';
15+
import * as workflowOperations from './workflow-operations.js';
1616

1717
jest.mock('./editor');
1818
jest.mock('./release-plan');

src/monorepo-workflow-operations.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@ import {
55
fileExists,
66
removeFile,
77
writeFile,
8-
} from './fs';
9-
import { determineEditor } from './editor';
10-
import { ReleaseType } from './initial-parameters';
8+
} from './fs.js';
9+
import { determineEditor } from './editor.js';
10+
import { ReleaseType } from './initial-parameters.js';
1111
import {
1212
Project,
1313
updateChangelogsForChangedPackages,
1414
restoreChangelogsForSkippedPackages,
15-
} from './project';
16-
import { planRelease, executeReleasePlan } from './release-plan';
17-
import { commitAllChanges } from './repo';
15+
} from './project.js';
16+
import { planRelease, executeReleasePlan } from './release-plan.js';
17+
import { commitAllChanges } from './repo.js';
1818
import {
1919
generateReleaseSpecificationTemplateForMonorepo,
2020
waitForUserToEditReleaseSpecification,
2121
validateReleaseSpecification,
22-
} from './release-specification';
23-
import { createReleaseBranch } from './workflow-operations';
22+
} from './release-specification.js';
23+
import { createReleaseBranch } from './workflow-operations.js';
2424

2525
/**
2626
* For a monorepo, the process works like this:

src/package-manifest.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import fs from 'fs';
22
import path from 'path';
33
import { SemVer } from 'semver';
4-
import { withSandbox } from '../tests/helpers';
5-
import { readPackageManifest } from './package-manifest';
4+
import { withSandbox } from '../tests/helpers.js';
5+
import { readPackageManifest } from './package-manifest.js';
66

77
describe('package-manifest', () => {
88
describe('readPackageManifest', () => {

src/package-manifest.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import {
44
ManifestDependencyFieldNames as PackageManifestDependenciesFieldNames,
55
} from '@metamask/action-utils';
66
import { isPlainObject } from '@metamask/utils';
7-
import { readJsonObjectFile } from './fs';
8-
import { isTruthyString } from './misc-utils';
9-
import { semver, SemVer } from './semver';
7+
import { readJsonObjectFile } from './fs.js';
8+
import { isTruthyString } from './misc-utils.js';
9+
import { semver, SemVer } from './semver.js';
1010

1111
export { PackageManifestFieldNames, PackageManifestDependenciesFieldNames };
1212

src/package.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import {
1010
buildMockProject,
1111
buildMockManifest,
1212
createNoopWriteStream,
13-
} from '../tests/unit/helpers';
13+
} from '../tests/unit/helpers.js';
1414
import {
1515
readMonorepoRootPackage,
1616
readMonorepoWorkspacePackage,
1717
updatePackage,
1818
updatePackageChangelog,
19-
} from './package';
20-
import * as fsModule from './fs';
21-
import * as packageManifestModule from './package-manifest';
22-
import * as repoModule from './repo';
19+
} from './package.js';
20+
import * as fsModule from './fs.js';
21+
import * as packageManifestModule from './package-manifest.js';
22+
import * as repoModule from './repo.js';
2323

2424
jest.mock('./package-manifest');
2525
jest.mock('./repo');

src/package.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ import fs, { WriteStream } from 'fs';
22
import path from 'path';
33
import { format } from 'util';
44
import { parseChangelog, updateChangelog } from '@metamask/auto-changelog';
5-
import { WriteStreamLike, readFile, writeFile, writeJsonFile } from './fs';
6-
import { isErrorWithCode } from './misc-utils';
5+
import { WriteStreamLike, readFile, writeFile, writeJsonFile } from './fs.js';
6+
import { isErrorWithCode } from './misc-utils.js';
77
import {
88
readPackageManifest,
99
UnvalidatedPackageManifest,
1010
ValidatedPackageManifest,
11-
} from './package-manifest';
12-
import { Project } from './project';
13-
import { PackageReleasePlan } from './release-plan';
14-
import { hasChangesInDirectorySinceGitTag } from './repo';
15-
import { SemVer } from './semver';
11+
} from './package-manifest.js';
12+
import { Project } from './project.js';
13+
import { PackageReleasePlan } from './release-plan.js';
14+
import { hasChangesInDirectorySinceGitTag } from './repo.js';
15+
import { SemVer } from './semver.js';
1616

1717
const MANIFEST_FILE_NAME = 'package.json';
1818
const CHANGELOG_FILE_NAME = 'CHANGELOG.md';

src/project.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import {
1313
readProject,
1414
restoreChangelogsForSkippedPackages,
1515
updateChangelogsForChangedPackages,
16-
} from './project';
17-
import * as packageModule from './package';
18-
import * as repoModule from './repo';
19-
import * as fs from './fs';
20-
import { IncrementableVersionParts } from './release-specification';
16+
} from './project.js';
17+
import * as packageModule from './package.js';
18+
import * as repoModule from './repo.js';
19+
import * as fs from './fs.js';
20+
import { IncrementableVersionParts } from './release-specification.js';
2121

2222
jest.mock('./package');
2323
jest.mock('./repo');

src/project.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { WriteStream } from 'fs';
22
import { resolve } from 'path';
33
import { getWorkspaceLocations } from '@metamask/action-utils';
4-
import { WriteStreamLike, fileExists } from './fs';
4+
import { WriteStreamLike, fileExists } from './fs.js';
55
import {
66
Package,
77
readMonorepoRootPackage,
88
readMonorepoWorkspacePackage,
99
updatePackageChangelog,
10-
} from './package';
11-
import { getRepositoryHttpsUrl, getTagNames, restoreFiles } from './repo';
12-
import { SemVer } from './semver';
13-
import { PackageManifestFieldNames } from './package-manifest';
14-
import { ReleaseSpecification } from './release-specification';
10+
} from './package.js';
11+
import { getRepositoryHttpsUrl, getTagNames, restoreFiles } from './repo.js';
12+
import { SemVer } from './semver.js';
13+
import { PackageManifestFieldNames } from './package-manifest.js';
14+
import { ReleaseSpecification } from './release-specification.js';
1515

1616
/**
1717
* The release version of the root package of a monorepo extracted from its

src/release-plan.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import fs from 'fs';
22
import { SemVer } from 'semver';
3-
import { buildMockProject, buildMockPackage } from '../tests/unit/helpers';
4-
import { planRelease, executeReleasePlan } from './release-plan';
5-
import { IncrementableVersionParts } from './release-specification';
6-
import * as packageUtils from './package';
3+
import { buildMockProject, buildMockPackage } from '../tests/unit/helpers.js';
4+
import { planRelease, executeReleasePlan } from './release-plan.js';
5+
import { IncrementableVersionParts } from './release-specification.js';
6+
import * as packageUtils from './package.js';
77

88
jest.mock('./package');
99

0 commit comments

Comments
 (0)