diff --git a/.config/vitest.config.mts b/.config/vitest.config.mts index 7cfda11c6..26de6c2ad 100644 --- a/.config/vitest.config.mts +++ b/.config/vitest.config.mts @@ -8,7 +8,13 @@ export default defineConfig({ path.join(path.dirname(testPath), '.snapshots', path.basename(testPath) + snapExt) }, resolve: { - // Allow importing the current package under test via `~package` - alias: [{ find: /^~package$/, replacement: path.resolve(process.cwd()) }] + // Allow importing the current package under test via `~package`. + // Point directly at the built entry so dynamic imports in tests work consistently. + alias: [ + { + find: /^~package$/, + replacement: path.resolve(process.cwd(), 'dist/index.js') + } + ] } }); diff --git a/packages/auto-install/package.json b/packages/auto-install/package.json index 8d2346268..9080f5e7d 100755 --- a/packages/auto-install/package.json +++ b/packages/auto-install/package.json @@ -13,34 +13,33 @@ "author": "Rich Harris", "homepage": "https://github.com/rollup/plugins/tree/master/packages/auto-install/#readme", "bugs": "https://github.com/rollup/plugins/issues", - "main": "./dist/cjs/index.js", - "module": "./dist/es/index.js", + "type": "module", "exports": { - "types": "./types/index.d.ts", - "import": "./dist/es/index.js", - "default": "./dist/cjs/index.js" + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } }, "engines": { - "node": ">=14.0.0" + "node": ">=20.19.0" }, "scripts": { - "build": "rollup -c", + "build": "tsc --project tsconfig.json", "ci:coverage": "nyc pnpm test && nyc report --reporter=text-lcov > coverage.lcov", "ci:lint": "pnpm build && pnpm lint", "ci:lint:commits": "commitlint --from=${CIRCLE_BRANCH} --to=${CIRCLE_SHA1}", - "ci:test": "pnpm test -- --verbose", + "ci:test": "pnpm test -- --reporter=verbose", "prebuild": "del-cli dist", "prepare": "if [ ! -d 'dist' ]; then pnpm build; fi", "prerelease": "pnpm build", "pretest": "pnpm build", "release": "pnpm --workspace-root package:release $(pwd)", - "test": "ava", + "test": "vitest --config ../../.config/vitest.config.mts run", "test:ts": "tsc --noEmit" }, "files": [ "dist", - "!dist/**/*.map", - "types", "README.md", "LICENSE" ], @@ -53,7 +52,7 @@ "modules" ], "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "rollup": "^4" }, "peerDependenciesMeta": { "rollup": { @@ -62,13 +61,13 @@ }, "devDependencies": { "@rollup/plugin-node-resolve": "^15.0.0", - "@rollup/plugin-typescript": "^9.0.1", "del": "^6.1.1", + "del-cli": "^5.0.0", "node-noop": "^1.0.0", - "rollup": "^4.0.0-24", - "typescript": "^4.8.3" + "rollup": "^4.0.0", + "typescript": "catalog:" }, - "types": "./types/index.d.ts", + "types": "./dist/index.d.ts", "ava": { "workerThreads": false, "files": [ diff --git a/packages/auto-install/rollup.config.mjs b/packages/auto-install/rollup.config.mjs deleted file mode 100644 index 2a28aaaf6..000000000 --- a/packages/auto-install/rollup.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { readFileSync } from 'fs'; - -import { createConfig } from '../../shared/rollup.config.mjs'; - -export default createConfig({ - pkg: JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')) -}); diff --git a/packages/auto-install/src/index.ts b/packages/auto-install/src/index.ts index 13dbd7b07..57885dd50 100644 --- a/packages/auto-install/src/index.ts +++ b/packages/auto-install/src/index.ts @@ -1,30 +1,63 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import mod from 'module'; -import { exec } from 'child_process'; -import { promisify } from 'util'; +import fs from 'node:fs'; +import path from 'node:path'; +import { builtinModules } from 'node:module'; +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; import type { Plugin } from 'rollup'; -import type { RollupAutoInstallOptions } from '../types'; +type PackageManager = 'npm' | 'yarn' | 'pnpm'; + +export interface RollupAutoInstallOptions { + /** + * Specifies the location on disk of the target `package.json` file. + * If the file doesn't exist, it will be created by the plugin, + * as package managers need to populate the `dependencies` property. + * @default '{cwd}/package.json' + */ + pkgFile?: string; + + /** + * Specifies the package manager to use. + * If not specified, the plugin will default to `yarn` if `yarn.lock` exists, + * to `pnpm` if `pnpm-lock.yaml` exists, or `npm` otherwise. + */ + manager?: PackageManager; + + /** + * Test-only override of package manager commands. + * @internal + */ + commands?: Partial>; +} const execAsync = promisify(exec); export default function autoInstall(opts: RollupAutoInstallOptions = {}): Plugin { + // Restore the historic `defaults` object (including `commands`) so tests can + // optionally override command strings via options. const defaults = { // intentionally undocumented options. used for tests commands: { npm: 'npm install', - pnpm: 'pnpm install', + pnpm: 'pnpm add', yarn: 'yarn add' - }, + } as Record, manager: fs.existsSync('yarn.lock') ? 'yarn' : fs.existsSync('pnpm-lock.yaml') ? 'pnpm' : 'npm', - pkgFile: path.resolve(opts.pkgFile || 'package.json') + pkgFile: 'package.json' + } as const; + + // Shallow-merge, with a one-level deep merge for `commands` to allow partial overrides in tests + const options = { + ...defaults, + ...opts, + commands: { ...defaults.commands, ...(opts.commands || {}) } }; - const options = Object.assign({}, defaults, opts); - const { manager, pkgFile } = options; - const validManagers = ['npm', 'yarn', 'pnpm']; + const { manager } = options; + const pkgFile = path.resolve(options.pkgFile); + + const validManagers = Object.keys(options.commands) as PackageManager[]; if (!validManagers.includes(manager)) { throw new RangeError( @@ -41,7 +74,15 @@ export default function autoInstall(opts: RollupAutoInstallOptions = {}): Plugin pkg = {}; } - const installed = new Set(Object.keys(pkg.dependencies || {}).concat(mod.builtinModules)); + // Normalize core module names to include both `fs` and `node:fs` forms so we never try to + // install built-ins regardless of how Rollup reports them or how they are imported. + const coreModules = new Set([ + ...builtinModules, + ...builtinModules.filter((m) => m.startsWith('node:')).map((m) => m.slice(5)), + ...builtinModules.filter((m) => !m.startsWith('node:')).map((m) => `node:${m}`) + ]); + + const installed = new Set([...Object.keys(pkg.dependencies || {}), ...coreModules]); const cmd = options.commands[manager]; return { diff --git a/packages/auto-install/test/fixtures/yarn/.gitkeep b/packages/auto-install/test/fixtures/yarn/.gitkeep new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/auto-install/test/fixtures/yarn/.gitkeep @@ -0,0 +1 @@ + diff --git a/packages/auto-install/test/fixtures/yarn/yarn.lock b/packages/auto-install/test/fixtures/yarn/yarn.lock deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/auto-install/test/npm-bare.js b/packages/auto-install/test/npm-bare.js deleted file mode 100644 index 95469f7f6..000000000 --- a/packages/auto-install/test/npm-bare.js +++ /dev/null @@ -1,33 +0,0 @@ -const { readFileSync } = require('fs'); -const { join } = require('path'); - -const test = require('ava'); -const del = require('del'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const { rollup } = require('rollup'); - -const autoInstall = require('..'); - -const cwd = join(__dirname, 'fixtures/npm-bare'); -const file = join(cwd, 'output/bundle.js'); -const input = join(cwd, '../input.js'); - -process.chdir(cwd); - -test('npm, bare', async (t) => { - t.timeout(50000); - await rollup({ - input, - output: { - file, - format: 'cjs' - }, - plugins: [autoInstall(), nodeResolve()] - }); - t.snapshot(readFileSync('package.json', 'utf-8')); - t.truthy(readFileSync('package-lock.json', 'utf-8').includes('"node-noop"')); -}); - -test.after(async () => { - await del(['node_modules', 'package.json', 'package-lock.json']); -}); diff --git a/packages/auto-install/test/npm-bare.test.ts b/packages/auto-install/test/npm-bare.test.ts new file mode 100644 index 000000000..6dacca629 --- /dev/null +++ b/packages/auto-install/test/npm-bare.test.ts @@ -0,0 +1,86 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import del from 'del'; +import { it, afterAll } from 'vitest'; +import { rollup } from 'rollup'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +// Dynamically import the plugin within gated tests to avoid Node <20 execution + +const DIR = fileURLToPath(new URL('.', import.meta.url)); +const cwd = path.join(DIR, 'fixtures/npm-bare'); +const file = path.join(cwd, 'output/bundle.js'); +// Use a local input inside the cwd so Node resolution finds packages installed +// by the test (e.g., on Windows where upward-only resolution won't see +// `npm-bare/node_modules` from `fixtures/input.js`). +const input = path.join(cwd, 'input.local.js'); +const pkgFile = path.join(cwd, 'package.json'); + +// Helper to temporarily disable slow npm features during the test +function stubNpmQuietEnv() { + const keys = [ + 'npm_config_audit', + 'npm_config_fund', + 'npm_config_progress', + 'npm_config_update_notifier', + 'npm_config_loglevel' + ] as const; + const prev: Record = {}; + for (const k of keys) prev[k] = process.env[k]; + process.env.npm_config_audit = 'false'; + process.env.npm_config_fund = 'false'; + process.env.npm_config_progress = 'false'; + process.env.npm_config_update_notifier = 'false'; + process.env.npm_config_loglevel = 'error'; + return () => { + for (const k of keys) { + const v = prev[k]; + if (v == null) delete (process.env as any)[k]; + else (process.env as any)[k] = v; + } + }; +} +const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number); +const RUN_ON_THIS_NODE = NODE_MAJOR > 20 || (NODE_MAJOR === 20 && NODE_MINOR >= 19); + +// npm installs can be slower than pnpm/yarn on CI; allow extra time. +it.runIf(RUN_ON_THIS_NODE)( + 'npm, bare', + async () => { + const restoreEnv = stubNpmQuietEnv(); + const prevCwd = process.cwd(); + // Create a local copy of the shared input so resolution starts from `cwd`. + fs.copyFileSync(path.join(cwd, '../input.js'), input); + process.chdir(cwd); + try { + const { default: autoInstall } = await import('~package'); + const bundle = await rollup({ + input, + // @ts-expect-error - rollup() ignores output here but tests kept it historically + output: { file, format: 'es' }, + plugins: [autoInstall({ pkgFile, manager: 'npm' }), nodeResolve()] + }); + await bundle.close(); + + const json = JSON.parse(fs.readFileSync(pkgFile, 'utf-8')); + if (!json.dependencies || !json.dependencies['node-noop']) { + throw new Error('Expected node-noop to be added to dependencies'); + } + } finally { + process.chdir(prevCwd); + try { + fs.unlinkSync(input); + } catch { + /* ignore cleanup errors */ + } + restoreEnv(); + } + }, + 60000 +); + +afterAll(async () => { + await del(['node_modules', 'package.json', 'package-lock.json'], { cwd }); +}); diff --git a/packages/auto-install/test/npm.js b/packages/auto-install/test/npm.js deleted file mode 100644 index 02fff0325..000000000 --- a/packages/auto-install/test/npm.js +++ /dev/null @@ -1,54 +0,0 @@ -const { readFileSync, writeFileSync } = require('fs'); -const { join } = require('path'); - -const test = require('ava'); -const del = require('del'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const { rollup } = require('rollup'); - -const autoInstall = require('..'); - -const cwd = join(__dirname, 'fixtures/npm'); -const file = join(cwd, 'output/bundle.js'); -const input = join(cwd, '../input.js'); -const manager = 'npm'; -const pkgFile = join(cwd, 'package.json'); - -process.chdir(cwd); - -test('invalid manager', (t) => { - t.timeout(50000); - const error = t.throws( - () => - rollup({ - input, - output: { - file, - format: 'cjs' - }, - plugins: [autoInstall({ pkgFile, manager: 'foo' }), nodeResolve()] - }), - { - instanceOf: RangeError - } - ); - t.snapshot(error.message); -}); - -test('npm', async (t) => { - t.timeout(50000); - await rollup({ - input, - output: { - file, - format: 'cjs' - }, - plugins: [autoInstall({ pkgFile, manager }), nodeResolve()] - }); - t.snapshot(readFileSync('package.json', 'utf-8')); -}); - -test.after(async () => { - await del(['node_modules', 'package-lock.json']); - writeFileSync(pkgFile, '{}'); -}); diff --git a/packages/auto-install/test/npm.test.ts b/packages/auto-install/test/npm.test.ts new file mode 100644 index 000000000..202f8aa2f --- /dev/null +++ b/packages/auto-install/test/npm.test.ts @@ -0,0 +1,81 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import del from 'del'; +import { it, expect, afterAll } from 'vitest'; +import { rollup } from 'rollup'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +// IMPORTANT: Don't import the plugin at module scope. The plugin requires Node ≥20.19 +// (uses import.meta.dirname). Dynamically import it inside gated tests only. + +const DIR = fileURLToPath(new URL('.', import.meta.url)); +const cwd = path.join(DIR, 'fixtures/npm'); +const file = path.join(cwd, 'output/bundle.js'); +const input = path.join(cwd, '../input.js'); +const pkgFile = path.join(cwd, 'package.json'); + +// Helper to temporarily disable slow npm features during the test +function stubNpmQuietEnv() { + const keys = [ + 'npm_config_audit', + 'npm_config_fund', + 'npm_config_progress', + 'npm_config_update_notifier', + 'npm_config_loglevel' + ] as const; + const prev: Record = {}; + for (const k of keys) prev[k] = process.env[k]; + process.env.npm_config_audit = 'false'; + process.env.npm_config_fund = 'false'; + process.env.npm_config_progress = 'false'; + process.env.npm_config_update_notifier = 'false'; + process.env.npm_config_loglevel = 'error'; + return () => { + for (const k of keys) { + const v = prev[k]; + if (v == null) delete (process.env as any)[k]; + else (process.env as any)[k] = v; + } + }; +} +const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number); +const RUN_ON_THIS_NODE = NODE_MAJOR > 20 || (NODE_MAJOR === 20 && NODE_MINOR >= 19); + +it.runIf(RUN_ON_THIS_NODE)('invalid manager', async () => { + const { default: autoInstall } = await import('~package'); + expect(() => autoInstall({ pkgFile, manager: 'foo' as any })).toThrow(RangeError); +}); + +// npm installs can be a bit slower than pnpm/yarn on CI; allow extra time. +it.runIf(RUN_ON_THIS_NODE)( + 'npm', + async () => { + const restoreEnv = stubNpmQuietEnv(); + const prevCwd = process.cwd(); + process.chdir(cwd); + try { + const { default: autoInstall } = await import('~package'); + const bundle = await rollup({ + input, + // @ts-expect-error - rollup() ignores output here but tests kept it historically + output: { file, format: 'es' }, + plugins: [autoInstall({ pkgFile, manager: 'npm' }), nodeResolve()] + }); + await bundle.close(); + + const json = JSON.parse(fs.readFileSync(pkgFile, 'utf-8')); + expect(json.dependencies && json.dependencies['node-noop']).toBeDefined(); + } finally { + process.chdir(prevCwd); + restoreEnv(); + } + }, + 60000 +); + +afterAll(async () => { + await del(['node_modules', 'package-lock.json']); + fs.writeFileSync(pkgFile, '{}'); +}); diff --git a/packages/auto-install/test/pnpm-bare.js b/packages/auto-install/test/pnpm-bare.js deleted file mode 100644 index acb8aea9a..000000000 --- a/packages/auto-install/test/pnpm-bare.js +++ /dev/null @@ -1,34 +0,0 @@ -const { readFileSync } = require('fs'); -const { join } = require('path'); - -const test = require('ava'); -const del = require('del'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const { rollup } = require('rollup'); - -const autoInstall = require('..'); - -const cwd = join(__dirname, 'fixtures/pnpm-bare'); -const file = join(cwd, 'output/bundle.js'); -const input = join(cwd, '../input.js'); - -process.chdir(cwd); - -test('pnpm, bare', async (t) => { - t.timeout(50000); - await rollup({ - input, - output: { - file, - format: 'cjs' - }, - plugins: [autoInstall({ manager: 'pnpm' }), nodeResolve()] - }); - const json = JSON.parse(readFileSync('package.json', 'utf-8')); - // snapshots for this are a nightmare cross-platform - t.truthy('node-noop' in json.dependencies); -}); - -test.after(async () => { - await del(['node_modules', 'package.json', 'pnpm-lock.yaml']); -}); diff --git a/packages/auto-install/test/pnpm-bare.test.ts b/packages/auto-install/test/pnpm-bare.test.ts new file mode 100644 index 000000000..73302ef7b --- /dev/null +++ b/packages/auto-install/test/pnpm-bare.test.ts @@ -0,0 +1,52 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import del from 'del'; +import { it, afterAll } from 'vitest'; +import { rollup } from 'rollup'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +// Dynamically import the plugin within gated tests to avoid Node <20 execution + +const DIR = fileURLToPath(new URL('.', import.meta.url)); +const cwd = path.join(DIR, 'fixtures/pnpm-bare'); +const file = path.join(cwd, 'output/bundle.js'); +// Use a local input inside the cwd so Node resolution finds packages installed +// by the test (e.g., on Windows where upward-only resolution won't see +// `pnpm-bare/node_modules` from `fixtures/input.js`). +const input = path.join(cwd, 'input.local.js'); + +const PREV_CWD = process.cwd(); +const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number); +const RUN_ON_THIS_NODE = NODE_MAJOR > 20 || (NODE_MAJOR === 20 && NODE_MINOR >= 19); + +it.runIf(RUN_ON_THIS_NODE)('pnpm, bare', async () => { + // Create a local copy of the shared input so resolution starts from `cwd`. + fs.copyFileSync(path.join(cwd, '../input.js'), input); + process.chdir(cwd); + const { default: autoInstall } = await import('~package'); + const bundle = await rollup({ + input, + // @ts-expect-error - rollup() ignores output here but tests kept it historically + output: { file, format: 'es' }, + plugins: [autoInstall({ manager: 'pnpm' }), nodeResolve()] + }); + await bundle.close(); + + const json = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8')); + if (!json.dependencies || !json.dependencies['node-noop']) { + throw new Error('Expected node-noop to be added to dependencies'); + } +}); + +afterAll(async () => { + // Ensure cleanup runs against the fixture directory, not the repo root + await del(['node_modules', 'package.json', 'pnpm-lock.yaml'], { cwd }); + try { + fs.unlinkSync(input); + } catch { + /* ignore cleanup errors */ + } + process.chdir(PREV_CWD); +}); diff --git a/packages/auto-install/test/pnpm.js b/packages/auto-install/test/pnpm.js deleted file mode 100644 index 2fe1e5856..000000000 --- a/packages/auto-install/test/pnpm.js +++ /dev/null @@ -1,36 +0,0 @@ -const { readFileSync, writeFileSync } = require('fs'); -const { join } = require('path'); - -const test = require('ava'); -const del = require('del'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const { rollup } = require('rollup'); - -const autoInstall = require('..'); - -const cwd = join(__dirname, 'fixtures/pnpm'); -const file = join(cwd, 'output/bundle.js'); -const input = join(cwd, '../input.js'); - -process.chdir(cwd); - -test('pnpm', async (t) => { - t.timeout(50000); - await rollup({ - input, - output: { - file, - format: 'cjs' - }, - plugins: [autoInstall(), nodeResolve()] - }); - - const json = JSON.parse(readFileSync('package.json', 'utf-8')); - // snapshots for this are a nightmare cross-platform - t.truthy('node-noop' in json.dependencies); -}); - -test.after(async () => { - await del(['node_modules', 'package.json']); - writeFileSync('pnpm-lock.yaml', ''); -}); diff --git a/packages/auto-install/test/pnpm.test.ts b/packages/auto-install/test/pnpm.test.ts new file mode 100644 index 000000000..8112d58e0 --- /dev/null +++ b/packages/auto-install/test/pnpm.test.ts @@ -0,0 +1,43 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import del from 'del'; +import { it, afterAll } from 'vitest'; +import { rollup } from 'rollup'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +// Dynamically import the plugin within gated tests to avoid Node <20 execution + +const DIR = fileURLToPath(new URL('.', import.meta.url)); +const cwd = path.join(DIR, 'fixtures/pnpm'); +const file = path.join(cwd, 'output/bundle.js'); +const input = path.join(cwd, '../input.js'); + +const PREV_CWD = process.cwd(); +const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number); +const RUN_ON_THIS_NODE = NODE_MAJOR > 20 || (NODE_MAJOR === 20 && NODE_MINOR >= 19); + +it.runIf(RUN_ON_THIS_NODE)('pnpm', async () => { + process.chdir(cwd); + const { default: autoInstall } = await import('~package'); + const bundle = await rollup({ + input, + // @ts-expect-error - rollup() ignores output here but tests kept it historically + output: { file, format: 'es' }, + plugins: [autoInstall({ manager: 'pnpm' }), nodeResolve()] + }); + await bundle.close(); + + const json = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8')); + if (!json.dependencies || !json.dependencies['node-noop']) { + throw new Error('Expected node-noop to be added to dependencies'); + } +}); + +afterAll(async () => { + await del(['node_modules', 'package.json']); + // keep pnpm from complaining on subsequent runs; fixtures snapshot this file in CI + fs.writeFileSync(path.join(cwd, 'pnpm-lock.yaml'), ''); + process.chdir(PREV_CWD); +}); diff --git a/packages/auto-install/test/snapshots/npm-bare.js.md b/packages/auto-install/test/snapshots/npm-bare.js.md deleted file mode 100644 index 5b47ced68..000000000 --- a/packages/auto-install/test/snapshots/npm-bare.js.md +++ /dev/null @@ -1,16 +0,0 @@ -# Snapshot report for `test/npm-bare.js` - -The actual snapshot is saved in `npm-bare.js.snap`. - -Generated by [AVA](https://avajs.dev). - -## npm, bare - -> Snapshot 1 - - `{␊ - "dependencies": {␊ - "node-noop": "^1.0.0"␊ - }␊ - }␊ - ` diff --git a/packages/auto-install/test/snapshots/npm-bare.js.snap b/packages/auto-install/test/snapshots/npm-bare.js.snap deleted file mode 100644 index b4e4b4875..000000000 Binary files a/packages/auto-install/test/snapshots/npm-bare.js.snap and /dev/null differ diff --git a/packages/auto-install/test/snapshots/npm.js.md b/packages/auto-install/test/snapshots/npm.js.md deleted file mode 100644 index 77c82b1e2..000000000 --- a/packages/auto-install/test/snapshots/npm.js.md +++ /dev/null @@ -1,22 +0,0 @@ -# Snapshot report for `test/npm.js` - -The actual snapshot is saved in `npm.js.snap`. - -Generated by [AVA](https://avajs.dev). - -## invalid manager - -> Snapshot 1 - - '\'foo\' is not a valid package manager. Valid managers include: \'npm\', \'yarn\', \'pnpm\'.' - -## npm - -> Snapshot 1 - - `{␊ - "dependencies": {␊ - "node-noop": "^1.0.0"␊ - }␊ - }␊ - ` diff --git a/packages/auto-install/test/snapshots/npm.js.snap b/packages/auto-install/test/snapshots/npm.js.snap deleted file mode 100644 index d3c36cb01..000000000 Binary files a/packages/auto-install/test/snapshots/npm.js.snap and /dev/null differ diff --git a/packages/auto-install/test/yarn-bare.js b/packages/auto-install/test/yarn-bare.js deleted file mode 100644 index 6b991594e..000000000 --- a/packages/auto-install/test/yarn-bare.js +++ /dev/null @@ -1,38 +0,0 @@ -const { readFileSync } = require('fs'); -const { join } = require('path'); - -const test = require('ava'); -const del = require('del'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const { rollup } = require('rollup'); - -const autoInstall = require('..'); - -const cwd = join(__dirname, 'fixtures/yarn-bare'); -const file = join(cwd, 'output/bundle.js'); -const input = join(cwd, '../input.js'); - -process.chdir(cwd); - -test('yarn, bare', async (t) => { - t.timeout(50000); - await rollup({ - input, - output: { - file, - format: 'cjs' - }, - plugins: [ - // mock the call to yarn here. yarn has had consistent issues in this test env - autoInstall({ manager: 'yarn', commands: { yarn: 'echo yarn.bare > yarn.lock' } }), - nodeResolve() - ] - }); - const lockFile = readFileSync('yarn.lock', 'utf-8'); - // snapshots for this are a nightmare cross-platform - t.truthy(/yarn\.bare\s+node-noop/.test(lockFile)); -}); - -test.after(async () => { - await del(['node_modules', 'package.json', 'yarn.lock']); -}); diff --git a/packages/auto-install/test/yarn-bare.test.ts b/packages/auto-install/test/yarn-bare.test.ts new file mode 100644 index 000000000..fdd48a169 --- /dev/null +++ b/packages/auto-install/test/yarn-bare.test.ts @@ -0,0 +1,56 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import del from 'del'; +import { it, afterAll } from 'vitest'; +import { rollup } from 'rollup'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +// Dynamically import the plugin within gated tests to avoid Node <20 execution + +const DIR = fileURLToPath(new URL('.', import.meta.url)); +const cwd = path.join(DIR, 'fixtures/yarn-bare'); +const file = path.join(cwd, 'output/bundle.js'); +const input = path.join(cwd, '../input.js'); +const pkgFile = path.join(cwd, 'package.json'); + +const PREV_CWD = process.cwd(); +const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number); +const RUN_ON_THIS_NODE = NODE_MAJOR > 20 || (NODE_MAJOR === 20 && NODE_MINOR >= 19); + +it.runIf(RUN_ON_THIS_NODE)('yarn, bare', async () => { + process.chdir(cwd); + // Ensure Yarn classic does not traverse to the repo root and read its + // packageManager (pnpm). When no local package.json exists, Yarn v1 will + // use the nearest parent package.json and error out. Creating a minimal + // local package.json with an explicit Yarn version avoids the Corepack gate. + if (!fs.existsSync(pkgFile)) { + fs.writeFileSync( + pkgFile, + JSON.stringify( + { name: 'auto-install-yarn-bare-fixture', private: true, packageManager: 'yarn@1.22.22' }, + null, + 2 + ) + ); + } + const { default: autoInstall } = await import('~package'); + const bundle = await rollup({ + input, + // @ts-expect-error - rollup() ignores output here but tests kept it historically + output: { file, format: 'es' }, + plugins: [autoInstall({ manager: 'yarn' }), nodeResolve()] + }); + await bundle.close(); + + const json = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8')); + if (!json.dependencies || !json.dependencies['node-noop']) { + throw new Error('Expected node-noop to be added to dependencies'); + } +}); + +afterAll(async () => { + await del(['node_modules', 'yarn.lock', 'package.json']); + process.chdir(PREV_CWD); +}); diff --git a/packages/auto-install/test/yarn.js b/packages/auto-install/test/yarn.js deleted file mode 100644 index 70f86e23a..000000000 --- a/packages/auto-install/test/yarn.js +++ /dev/null @@ -1,36 +0,0 @@ -const { readFileSync, writeFileSync } = require('fs'); -const { join } = require('path'); - -const test = require('ava'); -const del = require('del'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const { rollup } = require('rollup'); - -const autoInstall = require('..'); - -const cwd = join(__dirname, 'fixtures/yarn'); -const file = join(cwd, 'output/bundle.js'); -const input = join(cwd, '../input.js'); - -process.chdir(cwd); - -test('yarn', async (t) => { - t.timeout(50000); - await rollup({ - input, - output: { - file, - format: 'cjs' - }, - // mock the call to yarn here. yarn has had consistent issues in this test env - plugins: [autoInstall({ commands: { yarn: 'echo yarn > yarn.lock' } }), nodeResolve()] - }); - const lockFile = readFileSync('yarn.lock', 'utf-8'); - // snapshots for this are a nightmare cross-platform - t.truthy(/yarn\s+node-noop/.test(lockFile)); -}); - -test.after(async () => { - await del(['node_modules', 'package.json']); - writeFileSync('yarn.lock', ''); -}); diff --git a/packages/auto-install/test/yarn.test.ts b/packages/auto-install/test/yarn.test.ts new file mode 100644 index 000000000..224e36ec8 --- /dev/null +++ b/packages/auto-install/test/yarn.test.ts @@ -0,0 +1,55 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import del from 'del'; +import { it, afterAll } from 'vitest'; +import { rollup } from 'rollup'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +// Dynamically import the plugin within gated tests to avoid Node <20 execution + +const DIR = fileURLToPath(new URL('.', import.meta.url)); +const cwd = path.join(DIR, 'fixtures/yarn'); +const file = path.join(cwd, 'output/bundle.js'); +const input = path.join(cwd, '../input.js'); +const pkgFile = path.join(cwd, 'package.json'); + +const PREV_CWD = process.cwd(); +const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number); +const RUN_ON_THIS_NODE = NODE_MAJOR > 20 || (NODE_MAJOR === 20 && NODE_MINOR >= 19); + +it.runIf(RUN_ON_THIS_NODE)('yarn', async () => { + process.chdir(cwd); + // Pre-create a local package.json with an explicit Yarn v1 requirement so + // the Yarn shim (Corepack) doesn't traverse to the repo root and pick up + // the root packageManager (pnpm), which would cause a usage error. + if (!fs.existsSync(pkgFile)) { + fs.writeFileSync( + pkgFile, + JSON.stringify( + { name: 'auto-install-yarn-fixture', private: true, packageManager: 'yarn@1.22.22' }, + null, + 2 + ) + ); + } + const { default: autoInstall } = await import('~package'); + const bundle = await rollup({ + input, + // @ts-expect-error - rollup() ignores output here but tests kept it historically + output: { file, format: 'es' }, + plugins: [autoInstall({ manager: 'yarn' }), nodeResolve()] + }); + await bundle.close(); + + const json = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8')); + if (!json.dependencies || !json.dependencies['node-noop']) { + throw new Error('Expected node-noop to be added to dependencies'); + } +}); + +afterAll(async () => { + await del(['node_modules', 'yarn.lock', 'package.json']); + process.chdir(PREV_CWD); +}); diff --git a/packages/auto-install/tsconfig.json b/packages/auto-install/tsconfig.json index 7cd38da8f..1ef31f009 120000 --- a/packages/auto-install/tsconfig.json +++ b/packages/auto-install/tsconfig.json @@ -1 +1 @@ -../../shared/tsconfig.json \ No newline at end of file +../../.config/tsconfig.plugin.json \ No newline at end of file diff --git a/packages/auto-install/types/index.d.ts b/packages/auto-install/types/index.d.ts deleted file mode 100644 index d9e564445..000000000 --- a/packages/auto-install/types/index.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Plugin } from 'rollup'; - -export interface RollupAutoInstallOptions { - /** - * Specifies the location on disk of the target `package.json` file. - * If the file doesn't exist, it will be created by the plugin, - * as package managers need to populate the `dependencies` property. - * @default '{cwd}/package.json' - */ - pkgFile?: string; - - /** - * Specifies the package manager to use; `npm` or `yarn`. - * If not specified, the plugin will default to `yarn` if `yarn.lock` exists, or `npm` otherwise. - */ - manager?: 'npm' | 'yarn'; -} - -/** - * 🍣 A Rollup plugin which automatically installs dependencies that are imported by a bundle, even if not yet in `package.json`. - */ -export default function auto(options?: RollupAutoInstallOptions): Plugin; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5908b66b..f2dad2f5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,22 +106,22 @@ importers: devDependencies: '@rollup/plugin-node-resolve': specifier: ^15.0.0 - version: 15.0.0(rollup@4.0.0-24) - '@rollup/plugin-typescript': - specifier: ^9.0.1 - version: 9.0.1(rollup@4.0.0-24)(tslib@2.4.0)(typescript@4.8.4) + version: 15.0.0(rollup@4.52.5) del: specifier: ^6.1.1 version: 6.1.1 + del-cli: + specifier: ^5.0.0 + version: 5.0.0 node-noop: specifier: ^1.0.0 version: 1.0.0 rollup: - specifier: ^4.0.0-24 - version: 4.0.0-24 + specifier: ^4.0.0 + version: 4.52.5 typescript: - specifier: ^4.8.3 - version: 4.8.4 + specifier: 'catalog:' + version: 5.9.3 packages/babel: dependencies: @@ -8051,7 +8051,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.0 + '@types/estree': 1.0.8 esutils@2.0.3: {}