From c3cdc7eba4b0c339e3c283b525366eb8f40d3ea4 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Fri, 4 Sep 2026 21:15:05 +0200 Subject: [PATCH 1/2] feat!: convert the package to ESM only Core is moving to an ESM only monorepo (MetaMask/core#9536), so this package should arrive already converted rather than landing as the sole hybrid one. BREAKING: the CommonJS build is gone. require('@metamask/utils') now fails with ERR_REQUIRE_ESM. `main` and `module` are removed, and both `.` and `./node` resolve through `exports` to a single ./dist/*.js with ./dist/*.d.ts types. Consumers already using `import` are unaffected. package.json adds "type": "module", collapses the dual exports map build ts-bridge -> tsc, since ts-bridge exists to emit both formats @ts-bridge/cli removed, rimraf added for build:only-clean tsconfig.build drops emitDeclarationOnly, tsc now emits the JS too 102 relative import specifiers across 45 files gained explicit .js extensions, which ESM requires. Directories resolve to /index.js. Core's sources already look like this and enforce it with n/file-extension-in-import, so the same three import rules are adopted here verbatim. Two things only surfaced by running the built output rather than the tests: lodash `import { memoize } from 'lodash'` throws at runtime under ESM, because Node's lexer cannot see named exports through lodash's CJS. Switched to `lodash/memoize.js`, a default import of the single method. Core solved the same problem by moving to lodash-es plus a jest moduleNameMapper; this needs neither. scure-bip39 the deep wordlist import needed an explicit .js. Every other CJS dependency survives named imports untouched: semver, superstruct, @scure/base, @noble/hashes and pony-cause all have lexer friendly CJS. Verified by importing all 27 built modules individually. jest.config.js and .prettierrc.js are renamed to .cjs, since "type": "module" makes bare .js ESM. Tests still compile to CommonJS through a ts-jest transform override, matching core, with a moduleNameMapper stripping the .js specifiers back off. constraints.pro is rewritten for the single entrypoint shape. --- .depcheckrc.json | 3 +- .prettierrc.js => .prettierrc.cjs | 0 CHANGELOG.md | 4 +++ constraints.pro | 26 ++++++++------- eslint.config.mjs | 28 ++++++++++++++++ jest.config.js => jest.config.cjs | 23 ++++++++++++-- package.json | 31 ++++++------------ src/__fixtures__/coercions.ts | 2 +- src/__fixtures__/index.ts | 10 +++--- src/assert.test.ts | 2 +- src/assert.ts | 2 +- src/base64.test.ts | 4 +-- src/base64.ts | 2 +- src/bytes.test.ts | 4 +-- src/bytes.ts | 6 ++-- src/caip-types.test-d.ts | 2 +- src/caip-types.test.ts | 4 +-- src/caip-types.ts | 2 +- src/checksum.test.ts | 2 +- src/checksum.ts | 2 +- src/coercers.test.ts | 13 +++++--- src/coercers.ts | 8 ++--- src/collections.test.ts | 2 +- src/errors.test.ts | 2 +- src/errors.ts | 2 +- src/fs.test.ts | 2 +- src/fs.ts | 4 +-- src/hashing.test.ts | 4 +-- src/hex.test-d.ts | 2 +- src/hex.test.ts | 4 +-- src/hex.ts | 4 +-- src/index.test.ts | 2 +- src/index.ts | 50 ++++++++++++++--------------- src/json.test-d.ts | 4 +-- src/json.test.ts | 24 +++++++------- src/json.ts | 6 ++-- src/keyring.ts | 6 ++-- src/logging.test.ts | 2 +- src/misc.test-d.ts | 4 +-- src/misc.test.ts | 4 +-- src/mnemonic.test.ts | 2 +- src/mnemonic.ts | 2 +- src/node.test.ts | 2 +- src/node.ts | 4 +-- src/number.test.ts | 9 ++++-- src/number.ts | 6 ++-- src/promise.test.ts | 2 +- src/superstruct.test.ts | 2 +- src/time.test.ts | 2 +- src/transaction-types.ts | 4 +-- src/unitsConversion.test.ts | 2 +- src/versions.test.ts | 4 +-- src/versions.ts | 4 +-- tsconfig.build.json | 1 - yarn.lock | 53 ++++++++----------------------- 55 files changed, 216 insertions(+), 191 deletions(-) rename .prettierrc.js => .prettierrc.cjs (100%) rename jest.config.js => jest.config.cjs (91%) diff --git a/.depcheckrc.json b/.depcheckrc.json index effc6b12f..db73216ee 100644 --- a/.depcheckrc.json +++ b/.depcheckrc.json @@ -4,11 +4,12 @@ "@lavamoat/preinstall-always-fail", "@metamask/auto-changelog", "@types/*", - "@yarnpkg/core", "@yarnpkg/cli", + "@yarnpkg/core", "@yarnpkg/fslib", "clipanion", "prettier-plugin-packagejson", + "rimraf", "ts-node", "typedoc" ] diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6817943e3..8d5dde8fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING:** The package is now ESM only ([#323](https://github.com/MetaMask/utils/pull/323)) + - The CommonJS build is gone. `require('@metamask/utils')` now fails with `ERR_REQUIRE_ESM`; use `import` instead. + - `main` and `module` are removed. Both `.` and `./node` resolve through `exports` to a single `./dist/*.js` with `./dist/*.d.ts` types. + - Consumers already using `import` are unaffected. - Bump `@ethereumjs/tx` from `^4.2.0` to `^5.4.0` ([#321](https://github.com/MetaMask/utils/pull/321)) - The deprecated `Keyring.signTransaction` now returns `LegacyTxData` rather than `TxData`. These describe the same shape: `@ethereumjs/tx@5` repurposed the name `TxData` for a map keyed by transaction type and renamed the old meaning to `LegacyTxData`. Implementations do not need changing. - Bump `@metamask/scure-bip39` from `^2.0.3` to `^2.1.1` ([#311](https://github.com/MetaMask/utils/pull/311)) diff --git a/constraints.pro b/constraints.pro index 0bec58439..b54ba6f36 100644 --- a/constraints.pro +++ b/constraints.pro @@ -65,18 +65,20 @@ gen_enforced_field(WorkspaceCwd, 'repository.url', 'https://github.com/MetaMask/ % The license for the package must be specified. gen_enforced_field(WorkspaceCwd, 'license'). -% The type definitions entrypoint the package must be `./dist/index.d.cts`. -gen_enforced_field(WorkspaceCwd, 'types', './dist/index.d.cts'). - -% The entrypoint for the package must be `./dist/index.cjs`. -gen_enforced_field(WorkspaceCwd, 'main', './dist/index.cjs'). -gen_enforced_field(WorkspaceCwd, 'exports["."].require.types', './dist/index.d.cts'). -gen_enforced_field(WorkspaceCwd, 'exports["."].require.default', './dist/index.cjs'). - -% The module entrypoint for the package must be `./dist/index.mjs`. -gen_enforced_field(WorkspaceCwd, 'module', './dist/index.mjs'). -gen_enforced_field(WorkspaceCwd, 'exports["."].import.types', './dist/index.d.mts'). -gen_enforced_field(WorkspaceCwd, 'exports["."].import.default', './dist/index.mjs'). +% The package is ESM only, so there is no `main` or `module` entrypoint and no +% `require` condition. A single build serves both. +gen_enforced_field(WorkspaceCwd, 'type', 'module'). + +% The type definitions entrypoint for the package must be `./dist/index.d.ts`. +gen_enforced_field(WorkspaceCwd, 'types', './dist/index.d.ts'). + +% The entrypoint for the package must be `./dist/index.js`. +gen_enforced_field(WorkspaceCwd, 'exports["."].types', './dist/index.d.ts'). +gen_enforced_field(WorkspaceCwd, 'exports["."].default', './dist/index.js'). + +% The Node specific entrypoint must be `./dist/node.js`. +gen_enforced_field(WorkspaceCwd, 'exports["./node"].types', './dist/node.d.ts'). +gen_enforced_field(WorkspaceCwd, 'exports["./node"].default', './dist/node.js'). gen_enforced_field(WorkspaceCwd, 'exports["./package.json"]', './package.json'). diff --git a/eslint.config.mjs b/eslint.config.mjs index e722c8414..539737679 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,7 @@ import base, { createConfig } from '@metamask/eslint-config'; import jest from '@metamask/eslint-config-jest'; import nodejs from '@metamask/eslint-config-nodejs'; import typescript from '@metamask/eslint-config-typescript'; +import nodePlugin from 'eslint-plugin-n'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -85,6 +86,33 @@ const config = createConfig([ 'import-x/ignore': ['uuid'], }, }, + { + // The package is ESM, so relative imports carry explicit `.js` specifiers. + // These are the same three rules core configures for that, verbatim. + // `import-x/extensions` does not support using ".js" for TypeScript + // files(?), so we load the `n` plugin and use `n/file-extension-in-import` + // instead. + plugins: { n: nodePlugin }, + + rules: { + 'n/file-extension-in-import': ['error', 'always'], + 'import-x/extensions': [ + 'error', + { + js: 'ignorePackages', + ts: 'never', + tsx: 'never', + json: 'always', + }, + ], + 'import-x/no-useless-path-segments': [ + 'error', + { + noUselessIndex: false, + }, + ], + }, + }, { files: ['**/*.test-d.ts'], rules: { diff --git a/jest.config.js b/jest.config.cjs similarity index 91% rename from jest.config.js rename to jest.config.cjs index 5ec597fe5..3f0397b54 100644 --- a/jest.config.js +++ b/jest.config.cjs @@ -90,7 +90,12 @@ 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: {}, + // The sources are ESM and so use explicit `.js` specifiers on relative + // imports. The tests compile to CommonJS (see `transform` below), where those + // files are still `.ts`, so the extension is stripped back off here. + moduleNameMapper: { + '^(\\.{1,2}/.+)\\.js$': '$1', + }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // modulePathIgnorePatterns: [], @@ -187,7 +192,21 @@ module.exports = { // timers: "real", // A map from regular expressions to paths to transformers - // transform: undefined, + // The package is ESM, but Jest runs the tests as CommonJS. Overriding the + // module settings here compiles the sources to CommonJS for tests without + // affecting the published build. + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + tsconfig: { + module: 'CommonJS', + moduleResolution: 'Node', + verbatimModuleSyntax: false, + }, + }, + ], + }, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // transformIgnorePatterns: [ diff --git a/package.json b/package.json index 5b35486bf..d86a6df72 100644 --- a/package.json +++ b/package.json @@ -14,30 +14,17 @@ "files": [ "dist" ], + "type": "module", "sideEffects": false, - "main": "./dist/index.cjs", - "module": "./dist/index.mjs", - "types": "./dist/index.d.cts", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "types": "./dist/index.d.ts", + "default": "./dist/index.js" }, "./node": { - "import": { - "types": "./dist/node.d.mts", - "default": "./dist/node.mjs" - }, - "require": { - "types": "./dist/node.d.cts", - "default": "./dist/node.cjs" - } + "types": "./dist/node.d.ts", + "default": "./dist/node.js" }, "./package.json": "./package.json" }, @@ -46,8 +33,10 @@ "registry": "https://registry.npmjs.org/" }, "scripts": { - "build": "ts-bridge --project tsconfig.build.json --clean", + "build": "tsc --project tsconfig.build.json", + "build:clean": "yarn build:only-clean && yarn build", "build:docs": "typedoc", + "build:only-clean": "rimraf ./dist ./tsconfig.build.tsbuildinfo", "lint": "yarn lint:eslint && yarn lint:constraints && yarn lint:misc --check && yarn lint:dependencies --check && yarn lint:changelog", "lint:changelog": "auto-changelog validate --prettier", "lint:constraints": "yarn constraints", @@ -83,7 +72,6 @@ "@metamask/eslint-config-jest": "^15.0.0", "@metamask/eslint-config-nodejs": "^15.0.0", "@metamask/eslint-config-typescript": "^15.0.0", - "@ts-bridge/cli": "^0.6.4", "@types/jest": "^30.0.0", "@types/jest-when": "^3.5.3", "@types/node": "~18.18.14", @@ -104,6 +92,7 @@ "jest-when": "^3.7.0", "oxfmt": "^0.44.0", "prettier": "^3.3.3", + "rimraf": "^5.0.5", "stdio-mock": "^1.2.0", "ts-jest": "^29.4.11", "ts-node": "^10.7.0", diff --git a/src/__fixtures__/coercions.ts b/src/__fixtures__/coercions.ts index c80ebde3f..895e4b7c9 100644 --- a/src/__fixtures__/coercions.ts +++ b/src/__fixtures__/coercions.ts @@ -1,4 +1,4 @@ -import type { Hex } from '../hex'; +import type { Hex } from '../hex.js'; export const POSITIVE_INTEGERS = [0, 1, 10, 100, 1000, 123456789, 2147483647]; export const NEGATIVE_INTEGERS = [ diff --git a/src/__fixtures__/index.ts b/src/__fixtures__/index.ts index 39d2d2b42..53604a1fd 100644 --- a/src/__fixtures__/index.ts +++ b/src/__fixtures__/index.ts @@ -1,5 +1,5 @@ -export * from './bytes'; -export * from './caip-types'; -export * from './coercions'; -export * from './json'; -export * from './numbers'; +export * from './bytes.js'; +export * from './caip-types.js'; +export * from './coercions.js'; +export * from './json.js'; +export * from './numbers.js'; diff --git a/src/assert.test.ts b/src/assert.test.ts index ab1175420..22a005817 100644 --- a/src/assert.test.ts +++ b/src/assert.test.ts @@ -5,7 +5,7 @@ import { assertExhaustive, AssertionError, assertStruct, -} from './assert'; +} from './assert.js'; jest.mock('@metamask/superstruct', () => ({ ...jest.requireActual('@metamask/superstruct'), diff --git a/src/assert.ts b/src/assert.ts index ba9e821d6..7a8473152 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -1,7 +1,7 @@ import type { Struct } from '@metamask/superstruct'; import { assert as assertSuperstruct } from '@metamask/superstruct'; -import { getErrorMessage } from './errors'; +import { getErrorMessage } from './errors.js'; export type AssertionErrorConstructor = | (new (args: { message: string }) => Error) diff --git a/src/base64.test.ts b/src/base64.test.ts index 8499e606c..6a78b677d 100644 --- a/src/base64.test.ts +++ b/src/base64.test.ts @@ -1,7 +1,7 @@ import { is, size, string } from '@metamask/superstruct'; -import type { Base64Options } from './base64'; -import { base64 } from './base64'; +import type { Base64Options } from './base64.js'; +import { base64 } from './base64.js'; describe('base64', () => { it.each([ diff --git a/src/base64.ts b/src/base64.ts index 1f6f71d7e..76141df23 100644 --- a/src/base64.ts +++ b/src/base64.ts @@ -1,7 +1,7 @@ import type { Struct } from '@metamask/superstruct'; import { pattern } from '@metamask/superstruct'; -import { assert } from './assert'; +import { assert } from './assert.js'; export type Base64Options = { /** diff --git a/src/bytes.test.ts b/src/bytes.test.ts index a37c698c0..14a90807d 100644 --- a/src/bytes.test.ts +++ b/src/bytes.test.ts @@ -5,7 +5,7 @@ import { TWOS_COMPLEMENT_BYTES_FIXTURES, UPPER_CASE_HEX_FIXTURES, UTF_8_BYTES_FIXTURES, -} from './__fixtures__'; +} from './__fixtures__/index.js'; import { areUint8ArraysEqual, assertIsBytes, @@ -25,7 +25,7 @@ import { signedBigIntToBytes, stringToBytes, valueToBytes, -} from './bytes'; +} from './bytes.js'; describe('isBytes', () => { it('returns true for a Node.js Buffer', () => { diff --git a/src/bytes.ts b/src/bytes.ts index 2b3bc44cf..5992b9e04 100644 --- a/src/bytes.ts +++ b/src/bytes.ts @@ -1,8 +1,8 @@ import { base64 } from '@scure/base'; -import { assert } from './assert'; -import type { Hex } from './hex'; -import { add0x, assertIsHexString, remove0x } from './hex'; +import { assert } from './assert.js'; +import type { Hex } from './hex.js'; +import { add0x, assertIsHexString, remove0x } from './hex.js'; // '0'.charCodeAt(0) === 48 const HEX_MINIMUM_NUMBER_CHARACTER = 48; diff --git a/src/caip-types.test-d.ts b/src/caip-types.test-d.ts index 0afcfc029..c6255b245 100644 --- a/src/caip-types.test-d.ts +++ b/src/caip-types.test-d.ts @@ -10,7 +10,7 @@ import type { CaipChainId, CaipNamespace, CaipReference, -} from '.'; +} from './index.js'; const embeddedString = 'test'; diff --git a/src/caip-types.test.ts b/src/caip-types.test.ts index 2b7e89507..fc0963c48 100644 --- a/src/caip-types.test.ts +++ b/src/caip-types.test.ts @@ -8,7 +8,7 @@ import { CAIP_CHAIN_ID_FIXTURES, CAIP_NAMESPACE_FIXTURES, CAIP_REFERENCE_FIXTURES, -} from './__fixtures__'; +} from './__fixtures__/index.js'; import { CAIP_ACCOUNT_ADDRESS_REGEX, CAIP_ASSET_NAMESPACE_REGEX, @@ -34,7 +34,7 @@ import { toCaipAssetId, toCaipAssetType, toCaipChainId, -} from './caip-types'; +} from './caip-types.js'; describe('isCaipChainId', () => { it.each(CAIP_CHAIN_ID_FIXTURES)( diff --git a/src/caip-types.ts b/src/caip-types.ts index 416026ee5..bc7eea413 100644 --- a/src/caip-types.ts +++ b/src/caip-types.ts @@ -1,6 +1,6 @@ import type { Infer } from '@metamask/superstruct'; -import { definePattern } from './superstruct'; +import { definePattern } from './superstruct.js'; export const CAIP_CHAIN_ID_REGEX = /^(?[-a-z0-9]{3,8}):(?[-_a-zA-Z0-9]{1,32})$/u; diff --git a/src/checksum.test.ts b/src/checksum.test.ts index 4a5c90a37..b97fad779 100644 --- a/src/checksum.test.ts +++ b/src/checksum.test.ts @@ -1,6 +1,6 @@ import { is } from '@metamask/superstruct'; -import { ChecksumStruct } from './checksum'; +import { ChecksumStruct } from './checksum.js'; describe('ChecksumStruct', () => { it('validates valid checksum', () => { diff --git a/src/checksum.ts b/src/checksum.ts index fe42dfc36..c466cbf1f 100644 --- a/src/checksum.ts +++ b/src/checksum.ts @@ -1,6 +1,6 @@ import { size, string } from '@metamask/superstruct'; -import { base64 } from './base64'; +import { base64 } from './base64.js'; export const ChecksumStruct = size( base64(string(), { paddingRequired: true }), diff --git a/src/coercers.test.ts b/src/coercers.test.ts index fb22cdad2..634db0ee3 100644 --- a/src/coercers.test.ts +++ b/src/coercers.test.ts @@ -3,10 +3,15 @@ import { HEX_STRINGS, NEGATIVE_INTEGERS, POSITIVE_INTEGERS, -} from './__fixtures__'; -import { bytesToHex, hexToBytes } from './bytes'; -import { createBigInt, createBytes, createHex, createNumber } from './coercers'; -import { add0x } from './hex'; +} from './__fixtures__/index.js'; +import { bytesToHex, hexToBytes } from './bytes.js'; +import { + createBigInt, + createBytes, + createHex, + createNumber, +} from './coercers.js'; +import { add0x } from './hex.js'; describe('createNumber', () => { it.each(POSITIVE_INTEGERS)( diff --git a/src/coercers.ts b/src/coercers.ts index d7fed259b..9c01d119e 100644 --- a/src/coercers.ts +++ b/src/coercers.ts @@ -10,10 +10,10 @@ import { union, } from '@metamask/superstruct'; -import { assert } from './assert'; -import { bytesToHex, hexToBytes } from './bytes'; -import type { Hex } from './hex'; -import { StrictHexStruct } from './hex'; +import { assert } from './assert.js'; +import { bytesToHex, hexToBytes } from './bytes.js'; +import type { Hex } from './hex.js'; +import { StrictHexStruct } from './hex.js'; const NumberLikeStruct = union([number(), bigint(), string(), StrictHexStruct]); const NumberCoercer = coerce(number(), NumberLikeStruct, Number); diff --git a/src/collections.test.ts b/src/collections.test.ts index 263a3ebf4..95cd82c92 100644 --- a/src/collections.test.ts +++ b/src/collections.test.ts @@ -1,4 +1,4 @@ -import { FrozenMap, FrozenSet } from './collections'; +import { FrozenMap, FrozenSet } from './collections.js'; describe('FrozenMap', () => { describe('immutability', () => { diff --git a/src/errors.test.ts b/src/errors.test.ts index b2c01d0c0..1e1a7cec8 100644 --- a/src/errors.test.ts +++ b/src/errors.test.ts @@ -7,7 +7,7 @@ import { isErrorWithMessage, isErrorWithStack, wrapError, -} from './errors'; +} from './errors.js'; describe('isErrorWithCode', () => { it('returns true if given an object that includes a "code" property', () => { diff --git a/src/errors.ts b/src/errors.ts index 2bb8d8901..6c2a8e9e7 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,6 +1,6 @@ import { ErrorWithCause } from 'pony-cause'; -import { isNullOrUndefined, isObject } from './misc'; +import { isNullOrUndefined, isObject } from './misc.js'; /** * Type guard for determining whether the given value is an instance of Error. diff --git a/src/fs.test.ts b/src/fs.test.ts index 9478bcfdd..1f128e8eb 100644 --- a/src/fs.test.ts +++ b/src/fs.test.ts @@ -15,7 +15,7 @@ import { readJsonFile, writeFile, writeJsonFile, -} from './fs'; +} from './fs.js'; const { withinSandbox } = createSandbox('utils'); diff --git a/src/fs.ts b/src/fs.ts index 58c0eae81..36e6b0ff6 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -6,8 +6,8 @@ import os from 'os'; import path from 'path'; import * as uuid from 'uuid'; -import { isErrorWithCode, wrapError } from './errors'; -import type { Json } from './json'; +import { isErrorWithCode, wrapError } from './errors.js'; +import type { Json } from './json.js'; /** * Information about the file sandbox provided to tests that need temporary diff --git a/src/hashing.test.ts b/src/hashing.test.ts index e418cd7be..aab753367 100644 --- a/src/hashing.test.ts +++ b/src/hashing.test.ts @@ -3,8 +3,8 @@ import * as nobleHashes512 from '@noble/hashes/sha512'; import { webcrypto } from 'crypto'; import { parse } from 'semver'; -import { bytesToHex, stringToBytes } from './bytes'; -import { sha256, sha512, sha384 } from './hashing'; +import { bytesToHex, stringToBytes } from './bytes.js'; +import { sha256, sha512, sha384 } from './hashing.js'; describe('hash functions', () => { const originalSubtle = globalThis.crypto?.subtle ?? webcrypto.subtle; diff --git a/src/hex.test-d.ts b/src/hex.test-d.ts index 0321839b8..08987e2ae 100644 --- a/src/hex.test-d.ts +++ b/src/hex.test-d.ts @@ -1,6 +1,6 @@ import { expectAssignable, expectNotAssignable } from 'tsd'; -import type { Hex } from '.'; +import type { Hex } from './index.js'; // Valid hex strings: diff --git a/src/hex.test.ts b/src/hex.test.ts index c4c18d6c1..3cf8720ec 100644 --- a/src/hex.test.ts +++ b/src/hex.test.ts @@ -3,7 +3,7 @@ element, but dropping them widens the inferred array element type from `Hex` to `string`, so the callback argument stops satisfying the `Hex` parameter and the suite fails to compile. */ -import type { Hex } from './hex'; +import type { Hex } from './hex.js'; import { add0x, assertIsHexString, @@ -17,7 +17,7 @@ import { remove0x, getChecksumAddressUnmemoized as getChecksumAddress, getChecksumAddress as getChecksumAddressMemoized, -} from './hex'; +} from './hex.js'; describe('isHexString', () => { it.each([ diff --git a/src/hex.ts b/src/hex.ts index fb301e0d1..66268ef0e 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -1,9 +1,9 @@ import { pattern, string } from '@metamask/superstruct'; import type { Struct } from '@metamask/superstruct'; import { keccak_256 as keccak256 } from '@noble/hashes/sha3'; -import { memoize } from 'lodash'; +import memoize from 'lodash/memoize.js'; -import { assert } from './assert'; +import { assert } from './assert.js'; export type Hex = `0x${string}`; diff --git a/src/index.test.ts b/src/index.test.ts index 418d50fcd..8123ffdd8 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,4 +1,4 @@ -import * as allExports from '.'; +import * as allExports from './index.js'; describe('index', () => { it('includes only cross-platform exports', () => { diff --git a/src/index.ts b/src/index.ts index f4121ed9a..def1b20dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,14 @@ -export * from './assert'; -export * from './base64'; -export * from './bytes'; -export * from './caip-types'; -export * from './checksum'; -export * from './coercers'; -export * from './collections'; -export type * from './encryption-types'; -export * from './errors'; -export * from './hashing'; -export type { Hex } from './hex'; +export * from './assert.js'; +export * from './base64.js'; +export * from './bytes.js'; +export * from './caip-types.js'; +export * from './checksum.js'; +export * from './coercers.js'; +export * from './collections.js'; +export type * from './encryption-types.js'; +export * from './errors.js'; +export * from './hashing.js'; +export type { Hex } from './hex.js'; export { HexStruct, StrictHexStruct, @@ -25,23 +25,23 @@ export { isValidChecksumAddress, add0x, remove0x, -} from './hex'; -export * from './json'; -export type * from './keyring'; -export * from './logging'; -export * from './misc'; -export * from './mnemonic'; -export * from './number'; -export type * from './opaque'; -export * from './promise'; -export * from './superstruct'; -export * from './time'; -export type * from './transaction-types'; -export * from './versions'; +} from './hex.js'; +export * from './json.js'; +export type * from './keyring.js'; +export * from './logging.js'; +export * from './misc.js'; +export * from './mnemonic.js'; +export * from './number.js'; +export type * from './opaque.js'; +export * from './promise.js'; +export * from './superstruct.js'; +export * from './time.js'; +export type * from './transaction-types.js'; +export * from './versions.js'; export { toWei, fromWei, numberToString, getValueOfUnit, unitMap, -} from './unitsConversion'; +} from './unitsConversion.js'; diff --git a/src/json.test-d.ts b/src/json.test-d.ts index e2c6a6b28..a74dc6bc4 100644 --- a/src/json.test-d.ts +++ b/src/json.test-d.ts @@ -4,8 +4,8 @@ import type { Infer } from '@metamask/superstruct'; import { boolean, number, optional, string } from '@metamask/superstruct'; import { expectAssignable, expectNotAssignable } from 'tsd'; -import type { Json } from '.'; -import { exactOptional, object } from '.'; +import type { Json } from './index.js'; +import { exactOptional, object } from './index.js'; // Valid Json: diff --git a/src/json.test.ts b/src/json.test.ts index 1baed1e32..e7bb06870 100644 --- a/src/json.test.ts +++ b/src/json.test.ts @@ -10,6 +10,17 @@ import { optional, } from '@metamask/superstruct'; +import { + JSON_FIXTURES, + JSON_RPC_ERROR_FIXTURES, + JSON_RPC_FAILURE_FIXTURES, + JSON_RPC_NOTIFICATION_FIXTURES, + JSON_RPC_PENDING_RESPONSE_FIXTURES, + JSON_RPC_REQUEST_FIXTURES, + JSON_RPC_RESPONSE_FIXTURES, + JSON_RPC_SUCCESS_FIXTURES, + JSON_VALIDATION_FIXTURES, +} from './__fixtures__/index.js'; import { assert, assertIsJsonRpcError, @@ -33,18 +44,7 @@ import { object, exactOptional, JsonStruct, -} from '.'; -import { - JSON_FIXTURES, - JSON_RPC_ERROR_FIXTURES, - JSON_RPC_FAILURE_FIXTURES, - JSON_RPC_NOTIFICATION_FIXTURES, - JSON_RPC_PENDING_RESPONSE_FIXTURES, - JSON_RPC_REQUEST_FIXTURES, - JSON_RPC_RESPONSE_FIXTURES, - JSON_RPC_SUCCESS_FIXTURES, - JSON_VALIDATION_FIXTURES, -} from './__fixtures__'; +} from './index.js'; jest.mock('@metamask/superstruct', () => ({ ...jest.requireActual('@metamask/superstruct'), diff --git a/src/json.ts b/src/json.ts index 59b7b1c67..755bce08b 100644 --- a/src/json.ts +++ b/src/json.ts @@ -26,9 +26,9 @@ import type { Optionalize, } from '@metamask/superstruct'; -import type { AssertionErrorConstructor } from './assert'; -import { assertStruct } from './assert'; -import { hasProperty } from './misc'; +import type { AssertionErrorConstructor } from './assert.js'; +import { assertStruct } from './assert.js'; +import { hasProperty } from './misc.js'; /** * Any JSON-compatible value. diff --git a/src/keyring.ts b/src/keyring.ts index 30b422d21..b5ba3686d 100644 --- a/src/keyring.ts +++ b/src/keyring.ts @@ -1,8 +1,8 @@ import type { TypedTransaction, LegacyTxData } from '@ethereumjs/tx'; -import type { Eip1024EncryptedData } from './encryption-types'; -import type { Hex } from './hex'; -import type { Json } from './json'; +import type { Eip1024EncryptedData } from './encryption-types.js'; +import type { Hex } from './hex.js'; +import type { Json } from './json.js'; /** * A Keyring class. diff --git a/src/logging.test.ts b/src/logging.test.ts index cd2245bca..1f0c75a0d 100644 --- a/src/logging.test.ts +++ b/src/logging.test.ts @@ -1,6 +1,6 @@ import { MockWritable } from 'stdio-mock'; -import { createProjectLogger, createModuleLogger } from './logging'; +import { createProjectLogger, createModuleLogger } from './logging.js'; describe('logging', () => { beforeAll(() => { diff --git a/src/misc.test-d.ts b/src/misc.test-d.ts index ab8e306ec..f334de414 100644 --- a/src/misc.test-d.ts +++ b/src/misc.test-d.ts @@ -1,7 +1,7 @@ import { expectAssignable, expectNotAssignable, expectType } from 'tsd'; -import type { PublicInterface, RuntimeObject } from './misc'; -import { isObject, hasProperty, getKnownPropertyNames } from './misc'; +import type { PublicInterface, RuntimeObject } from './misc.js'; +import { isObject, hasProperty, getKnownPropertyNames } from './misc.js'; //============================================================================= // PublicInterface diff --git a/src/misc.test.ts b/src/misc.test.ts index 6c6375546..946e58677 100644 --- a/src/misc.test.ts +++ b/src/misc.test.ts @@ -1,4 +1,4 @@ -import type { RuntimeObject } from '.'; +import type { RuntimeObject } from './index.js'; import { isNonEmptyArray, isNullOrUndefined, @@ -9,7 +9,7 @@ import { calculateNumberSize, isASCII, calculateStringSize, -} from '.'; +} from './index.js'; describe('miscellaneous', () => { describe('isNonEmptyArray', () => { diff --git a/src/mnemonic.test.ts b/src/mnemonic.test.ts index e42413aa8..42aa7e3cf 100644 --- a/src/mnemonic.test.ts +++ b/src/mnemonic.test.ts @@ -1,7 +1,7 @@ import { convertMnemonicToWordlistIndices, uint8ArrayToMnemonic, -} from './mnemonic'; +} from './mnemonic.js'; const TWELVE_WORD_MNEMONIC = 'bulk riot robust reward museum path chunk health rate soon zone wagon'; diff --git a/src/mnemonic.ts b/src/mnemonic.ts index ea462964b..bcca3b380 100644 --- a/src/mnemonic.ts +++ b/src/mnemonic.ts @@ -1,4 +1,4 @@ -import * as englishWordlist from '@metamask/scure-bip39/dist/wordlists/english'; +import * as englishWordlist from '@metamask/scure-bip39/dist/wordlists/english.js'; const { wordlist } = englishWordlist; diff --git a/src/node.test.ts b/src/node.test.ts index 8f9718bc3..07a5881ce 100644 --- a/src/node.test.ts +++ b/src/node.test.ts @@ -1,4 +1,4 @@ -import * as allExports from './node'; +import * as allExports from './node.js'; describe('node', () => { it('includes Node-specific exports in addition to the cross-platform ones', () => { diff --git a/src/node.ts b/src/node.ts index c81d25a1f..447366cee 100644 --- a/src/node.ts +++ b/src/node.ts @@ -1,2 +1,2 @@ -export * from '.'; -export * from './fs'; +export * from './index.js'; +export * from './fs.js'; diff --git a/src/number.test.ts b/src/number.test.ts index 89aa9d854..92119da80 100644 --- a/src/number.test.ts +++ b/src/number.test.ts @@ -1,5 +1,10 @@ -import { NUMBER_VALUES } from './__fixtures__'; -import { bigIntToHex, hexToBigInt, hexToNumber, numberToHex } from './number'; +import { NUMBER_VALUES } from './__fixtures__/index.js'; +import { + bigIntToHex, + hexToBigInt, + hexToNumber, + numberToHex, +} from './number.js'; describe('numberToHex', () => { it.each(NUMBER_VALUES)( diff --git a/src/number.ts b/src/number.ts index 70e4ae935..6f8b94318 100644 --- a/src/number.ts +++ b/src/number.ts @@ -1,6 +1,6 @@ -import { assert } from './assert'; -import type { Hex } from './hex'; -import { add0x, assertIsHexString } from './hex'; +import { assert } from './assert.js'; +import type { Hex } from './hex.js'; +import { add0x, assertIsHexString } from './hex.js'; /** * Convert a number to a hexadecimal string. This verifies that the number is a diff --git a/src/promise.test.ts b/src/promise.test.ts index 43c0b0660..0d3978ee4 100644 --- a/src/promise.test.ts +++ b/src/promise.test.ts @@ -1,4 +1,4 @@ -import { createDeferredPromise } from './promise'; +import { createDeferredPromise } from './promise.js'; describe('Promise utilities', () => { describe('createDeferredPromise', () => { diff --git a/src/superstruct.test.ts b/src/superstruct.test.ts index 79fe5020d..b988034a3 100644 --- a/src/superstruct.test.ts +++ b/src/superstruct.test.ts @@ -1,6 +1,6 @@ import { assert, is, pattern, string } from '@metamask/superstruct'; -import { definePattern } from './superstruct'; +import { definePattern } from './superstruct.js'; describe('definePattern', () => { const hexPattern = /^0x[0-9a-f]+$/u; diff --git a/src/time.test.ts b/src/time.test.ts index c84f60662..746b9bca7 100644 --- a/src/time.test.ts +++ b/src/time.test.ts @@ -1,4 +1,4 @@ -import { Duration, inMilliseconds, timeSince } from '.'; +import { Duration, inMilliseconds, timeSince } from './index.js'; describe('time utilities', () => { describe('Duration', () => { diff --git a/src/transaction-types.ts b/src/transaction-types.ts index d5474cdd6..846cf8d10 100644 --- a/src/transaction-types.ts +++ b/src/transaction-types.ts @@ -1,5 +1,5 @@ -import type { Bytes } from './bytes'; -import type { Hex } from './hex'; +import type { Bytes } from './bytes.js'; +import type { Hex } from './hex.js'; export type Transaction = | LegacyTransaction diff --git a/src/unitsConversion.test.ts b/src/unitsConversion.test.ts index f8cd58b41..48a23b820 100644 --- a/src/unitsConversion.test.ts +++ b/src/unitsConversion.test.ts @@ -7,7 +7,7 @@ import { numericToBigInt, getValueOfUnit, unitMap, -} from './unitsConversion'; +} from './unitsConversion.js'; // Import the internal function for testing (note: this would normally be exported for testing) // For now we'll test it indirectly through the public functions diff --git a/src/versions.test.ts b/src/versions.test.ts index 28e150a48..13b5da831 100644 --- a/src/versions.test.ts +++ b/src/versions.test.ts @@ -1,4 +1,4 @@ -import type { SemVerRange, SemVerVersion } from './versions'; +import type { SemVerRange, SemVerVersion } from './versions.js'; import { assertIsSemVerRange, assertIsSemVerVersion, @@ -7,7 +7,7 @@ import { isValidSemVerRange, isValidSemVerVersion, satisfiesVersionRange, -} from './versions'; +} from './versions.js'; describe('assertIsSemVerVersion', () => { it('shows descriptive errors', () => { diff --git a/src/versions.ts b/src/versions.ts index 1a67c0659..4be071223 100644 --- a/src/versions.ts +++ b/src/versions.ts @@ -8,8 +8,8 @@ import { validRange as validSemVerRange, } from 'semver'; -import { assertStruct } from './assert'; -import type { Opaque } from './opaque'; +import { assertStruct } from './assert.js'; +import type { Opaque } from './opaque.js'; /** * {@link https://codemix.com/opaque-types-in-javascript/ Opaque} type for SemVer ranges. diff --git a/tsconfig.build.json b/tsconfig.build.json index 0160af457..524df103d 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -3,7 +3,6 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "emitDeclarationOnly": true, "inlineSources": true, "noEmit": false, "outDir": "dist", diff --git a/yarn.lock b/yarn.lock index 659676d4a..e7fb991b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1197,7 +1197,6 @@ __metadata: "@metamask/superstruct": "npm:^3.4.1" "@noble/hashes": "npm:^1.8.0" "@scure/base": "npm:^1.2.6" - "@ts-bridge/cli": "npm:^0.6.4" "@types/debug": "npm:^4.1.7" "@types/jest": "npm:^30.0.0" "@types/jest-when": "npm:^3.5.3" @@ -1223,6 +1222,7 @@ __metadata: oxfmt: "npm:^0.44.0" pony-cause: "npm:^2.1.10" prettier: "npm:^3.3.3" + rimraf: "npm:^5.0.5" semver: "npm:^7.6.3" stdio-mock: "npm:^1.2.0" ts-jest: "npm:^29.4.11" @@ -1894,30 +1894,6 @@ __metadata: languageName: node linkType: hard -"@ts-bridge/cli@npm:^0.6.4": - version: 0.6.4 - resolution: "@ts-bridge/cli@npm:0.6.4" - dependencies: - "@ts-bridge/resolver": "npm:^0.2.0" - chalk: "npm:^5.3.0" - cjs-module-lexer: "npm:^1.3.1" - yargs: "npm:^17.7.2" - peerDependencies: - typescript: ">=4.8.0" - bin: - ts-bridge: ./dist/index.js - tsbridge: ./dist/index.js - checksum: 10/257ee0cacec71c1b3cc018825088e06b4bd554ba5fe95bdb98c4c82edd6cd4bcff1054d351e6c7cdfdd3bd2145a055b5d14fa453a6ece761645e42b48b88e9a0 - languageName: node - linkType: hard - -"@ts-bridge/resolver@npm:^0.2.0": - version: 0.2.0 - resolution: "@ts-bridge/resolver@npm:0.2.0" - checksum: 10/d4cfd1f47e9648a5f9c893b1b076adabde3a57cbe81ef823bcbbcc77a122fb6f06d99f40ff48198f8dc766bfc4b3b351d4e87cfcf2db64f7e6db924eb82a5db1 - languageName: node - linkType: hard - "@tsconfig/node10@npm:^1.0.7": version: 1.0.9 resolution: "@tsconfig/node10@npm:1.0.9" @@ -3126,13 +3102,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.3.0": - version: 5.3.0 - resolution: "chalk@npm:5.3.0" - checksum: 10/6373caaab21bd64c405bfc4bd9672b145647fc9482657b5ea1d549b3b2765054e9d3d928870cdf764fb4aad67555f5061538ff247b8310f110c5c888d92397ea - languageName: node - linkType: hard - "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -3154,13 +3123,6 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.3.1": - version: 1.4.3 - resolution: "cjs-module-lexer@npm:1.4.3" - checksum: 10/d2b92f919a2dedbfd61d016964fce8da0035f827182ed6839c97cac56e8a8077cfa6a59388adfe2bc588a19cef9bbe830d683a76a6e93c51f65852062cfe2591 - languageName: node - linkType: hard - "cjs-module-lexer@npm:^2.2.0": version: 2.2.1 resolution: "cjs-module-lexer@npm:2.2.1" @@ -4386,7 +4348,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": version: 10.5.0 resolution: "glob@npm:10.5.0" dependencies: @@ -6961,6 +6923,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.5": + version: 5.0.10 + resolution: "rimraf@npm:5.0.10" + dependencies: + glob: "npm:^10.3.7" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10/f3b8ce81eecbde4628b07bdf9e2fa8b684e0caea4999acb1e3b0402c695cd41f28cd075609a808e61ce2672f528ca079f675ab1d8e8d5f86d56643a03e0b8d2e + languageName: node + linkType: hard + "run-async@npm:^2.3.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" From a2d588327bab95a3216f19f361137a610e3184b8 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Fri, 4 Sep 2026 21:21:54 +0200 Subject: [PATCH 2/2] fix: point jest-it-up at the renamed jest config test:source runs `jest && jest-it-up`, and jest-it-up defaults to looking for jest.config.js, which is now jest.config.cjs. It supports --config, so point it there. Caught by CI rather than locally: I had been running `yarn jest` directly to work around a broken watchman on this machine, which skipped jest-it-up entirely, so test:source was never actually exercised. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d86a6df72..9d2172404 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "lint:misc": "oxfmt --ignore-path .gitignore", "prepack": "./scripts/prepack.sh", "test": "yarn test:source && yarn test:types", - "test:source": "jest && jest-it-up", + "test:source": "jest && jest-it-up --config jest.config.cjs", "test:types": "tsd --files 'src/*.test-d.ts'", "test:watch": "jest --watch" },