From 49b987cfe80460fe8656ac8bb393d08da5ae2412 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 20 Jan 2024 18:57:38 +0100 Subject: [PATCH 1/2] chore: migrate to `node:test` --- loader.mjs | 44 +++++++++++ package.json | 3 +- tests/Disable.test.ts | 28 +++---- tests/Enable.test.ts | 19 +++-- tests/Up.test.ts | 22 +++++- tests/Use.test.ts | 22 +++++- tests/_runCli.ts | 3 +- tests/httpUtils.test.ts | 75 ++++++++++--------- tests/index.ts | 15 ++++ tests/main.test.ts | 54 +++++++++++-- ...tils.test.ts => npmRegistryUtils.rest._ts} | 2 +- tests/package.json | 1 + .../{recordRequests.js => recordRequests.cjs} | 0 tests/{setupTests.js => setupTests.cjs} | 0 yarn.lock | 73 +++++++++++++++++- 15 files changed, 280 insertions(+), 81 deletions(-) create mode 100644 loader.mjs create mode 100644 tests/index.ts rename tests/{npmRegistryUtils.test.ts => npmRegistryUtils.rest._ts} (99%) create mode 100644 tests/package.json rename tests/{recordRequests.js => recordRequests.cjs} (100%) rename tests/{setupTests.js => setupTests.cjs} (100%) diff --git a/loader.mjs b/loader.mjs new file mode 100644 index 000000000..c869ecdbe --- /dev/null +++ b/loader.mjs @@ -0,0 +1,44 @@ +import {fileURLToPath} from 'node:url'; +import {transform} from 'sucrase'; + +export async function resolve(specifier, context, next) { + if (specifier.endsWith(`.json`)) { + const result = await next(specifier, context); + return {...result, importAttributes: {type: `json`}}; + } + if (specifier[0] === `.` && !specifier.endsWith(`.js`) && !specifier.endsWith(`.cjs`)) specifier += `.ts`; + try { + return await next(specifier, context); + } catch (err) { + if (err?.code === `ERR_MODULE_NOT_FOUND` && specifier.endsWith(`.js`)) { + try { + return await next(specifier.replace(/\.js$/, `.ts`), context); + } catch (err2) { + if (err2) { + err2.cause = err; + throw err2; + } + throw err; + } + } + throw err; + } +} + +export async function load(urlStr, context, next) { + const url = new URL(urlStr); + if (url.pathname.endsWith(`.ts`)) { + const {source} = await next(urlStr, {...context, format: `module`}); + const {code} = transform(source.toString(`utf-8`), { + transforms: [`typescript`], + disableESTransforms: true, + filePath: fileURLToPath(url), + }); + return { + source: urlStr.endsWith(`/types.ts`) ? code.replace(/\] ?= ?"(Npm|Pnpm|Yarn)";/g, s => s.toLowerCase()) : code, + format: `module`, + }; + } else { + return next(urlStr, context); + } +} diff --git a/package.json b/package.json index 4a17d211d..44ff82611 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "nock": "^13.0.4", "proxy-agent": "^6.2.2", "semver": "^7.5.2", + "sucrase": "^3.35.0", "supports-color": "^9.0.0", "tar": "^6.0.1", "ts-node": "^10.0.0", @@ -58,7 +59,7 @@ "prepack": "yarn build", "postpack": "rm -rf dist shims", "typecheck": "tsc --noEmit", - "test": "jest" + "test": "yarn node --loader ./loader.mjs tests/index.ts" }, "files": [ "dist", diff --git a/tests/Disable.test.ts b/tests/Disable.test.ts index 9c93f9d7b..60a70acde 100644 --- a/tests/Disable.test.ts +++ b/tests/Disable.test.ts @@ -1,7 +1,8 @@ -import {describe, beforeEach, it, expect} from '@jest/globals'; import {Filename, ppath, xfs, npath} from '@yarnpkg/fslib'; +import assert from 'node:assert'; import {delimiter} from 'node:path'; import process from 'node:process'; +import {describe, beforeEach, it} from 'node:test'; import {Engine} from '../sources/Engine'; import {SupportedPackageManagerSetWithoutNpm} from '../sources/types'; @@ -29,9 +30,7 @@ describe(`DisableCommand`, () => { const PATH = process.env.PATH; try { process.env.PATH = `${npath.fromPortablePath(cwd)}${delimiter}${PATH}`; - await expect(runCli(cwd, [`disable`])).resolves.toMatchObject({ - exitCode: 0, - }); + assert.strictEqual((await runCli(cwd, [`disable`])).exitCode, 0); } finally { process.env.PATH = PATH; } @@ -40,7 +39,7 @@ describe(`DisableCommand`, () => { return entries.sort(); }); - await expect(sortedEntries).resolves.toEqual([ + assert.deepStrictEqual(await sortedEntries, [ ppath.basename(corepackBin), ppath.basename(dontRemoveBin), ]); @@ -56,11 +55,10 @@ describe(`DisableCommand`, () => { for (const variant of getBinaryNames(binName)) await makeBin(cwd, variant as Filename, {ignorePlatform: true}); - await expect(runCli(cwd, [`disable`, `--install-directory`, npath.fromPortablePath(cwd)])).resolves.toMatchObject({ - exitCode: 0, - }); - - await expect(xfs.readdirPromise(cwd)).resolves.toEqual([ + const runResult = await runCli(cwd, [`disable`, `--install-directory`, npath.fromPortablePath(cwd)]); + assert.strictEqual(runResult.exitCode, 0); + const readdirResult = await xfs.readdirPromise(cwd); + assert.deepStrictEqual(readdirResult, [ ppath.basename(dontRemoveBin), ]); }); @@ -87,9 +85,8 @@ describe(`DisableCommand`, () => { const PATH = process.env.PATH; try { process.env.PATH = `${npath.fromPortablePath(cwd)}${delimiter}${PATH}`; - await expect(runCli(cwd, [`disable`, `yarn`])).resolves.toMatchObject({ - exitCode: 0, - }); + const runResult = await runCli(cwd, [`disable`, `yarn`]); + assert.strictEqual(runResult.exitCode, 0); } finally { process.env.PATH = PATH; } @@ -99,11 +96,10 @@ describe(`DisableCommand`, () => { for (const variant of getBinaryNames(`yarnpkg`)) binNames.delete(variant); - const sortedEntries = xfs.readdirPromise(cwd).then(entries => { + const sortedEntries = await xfs.readdirPromise(cwd).then(entries => { return entries.sort(); }); - - await expect(sortedEntries).resolves.toEqual([...binNames].sort()); + assert.deepStrictEqual(sortedEntries, [...binNames].sort()); }); }); }); diff --git a/tests/Enable.test.ts b/tests/Enable.test.ts index ce9a8a9e3..6c321f689 100644 --- a/tests/Enable.test.ts +++ b/tests/Enable.test.ts @@ -1,7 +1,8 @@ -import {describe, beforeEach, it, expect} from '@jest/globals'; import {Filename, ppath, xfs, npath} from '@yarnpkg/fslib'; +import assert from 'node:assert'; import {delimiter} from 'node:path'; import process from 'node:process'; +import {describe, beforeEach, it} from 'node:test'; import {Engine} from '../sources/Engine'; import {SupportedPackageManagers, SupportedPackageManagerSetWithoutNpm} from '../sources/types'; @@ -24,7 +25,8 @@ describe(`EnableCommand`, () => { const PATH = process.env.PATH; try { process.env.PATH = `${npath.fromPortablePath(cwd)}${delimiter}${PATH}`; - await expect(runCli(cwd, [`enable`])).resolves.toMatchObject({ + const {stdout, stderr, exitCode} = await runCli(cwd, [`enable`]); + assert.deepStrictEqual({stdout, stderr, exitCode}, { stdout: ``, stderr: ``, exitCode: 0, @@ -38,11 +40,12 @@ describe(`EnableCommand`, () => { }); const expectedEntries: Array = [ppath.basename(corepackBin)]; + console.log({SupportedPackageManagerSetWithoutNpm}); for (const packageManager of SupportedPackageManagerSetWithoutNpm) for (const binName of engine.getBinariesFor(packageManager)) expectedEntries.push(...getBinaryNames(binName)); - await expect(sortedEntries).resolves.toEqual(expectedEntries.sort()); + assert.deepStrictEqual(await sortedEntries, expectedEntries.sort()); }); }); @@ -50,7 +53,8 @@ describe(`EnableCommand`, () => { await xfs.mktempPromise(async cwd => { const corepackBin = await makeBin(cwd, `corepack` as Filename); - await expect(runCli(cwd, [`enable`, `--install-directory`, npath.fromPortablePath(cwd)])).resolves.toMatchObject({ + const {stdout, stderr, exitCode} = await runCli(cwd, [`enable`, `--install-directory`, npath.fromPortablePath(cwd)]); + assert.deepStrictEqual({stdout, stderr, exitCode}, { stdout: ``, stderr: ``, exitCode: 0, @@ -65,7 +69,7 @@ describe(`EnableCommand`, () => { for (const binName of engine.getBinariesFor(packageManager)) expectedEntries.push(...getBinaryNames(binName)); - await expect(sortedEntries).resolves.toEqual(expectedEntries.sort()); + assert.deepStrictEqual(await sortedEntries, expectedEntries.sort()); }); }); @@ -76,7 +80,8 @@ describe(`EnableCommand`, () => { const PATH = process.env.PATH; try { process.env.PATH = `${npath.fromPortablePath(cwd)}${delimiter}${PATH}`; - await expect(runCli(cwd, [`enable`, `yarn`])).resolves.toMatchObject({ + const {stdout, stderr, exitCode} = await runCli(cwd, [`enable`, `yarn`]); + assert.deepStrictEqual({stdout, stderr, exitCode}, { stdout: ``, stderr: ``, exitCode: 0, @@ -93,7 +98,7 @@ describe(`EnableCommand`, () => { for (const binName of engine.getBinariesFor(SupportedPackageManagers.Yarn)) expectedEntries.push(...getBinaryNames(binName)); - await expect(sortedEntries).resolves.toEqual(expectedEntries.sort()); + assert.deepStrictEqual(await sortedEntries, expectedEntries.sort()); }); }); }); diff --git a/tests/Up.test.ts b/tests/Up.test.ts index 1c78afdc5..fd4f9f0ba 100644 --- a/tests/Up.test.ts +++ b/tests/Up.test.ts @@ -1,8 +1,22 @@ -import {describe, beforeEach, it, expect} from '@jest/globals'; -import {ppath, xfs, npath} from '@yarnpkg/fslib'; -import process from 'node:process'; +import {ppath, xfs, npath} from '@yarnpkg/fslib'; +import assert from 'node:assert'; +import process from 'node:process'; +import {describe, beforeEach, it} from 'node:test'; + +import {runCli} from './_runCli'; + +function expect(promise: Promise>) { + return { + resolves: { + async toMatchObject(expected: Record) { + const result = await promise; + + assert.deepStrictEqual(Object.fromEntries(Object.entries(result).filter(e => e[0] in expected)), expected); + }, + }, + }; +} -import {runCli} from './_runCli'; beforeEach(async () => { process.env.COREPACK_HOME = npath.fromPortablePath(await xfs.mktempPromise()); diff --git a/tests/Use.test.ts b/tests/Use.test.ts index 10deef19e..c40e553cc 100644 --- a/tests/Use.test.ts +++ b/tests/Use.test.ts @@ -1,8 +1,22 @@ -import {describe, beforeEach, it, expect} from '@jest/globals'; -import {ppath, xfs, npath} from '@yarnpkg/fslib'; -import process from 'node:process'; +import {ppath, xfs, npath} from '@yarnpkg/fslib'; +import assert from 'node:assert'; +import process from 'node:process'; +import {describe, beforeEach, it} from 'node:test'; + +import {runCli} from './_runCli'; + +function expect(promise: Promise>) { + return { + resolves: { + async toMatchObject(expected: Record) { + const result = await promise; + + assert.deepStrictEqual(Object.fromEntries(Object.entries(result).filter(e => e[0] in expected)), expected); + }, + }, + }; +} -import {runCli} from './_runCli'; beforeEach(async () => { process.env.COREPACK_HOME = npath.fromPortablePath(await xfs.mktempPromise()); diff --git a/tests/_runCli.ts b/tests/_runCli.ts index e028af751..f758d469d 100644 --- a/tests/_runCli.ts +++ b/tests/_runCli.ts @@ -1,5 +1,6 @@ import {PortablePath, npath} from '@yarnpkg/fslib'; import {spawn} from 'child_process'; +import {fileURLToPath} from 'url'; export async function runCli(cwd: PortablePath, argv: Array): Promise<{exitCode: number | null, stdout: string, stderr: string}> { const out: Array = []; @@ -8,7 +9,7 @@ export async function runCli(cwd: PortablePath, argv: Array): Promise<{e return new Promise((resolve, reject) => { if (process.env.RUN_CLI_ID) (process.env.RUN_CLI_ID as any)++; - const child = spawn(process.execPath, [`--no-warnings`, `-r`, require.resolve(`./recordRequests.js`), require.resolve(`../dist/corepack.js`), ...argv], { + const child = spawn(process.execPath, [`--no-warnings`, `-r`, fileURLToPath(import.meta.resolve(`./recordRequests.cjs`)), fileURLToPath(import.meta.resolve(`../dist/corepack.js`)), ...argv], { cwd: npath.fromPortablePath(cwd), env: process.env, stdio: `pipe`, diff --git a/tests/httpUtils.test.ts b/tests/httpUtils.test.ts index 4676be3cf..2e8242321 100644 --- a/tests/httpUtils.test.ts +++ b/tests/httpUtils.test.ts @@ -1,13 +1,34 @@ -import {jest, describe, beforeEach, beforeAll, it, expect} from '@jest/globals'; - -import {fetchUrlStream} from '../sources/httpUtils'; +import assert from 'node:assert'; +import {describe, before as beforeAll, it} from 'node:test'; + +import {fetchUrlStream} from '../sources/httpUtils'; + +function doMock (pkg: string, replacer: (...args: Array) => unknown) { + const actualPath = require.resolve(pkg); + if (arguments.length === 1) { + require.cache[actualPath] = require(`../__mocks__/${pkg}`); + } else { + const actual = require(pkg); + const Module = require(`node:module`); // eslint-disable-line global-require + require.cache[actualPath] = new Module(actualPath, module); + Object.defineProperties(require.cache[actualPath], { + exports: { + // @ts-expect-error TS is wrong + __proto__: null, + value: replacer(actual), + }, + // @ts-expect-error TS is wrong + resetFn: {__proto__: null, value: replacer.bind(null, actual)}, + }); + } +} describe(`http utils fetchUrlStream`, () => { const getUrl = (statusCode: number | string, redirectCode?: number | string) => `https://registry.example.org/answered/${statusCode}${redirectCode ? `?redirectCode=${redirectCode}` : ``}`; - const httpsGetFn = jest.fn((url: string, _, callback: (response: any) => void) => { + const httpsGetFn = ((url: string, _: never, callback: (response: any) => void) => { const parsedURL = new URL(url); const statusCode = parsedURL.pathname.slice(parsedURL.pathname.lastIndexOf(`/`) + 1); const response = {url, statusCode: +statusCode}; @@ -37,59 +58,39 @@ describe(`http utils fetchUrlStream`, () => { }); beforeAll(() => { - jest.doMock(`https`, () => ({ + doMock(`https`, () => ({ get: httpsGetFn, Agent: class Agent {}, })); }); - beforeEach(() => { - httpsGetFn.mockClear(); - }); - it(`correct response answered statusCode should be >= 200 and < 300`, async () => { - await expect(fetchUrlStream(getUrl(200))).resolves.toMatchObject({ - statusCode: 200, - }); - - await expect(fetchUrlStream(getUrl(299))).resolves.toMatchObject({ - statusCode: 299, - }); - - expect(httpsGetFn).toHaveBeenCalledTimes(2); + assert.strictEqual((await fetchUrlStream(getUrl(200))).statusCode, 200); + assert.strictEqual((await fetchUrlStream(getUrl(299))).statusCode, 299); }); it(`bad response`, async () => { - await expect(fetchUrlStream(getUrl(300))).rejects.toThrowError(); - await expect(fetchUrlStream(getUrl(199))).rejects.toThrowError(); + await assert.rejects(fetchUrlStream(getUrl(300))); + await assert.rejects(fetchUrlStream(getUrl(199))); }); it(`redirection with correct response`, async () => { - await expect(fetchUrlStream(getUrl(301, 200))).resolves.toMatchObject({ - statusCode: 200, - }); - - expect(httpsGetFn).toHaveBeenCalledTimes(2); - - await expect(fetchUrlStream(getUrl(308, 299))).resolves.toMatchObject({ - statusCode: 299, - }); - - expect(httpsGetFn).toHaveBeenCalledTimes(4); + assert.strictEqual((await fetchUrlStream(getUrl(301, 200))).statusCode, 200); + assert.strictEqual((await fetchUrlStream(getUrl(308, 299))).statusCode, 299); }); it(`redirection with bad response`, async () => { - await expect(fetchUrlStream(getUrl(301, 300))).rejects.toThrowError(); - await expect(fetchUrlStream(getUrl(308, 199))).rejects.toThrowError(); - await expect(fetchUrlStream(getUrl(301, 302))).rejects.toThrowError(); - await expect(fetchUrlStream(getUrl(307))).rejects.toThrowError(); + await assert.rejects(fetchUrlStream(getUrl(301, 300))); + await assert.rejects(fetchUrlStream(getUrl(308, 199))); + await assert.rejects(fetchUrlStream(getUrl(301, 302))); + await assert.rejects(fetchUrlStream(getUrl(307))); }); it(`rejects with error`, async () => { - await expect(fetchUrlStream(getUrl(`error`))).rejects.toThrowError(); + await assert.rejects(fetchUrlStream(getUrl(`error`))); }); it(`rejects when redirection with error`, async () => { - await expect(fetchUrlStream(getUrl(307, `error`))).rejects.toThrowError(); + await assert.rejects(fetchUrlStream(getUrl(307, `error`))); }); }); diff --git a/tests/index.ts b/tests/index.ts new file mode 100644 index 000000000..8f1c20833 --- /dev/null +++ b/tests/index.ts @@ -0,0 +1,15 @@ +import fs from "node:fs/promises"; + +async function* findTestFiles(url: URL): AsyncGenerator { + for await (const dirent of await fs.opendir(url)) { + if (dirent.name === "node_modules") continue; + + if (dirent.isDirectory()) + yield* findTestFiles(new URL(`${dirent.name}/`, url)); + else if (dirent.name.endsWith(".test.ts")) yield new URL(dirent.name, url); + } +} + +for await (const file of findTestFiles(new URL("../", import.meta.url))) { + await import(file as any); +} diff --git a/tests/main.test.ts b/tests/main.test.ts index 83bacb94c..fdba00e55 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -1,6 +1,7 @@ -import {beforeEach, it, expect} from '@jest/globals'; import {Filename, ppath, xfs, npath, PortablePath} from '@yarnpkg/fslib'; +import assert from 'node:assert'; import process from 'node:process'; +import {beforeEach, it} from 'node:test'; import config from '../config.json'; @@ -8,6 +9,44 @@ import {runCli} from './_runCli'; let corepackHome!: PortablePath; +function expect(promise: Promise | Array>>) { + return { + resolves: { + async toMatchObject(expected: Record | Array>) { + const result = await promise; + + if (Array.isArray(expected) || Array.isArray(result)) { + await Promise.all((result as Array>).map(result => this.toMatchObject(result as any))); + return; + } + + + if (Object.values(expected).every(val => typeof val === `string` || typeof val === `number`)) { + assert.deepStrictEqual(result, expected); + return; + } + for (const key of Object.keys(expected)) { + if (typeof expected[key] === `function`) { + (expected[key] as (actual: unknown) => void)(result[key]); + } else if (typeof expected[key] !== `object`) { + assert.strictEqual(result[key], expected[key]); + } else if (expected[key] instanceof RegExp) { + assert.match(result[key] as string, expected[key] as RegExp); + } else { + assert.deepStrictEqual(result[key], expected[key]); + } + } + }, + }, + }; +} + +expect.not = { + stringMatching(regex: RegExp) { + return (str: string) => assert.doesNotMatch(str, regex); + }, +}, + beforeEach(async () => { corepackHome = await xfs.mktempPromise(); @@ -157,10 +196,9 @@ it(`should expose its root to spawned processes`, async () => { packageManager: `npm@6.14.2`, }); - await expect(runCli(cwd, [`npm`, `run`, `env`])).resolves.toMatchObject({ - exitCode: 0, - stdout: expect.stringContaining(`COREPACK_ROOT=${npath.dirname(__dirname)}`), - }); + const result = await runCli(cwd, [`npm`, `run`, `env`]); + assert.ok(result.stdout.includes(`COREPACK_ROOT=${npath.dirname(__dirname)}`), new Error(`${result.stdout} does not contain ${`COREPACK_ROOT=${npath.dirname(__dirname)}`}`)); + assert.strictEqual(result.exitCode, 0); }); }); @@ -260,7 +298,7 @@ it(`should allow to call "corepack install -g" with a tag`, async () => { }); await expect(runCli(cwd, [`npm`, `--version`])).resolves.toMatchObject({ - stdout: expect.stringMatching(/^7\./), + stdout: /^7\./, stderr: ``, exitCode: 0, }); @@ -418,7 +456,7 @@ it(`should support disabling the network accesses from the environment`, async ( }); await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ - stdout: expect.stringContaining(`Network access disabled by the environment`), + stdout: /Network access disabled by the environment/, stderr: ``, exitCode: 1, }); @@ -636,7 +674,7 @@ it(`should not preserve the process.exitCode when a package manager throws`, asy await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ exitCode: 1, stdout: ``, - stderr: expect.stringContaining(`foo`), + stderr: /foo/, }); }); }); diff --git a/tests/npmRegistryUtils.test.ts b/tests/npmRegistryUtils.rest._ts similarity index 99% rename from tests/npmRegistryUtils.test.ts rename to tests/npmRegistryUtils.rest._ts index 1ee82a635..d696cc9ed 100644 --- a/tests/npmRegistryUtils.test.ts +++ b/tests/npmRegistryUtils.rest._ts @@ -1,6 +1,6 @@ -import {jest, describe, beforeEach, afterEach, it, expect} from '@jest/globals'; import {Buffer} from 'node:buffer'; import process from 'node:process'; +import {jest, describe, beforeEach, afterEach, it, expect} from 'node:test'; import {fetchAsJson as httpFetchAsJson} from '../sources/httpUtils'; import {DEFAULT_HEADERS, DEFAULT_NPM_REGISTRY_URL, fetchAsJson} from '../sources/npmRegistryUtils'; diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 000000000..089153bcb --- /dev/null +++ b/tests/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/tests/recordRequests.js b/tests/recordRequests.cjs similarity index 100% rename from tests/recordRequests.js rename to tests/recordRequests.cjs diff --git a/tests/setupTests.js b/tests/setupTests.cjs similarity index 100% rename from tests/setupTests.js rename to tests/setupTests.cjs diff --git a/yarn.lock b/yarn.lock index bba984e59..b12724fdb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1645,6 +1645,13 @@ __metadata: languageName: node linkType: hard +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 + languageName: node + linkType: hard + "anymatch@npm:^3.0.3": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -2132,6 +2139,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -2175,6 +2189,7 @@ __metadata: nock: "npm:^13.0.4" proxy-agent: "npm:^6.2.2" semver: "npm:^7.5.2" + sucrase: "npm:^3.35.0" supports-color: "npm:^9.0.0" tar: "npm:^6.0.1" ts-node: "npm:^10.0.0" @@ -4628,6 +4643,17 @@ __metadata: languageName: node linkType: hard +"mz@npm:^2.7.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: "npm:^1.0.0" + object-assign: "npm:^4.0.1" + thenify-all: "npm:^1.0.0" + checksum: 103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -4721,7 +4747,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.1.1": +"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 @@ -4988,7 +5014,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.4": +"pirates@npm:^4.0.1, pirates@npm:^4.0.4": version: 4.0.6 resolution: "pirates@npm:4.0.6" checksum: 00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 @@ -5592,6 +5618,24 @@ __metadata: languageName: node linkType: hard +"sucrase@npm:^3.35.0": + version: 3.35.0 + resolution: "sucrase@npm:3.35.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.2" + commander: "npm:^4.0.0" + glob: "npm:^10.3.10" + lines-and-columns: "npm:^1.1.6" + mz: "npm:^2.7.0" + pirates: "npm:^4.0.1" + ts-interface-checker: "npm:^0.1.9" + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: ac85f3359d2c2ecbf5febca6a24ae9bf96c931f05fde533c22a94f59c6a74895e5d5f0e871878dfd59c2697a75ebb04e4b2224ef0bfc24ca1210735c2ec191ef + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -5665,6 +5709,24 @@ __metadata: languageName: node linkType: hard +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: "npm:>= 3.1.0 < 4" + checksum: 9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: "npm:^1.0.0" + checksum: f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -5697,6 +5759,13 @@ __metadata: languageName: node linkType: hard +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7 + languageName: node + linkType: hard + "ts-node@npm:^10.0.0": version: 10.9.2 resolution: "ts-node@npm:10.9.2" From 4e256c0eebe062a613bf2aed65db3f4b1fc4c7cb Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 20 Jan 2024 19:25:04 +0100 Subject: [PATCH 2/2] use pirates instead of ESM loader --- loader.mjs | 44 ------------ package.json | 3 +- tests/Enable.test.ts | 1 - tests/_runCli.ts | 3 +- tests/index.js | 69 +++++++++++++++++++ tests/index.ts | 15 ---- tests/package.json | 1 - .../{recordRequests.cjs => recordRequests.js} | 0 tests/{setupTests.cjs => setupTests.js} | 0 yarn.lock | 3 +- 10 files changed, 74 insertions(+), 65 deletions(-) delete mode 100644 loader.mjs create mode 100644 tests/index.js delete mode 100644 tests/index.ts delete mode 100644 tests/package.json rename tests/{recordRequests.cjs => recordRequests.js} (100%) rename tests/{setupTests.cjs => setupTests.js} (100%) diff --git a/loader.mjs b/loader.mjs deleted file mode 100644 index c869ecdbe..000000000 --- a/loader.mjs +++ /dev/null @@ -1,44 +0,0 @@ -import {fileURLToPath} from 'node:url'; -import {transform} from 'sucrase'; - -export async function resolve(specifier, context, next) { - if (specifier.endsWith(`.json`)) { - const result = await next(specifier, context); - return {...result, importAttributes: {type: `json`}}; - } - if (specifier[0] === `.` && !specifier.endsWith(`.js`) && !specifier.endsWith(`.cjs`)) specifier += `.ts`; - try { - return await next(specifier, context); - } catch (err) { - if (err?.code === `ERR_MODULE_NOT_FOUND` && specifier.endsWith(`.js`)) { - try { - return await next(specifier.replace(/\.js$/, `.ts`), context); - } catch (err2) { - if (err2) { - err2.cause = err; - throw err2; - } - throw err; - } - } - throw err; - } -} - -export async function load(urlStr, context, next) { - const url = new URL(urlStr); - if (url.pathname.endsWith(`.ts`)) { - const {source} = await next(urlStr, {...context, format: `module`}); - const {code} = transform(source.toString(`utf-8`), { - transforms: [`typescript`], - disableESTransforms: true, - filePath: fileURLToPath(url), - }); - return { - source: urlStr.endsWith(`/types.ts`) ? code.replace(/\] ?= ?"(Npm|Pnpm|Yarn)";/g, s => s.toLowerCase()) : code, - format: `module`, - }; - } else { - return next(urlStr, context); - } -} diff --git a/package.json b/package.json index 44ff82611..dcc05c678 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "eslint-plugin-arca": "^0.16.0", "jest": "^29.0.0", "nock": "^13.0.4", + "pirates": "^4.0.6", "proxy-agent": "^6.2.2", "semver": "^7.5.2", "sucrase": "^3.35.0", @@ -59,7 +60,7 @@ "prepack": "yarn build", "postpack": "rm -rf dist shims", "typecheck": "tsc --noEmit", - "test": "yarn node --loader ./loader.mjs tests/index.ts" + "test": "yarn node tests" }, "files": [ "dist", diff --git a/tests/Enable.test.ts b/tests/Enable.test.ts index 6c321f689..3c9d846ba 100644 --- a/tests/Enable.test.ts +++ b/tests/Enable.test.ts @@ -40,7 +40,6 @@ describe(`EnableCommand`, () => { }); const expectedEntries: Array = [ppath.basename(corepackBin)]; - console.log({SupportedPackageManagerSetWithoutNpm}); for (const packageManager of SupportedPackageManagerSetWithoutNpm) for (const binName of engine.getBinariesFor(packageManager)) expectedEntries.push(...getBinaryNames(binName)); diff --git a/tests/_runCli.ts b/tests/_runCli.ts index f758d469d..e028af751 100644 --- a/tests/_runCli.ts +++ b/tests/_runCli.ts @@ -1,6 +1,5 @@ import {PortablePath, npath} from '@yarnpkg/fslib'; import {spawn} from 'child_process'; -import {fileURLToPath} from 'url'; export async function runCli(cwd: PortablePath, argv: Array): Promise<{exitCode: number | null, stdout: string, stderr: string}> { const out: Array = []; @@ -9,7 +8,7 @@ export async function runCli(cwd: PortablePath, argv: Array): Promise<{e return new Promise((resolve, reject) => { if (process.env.RUN_CLI_ID) (process.env.RUN_CLI_ID as any)++; - const child = spawn(process.execPath, [`--no-warnings`, `-r`, fileURLToPath(import.meta.resolve(`./recordRequests.cjs`)), fileURLToPath(import.meta.resolve(`../dist/corepack.js`)), ...argv], { + const child = spawn(process.execPath, [`--no-warnings`, `-r`, require.resolve(`./recordRequests.js`), require.resolve(`../dist/corepack.js`), ...argv], { cwd: npath.fromPortablePath(cwd), env: process.env, stdio: `pipe`, diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 000000000..e5be0b93a --- /dev/null +++ b/tests/index.js @@ -0,0 +1,69 @@ +'use strict'; + +const fs = require("fs/promises"); +const path = require("path"); + +const pirates = require('pirates'); +const { transform } = require('sucrase'); + +/** + * @param {string} extension File extension. All files with said file extension + * that go through the CJS loader will be transpiled. + * @param {import('sucrase').Options} [options] Options to pass to the Sucrase transform function. + * @returns {import('pirates').RevertFunction} + */ +function addHook(extension, options) { + return pirates.addHook( + (code, filePath) => { + if (!options?.transforms) { + // If there are no Sucrase transform necessary, we can return the code as is. + return code; + } + const { code: transformedCode, sourceMap } = transform( + // Replace dynamic imports of `.ts` files with `require`. + // Hooking into the Node.js ESM resolver would take more effort. + code, + { + ...options, + sourceMapOptions: { compiledFilename: filePath }, + filePath, + }, + ); + // Split the source map comment across two strings so that tools like + // source-map-support don't accidentally interpret it as a source map + // comment for this file. + const sourceMappingURL = 'sourceMappingURL'; + const suffix = `//# ${sourceMappingURL}=data:application/json,${encodeURIComponent( + JSON.stringify(sourceMap), + )}`; + return `${filePath.endsWith(`types.ts`) ? transformedCode.replace(/\] ?= ?"(Npm|Pnpm|Yarn)";/g, s => s.toLowerCase()) :transformedCode}\n${suffix}`; + }, + { exts: [extension] }, + ); +} + + addHook('.ts', { + transforms: ['imports', 'typescript'], + disableESTransforms: true, + // We ask Sucrase to preserve dynamic imports because we replace them + // ourselves. + preserveDynamicImport: true, + }); + + + +async function* findTestFiles(dirpath) { + for await (const dirent of await fs.opendir(dirpath)) { + if (dirent.name === "node_modules") continue; + + if (dirent.isDirectory()) + yield* findTestFiles(path.join(dirpath, dirent.name)); + else if (dirent.name.endsWith(".test.ts")) yield path.join(dirpath, dirent.name); + } +} + +(async () => { + for await (const file of findTestFiles(__dirname)) { + require(file); + } +})() diff --git a/tests/index.ts b/tests/index.ts deleted file mode 100644 index 8f1c20833..000000000 --- a/tests/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import fs from "node:fs/promises"; - -async function* findTestFiles(url: URL): AsyncGenerator { - for await (const dirent of await fs.opendir(url)) { - if (dirent.name === "node_modules") continue; - - if (dirent.isDirectory()) - yield* findTestFiles(new URL(`${dirent.name}/`, url)); - else if (dirent.name.endsWith(".test.ts")) yield new URL(dirent.name, url); - } -} - -for await (const file of findTestFiles(new URL("../", import.meta.url))) { - await import(file as any); -} diff --git a/tests/package.json b/tests/package.json deleted file mode 100644 index 089153bcb..000000000 --- a/tests/package.json +++ /dev/null @@ -1 +0,0 @@ -{"type":"module"} diff --git a/tests/recordRequests.cjs b/tests/recordRequests.js similarity index 100% rename from tests/recordRequests.cjs rename to tests/recordRequests.js diff --git a/tests/setupTests.cjs b/tests/setupTests.js similarity index 100% rename from tests/setupTests.cjs rename to tests/setupTests.js diff --git a/yarn.lock b/yarn.lock index b12724fdb..2742c681a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2187,6 +2187,7 @@ __metadata: eslint-plugin-arca: "npm:^0.16.0" jest: "npm:^29.0.0" nock: "npm:^13.0.4" + pirates: "npm:^4.0.6" proxy-agent: "npm:^6.2.2" semver: "npm:^7.5.2" sucrase: "npm:^3.35.0" @@ -5014,7 +5015,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.1, pirates@npm:^4.0.4": +"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.6": version: 4.0.6 resolution: "pirates@npm:4.0.6" checksum: 00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36