Skip to content

Commit

Permalink
Convert package to ESM (#113)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rekmarks authored Dec 4, 2023
1 parent 66c90b4 commit 151c286
Show file tree
Hide file tree
Showing 45 changed files with 577 additions and 393 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.js → .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module.exports = {

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

parserOptions: {
sourceType: 'module',
},

rules: {
// This makes integration tests easier to read by allowing setup code and
// assertions to be grouped together better
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions bin/create-release-branch.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node

/* eslint-disable-next-line import/no-unassigned-import,import/no-unresolved */
require('../dist/cli');
// eslint-disable-next-line import/no-unassigned-import,import/no-unresolved
import '../dist/cli';
7 changes: 6 additions & 1 deletion jest.config.js → jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* https://jestjs.io/docs/configuration
*/

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

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

// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
Expand Down
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"type": "git",
"url": "https://github.com/MetaMask/create-release-branch.git"
},
"type": "module",
"exports": null,
"bin": "bin/create-release-branch.js",
"files": [
"bin/",
Expand All @@ -19,12 +21,12 @@
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
"prepack": "./scripts/prepack.sh",
"test": "jest && jest-it-up",
"test": "jest && jest-it-up --config jest.config.cjs",
"test:watch": "jest --watch"
},
"dependencies": {
"@metamask/action-utils": "^1.0.0",
"@metamask/utils": "^8.1.0",
"@metamask/utils": "^8.2.1",
"debug": "^4.3.4",
"execa": "^5.1.1",
"pony-cause": "^2.1.9",
Expand Down Expand Up @@ -58,15 +60,15 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0",
"jest-it-up": "^2.0.2",
"jest-it-up": "^3.0.0",
"jest-when": "^3.5.2",
"nanoid": "^3.3.4",
"prettier": "^2.2.1",
"prettier-plugin-packagejson": "^2.3.0",
"rimraf": "^4.0.5",
"stdio-mock": "^1.2.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.7.0",
"tsx": "^4.6.1",
"typescript": "~5.1.6"
},
"packageManager": "[email protected]",
Expand All @@ -79,7 +81,8 @@
},
"lavamoat": {
"allowScripts": {
"@lavamoat/preinstall-always-fail": false
"@lavamoat/preinstall-always-fail": false,
"tsx>esbuild": false
}
}
}
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { main } from './main';
import { main } from './main.js';

/**
* The entrypoint to this tool.
Expand Down
6 changes: 3 additions & 3 deletions src/editor.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { when } from 'jest-when';
import { determineEditor } from './editor';
import * as envModule from './env';
import * as miscUtils from './misc-utils';
import { determineEditor } from './editor.js';
import * as envModule from './env.js';
import * as miscUtils from './misc-utils.js';

jest.mock('./env');
jest.mock('./misc-utils');
Expand Down
4 changes: 2 additions & 2 deletions src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getEnvironmentVariables } from './env';
import { debug, resolveExecutable } from './misc-utils';
import { getEnvironmentVariables } from './env.js';
import { debug, resolveExecutable } from './misc-utils.js';

/**
* Information about the editor present on the user's computer.
Expand Down
2 changes: 1 addition & 1 deletion src/env.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getEnvironmentVariables } from './env';
import { getEnvironmentVariables } from './env.js';

describe('env', () => {
describe('getEnvironmentVariables', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/fs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';
import { rimraf } from 'rimraf';
import { when } from 'jest-when';
import * as actionUtils from '@metamask/action-utils';
import { withSandbox } from '../tests/helpers';
import { withSandbox } from '../tests/helpers.js';
import {
readFile,
writeFile,
Expand All @@ -12,7 +12,7 @@ import {
fileExists,
ensureDirectoryPathExists,
removeFile,
} from './fs';
} from './fs.js';

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

Expand Down
2 changes: 1 addition & 1 deletion src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
readJsonObjectFile as underlyingReadJsonObjectFile,
writeJsonFile as underlyingWriteJsonFile,
} from '@metamask/action-utils';
import { wrapError, isErrorWithCode } from './misc-utils';
import { wrapError, isErrorWithCode } from './misc-utils.js';

/**
* Represents a writeable stream, such as that represented by `process.stdout`
Expand Down
4 changes: 2 additions & 2 deletions src/functional.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { withMonorepoProjectEnvironment } from '../tests/functional/helpers/with';
import { buildChangelog } from '../tests/helpers';
import { withMonorepoProjectEnvironment } from '../tests/functional/helpers/with.js';
import { buildChangelog } from '../tests/helpers.js';

jest.setTimeout(10_000);

Expand Down
10 changes: 5 additions & 5 deletions src/initial-parameters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
buildMockProject,
buildMockPackage,
createNoopWriteStream,
} from '../tests/unit/helpers';
import { determineInitialParameters } from './initial-parameters';
import * as commandLineArgumentsModule from './command-line-arguments';
import * as envModule from './env';
import * as projectModule from './project';
} from '../tests/unit/helpers.js';
import { determineInitialParameters } from './initial-parameters.js';
import * as commandLineArgumentsModule from './command-line-arguments.js';
import * as envModule from './env.js';
import * as projectModule from './project.js';

jest.mock('./command-line-arguments');
jest.mock('./env');
Expand Down
6 changes: 3 additions & 3 deletions src/initial-parameters.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os from 'os';
import path from 'path';
import { readCommandLineArguments } from './command-line-arguments';
import { WriteStreamLike } from './fs';
import { readProject, Project } from './project';
import { readCommandLineArguments } from './command-line-arguments.js';
import { WriteStreamLike } from './fs.js';
import { readProject, Project } from './project.js';

/**
* The type of release being created as determined by the parent release.
Expand Down
8 changes: 4 additions & 4 deletions src/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fs from 'fs';
import { buildMockProject } from '../tests/unit/helpers';
import { main } from './main';
import * as initialParametersModule from './initial-parameters';
import * as monorepoWorkflowOperations from './monorepo-workflow-operations';
import { buildMockProject } from '../tests/unit/helpers.js';
import { main } from './main.js';
import * as initialParametersModule from './initial-parameters.js';
import * as monorepoWorkflowOperations from './monorepo-workflow-operations.js';

jest.mock('./initial-parameters');
jest.mock('./monorepo-workflow-operations');
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { WriteStream } from 'fs';
import { determineInitialParameters } from './initial-parameters';
import { followMonorepoWorkflow } from './monorepo-workflow-operations';
import { determineInitialParameters } from './initial-parameters.js';
import { followMonorepoWorkflow } from './monorepo-workflow-operations.js';

/**
* The main function for this tool. Designed to not access `process.argv`,
Expand Down
2 changes: 1 addition & 1 deletion src/misc-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
runCommand,
getStdoutFromCommand,
getLinesFromCommand,
} from './misc-utils';
} from './misc-utils.js';

jest.mock('which');
jest.mock('execa');
Expand Down
18 changes: 9 additions & 9 deletions src/monorepo-workflow-operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { when } from 'jest-when';
import { MockWritable } from 'stdio-mock';
import { withSandbox, Sandbox, isErrorWithCode } from '../tests/helpers';
import { buildMockProject, Require } from '../tests/unit/helpers';
import { followMonorepoWorkflow } from './monorepo-workflow-operations';
import * as editorModule from './editor';
import type { Editor } from './editor';
import * as releaseSpecificationModule from './release-specification';
import type { ReleaseSpecification } from './release-specification';
import * as releasePlanModule from './release-plan';
import type { ReleasePlan } from './release-plan';
import * as repoModule from './repo';
import * as workflowOperations from './workflow-operations';
import { followMonorepoWorkflow } from './monorepo-workflow-operations.js';
import * as editorModule from './editor.js';
import type { Editor } from './editor.js';
import * as releaseSpecificationModule from './release-specification.js';
import type { ReleaseSpecification } from './release-specification.js';
import * as releasePlanModule from './release-plan.js';
import type { ReleasePlan } from './release-plan.js';
import * as repoModule from './repo.js';
import * as workflowOperations from './workflow-operations.js';

jest.mock('./editor');
jest.mock('./release-plan');
Expand Down
16 changes: 8 additions & 8 deletions src/monorepo-workflow-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ import {
fileExists,
removeFile,
writeFile,
} from './fs';
import { determineEditor } from './editor';
import { ReleaseType } from './initial-parameters';
} from './fs.js';
import { determineEditor } from './editor.js';
import { ReleaseType } from './initial-parameters.js';
import {
Project,
updateChangelogsForChangedPackages,
restoreChangelogsForSkippedPackages,
} from './project';
import { planRelease, executeReleasePlan } from './release-plan';
import { commitAllChanges } from './repo';
} from './project.js';
import { planRelease, executeReleasePlan } from './release-plan.js';
import { commitAllChanges } from './repo.js';
import {
generateReleaseSpecificationTemplateForMonorepo,
waitForUserToEditReleaseSpecification,
validateReleaseSpecification,
} from './release-specification';
import { createReleaseBranch } from './workflow-operations';
} from './release-specification.js';
import { createReleaseBranch } from './workflow-operations.js';

/**
* For a monorepo, the process works like this:
Expand Down
4 changes: 2 additions & 2 deletions src/package-manifest.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fs from 'fs';
import path from 'path';
import { SemVer } from 'semver';
import { withSandbox } from '../tests/helpers';
import { readPackageManifest } from './package-manifest';
import { withSandbox } from '../tests/helpers.js';
import { readPackageManifest } from './package-manifest.js';

describe('package-manifest', () => {
describe('readPackageManifest', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/package-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
ManifestDependencyFieldNames as PackageManifestDependenciesFieldNames,
} from '@metamask/action-utils';
import { isPlainObject } from '@metamask/utils';
import { readJsonObjectFile } from './fs';
import { isTruthyString } from './misc-utils';
import { semver, SemVer } from './semver';
import { readJsonObjectFile } from './fs.js';
import { isTruthyString } from './misc-utils.js';
import { semver, SemVer } from './semver.js';

export { PackageManifestFieldNames, PackageManifestDependenciesFieldNames };

Expand Down
10 changes: 5 additions & 5 deletions src/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import {
buildMockProject,
buildMockManifest,
createNoopWriteStream,
} from '../tests/unit/helpers';
} from '../tests/unit/helpers.js';
import {
readMonorepoRootPackage,
readMonorepoWorkspacePackage,
updatePackage,
updatePackageChangelog,
} from './package';
import * as fsModule from './fs';
import * as packageManifestModule from './package-manifest';
import * as repoModule from './repo';
} from './package.js';
import * as fsModule from './fs.js';
import * as packageManifestModule from './package-manifest.js';
import * as repoModule from './repo.js';

jest.mock('./package-manifest');
jest.mock('./repo');
Expand Down
14 changes: 7 additions & 7 deletions src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import fs, { WriteStream } from 'fs';
import path from 'path';
import { format } from 'util';
import { parseChangelog, updateChangelog } from '@metamask/auto-changelog';
import { WriteStreamLike, readFile, writeFile, writeJsonFile } from './fs';
import { isErrorWithCode } from './misc-utils';
import { WriteStreamLike, readFile, writeFile, writeJsonFile } from './fs.js';
import { isErrorWithCode } from './misc-utils.js';
import {
readPackageManifest,
UnvalidatedPackageManifest,
ValidatedPackageManifest,
} from './package-manifest';
import { Project } from './project';
import { PackageReleasePlan } from './release-plan';
import { hasChangesInDirectorySinceGitTag } from './repo';
import { SemVer } from './semver';
} from './package-manifest.js';
import { Project } from './project.js';
import { PackageReleasePlan } from './release-plan.js';
import { hasChangesInDirectorySinceGitTag } from './repo.js';
import { SemVer } from './semver.js';

const MANIFEST_FILE_NAME = 'package.json';
const CHANGELOG_FILE_NAME = 'CHANGELOG.md';
Expand Down
10 changes: 5 additions & 5 deletions src/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import {
readProject,
restoreChangelogsForSkippedPackages,
updateChangelogsForChangedPackages,
} from './project';
import * as packageModule from './package';
import * as repoModule from './repo';
import * as fs from './fs';
import { IncrementableVersionParts } from './release-specification';
} from './project.js';
import * as packageModule from './package.js';
import * as repoModule from './repo.js';
import * as fs from './fs.js';
import { IncrementableVersionParts } from './release-specification.js';

jest.mock('./package');
jest.mock('./repo');
Expand Down
12 changes: 6 additions & 6 deletions src/project.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { WriteStream } from 'fs';
import { resolve } from 'path';
import { getWorkspaceLocations } from '@metamask/action-utils';
import { WriteStreamLike, fileExists } from './fs';
import { WriteStreamLike, fileExists } from './fs.js';
import {
Package,
readMonorepoRootPackage,
readMonorepoWorkspacePackage,
updatePackageChangelog,
} from './package';
import { getRepositoryHttpsUrl, getTagNames, restoreFiles } from './repo';
import { SemVer } from './semver';
import { PackageManifestFieldNames } from './package-manifest';
import { ReleaseSpecification } from './release-specification';
} from './package.js';
import { getRepositoryHttpsUrl, getTagNames, restoreFiles } from './repo.js';
import { SemVer } from './semver.js';
import { PackageManifestFieldNames } from './package-manifest.js';
import { ReleaseSpecification } from './release-specification.js';

/**
* The release version of the root package of a monorepo extracted from its
Expand Down
8 changes: 4 additions & 4 deletions src/release-plan.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import fs from 'fs';
import { SemVer } from 'semver';
import { buildMockProject, buildMockPackage } from '../tests/unit/helpers';
import { planRelease, executeReleasePlan } from './release-plan';
import { IncrementableVersionParts } from './release-specification';
import * as packageUtils from './package';
import { buildMockProject, buildMockPackage } from '../tests/unit/helpers.js';
import { planRelease, executeReleasePlan } from './release-plan.js';
import { IncrementableVersionParts } from './release-specification.js';
import * as packageUtils from './package.js';

jest.mock('./package');

Expand Down
Loading

0 comments on commit 151c286

Please sign in to comment.