chore: remove `jest-repl` (#15673)

Co-authored-by: eryue0220 <eryue0220@gmail.com>
This commit is contained in:
Tom Mrazauskas 2025-06-12 11:19:07 +03:00 committed by GitHub
parent edee3ab3a8
commit 113b947faf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 9 additions and 632 deletions

View File

@ -1,5 +1,9 @@
## main
### Chore & Maintenance
- `[*]` Remove and deprecate `jest-repl` package ([15673](https://github.com/jestjs/jest/pull/15673))
## 30.0.0
### Features
@ -105,7 +109,7 @@
- `[jest-util]` Always load `mjs` files with `import` ([#15447](https://github.com/jestjs/jest/pull/15447))
- `[jest-worker]` Properly handle a circular reference error when worker tries to send an assertion fails where either the expected or actual value is circular ([#15191](https://github.com/jestjs/jest/pull/15191))
- `[jest-worker]` Properly handle a BigInt when worker tries to send an assertion fails where either the expected or actual value is BigInt ([#15191](https://github.com/jestjs/jest/pull/15191))
- `[expect]` Resolve issue where `ObjectContaining` matched non-object values. ([#15463])(https://github.com/jestjs/jest/pull/15463).
- `[expect]` Resolve issue where `ObjectContaining` matched non-object values. [#15463](https://github.com/jestjs/jest/pull/15463).
- Adds a `conditional/check` to ensure the argument passed to `expect` is an object.
- Add unit tests for new `ObjectContaining` behavior.
- Remove `invalid/wrong` test case assertions for `ObjectContaining`.

View File

@ -705,7 +705,6 @@ const config = typescriptEslint.config(
'scripts/*',
'packages/*/__benchmarks__/test.js',
'packages/create-jest/src/runCreate.ts',
'packages/jest-repl/src/cli/runtime-cli.ts',
],
rules: {
'no-console': 'off',

View File

@ -61,7 +61,6 @@ export default {
'/packages/jest-haste-map/src/__tests__/haste_impl.js',
'/packages/jest-haste-map/src/__tests__/dependencyExtractor.js',
'/packages/jest-haste-map/src/__tests__/test_dotfiles_root/',
'/packages/jest-repl/src/__tests__/test_root',
'/packages/jest-runtime/src/__tests__/defaultResolver.js',
'/packages/jest-runtime/src/__tests__/module_dir/',
'/packages/jest-runtime/src/__tests__/NODE_PATH_dir',

View File

@ -108,7 +108,7 @@
"remove-examples": "node ./scripts/remove-examples.mjs",
"test-ci-partial": "yarn test-ci-partial:parallel -i",
"test-ci-partial:parallel": "yarn jest --color --config jest.config.ci.mjs",
"test-leak": "yarn jest -i --detectLeaks --color jest-mock jest-diff jest-repl pretty-format",
"test-leak": "yarn jest -i --detectLeaks --color jest-mock jest-diff pretty-format",
"test-ts": "yarn jest --config jest.config.ts.mjs",
"test-types": "yarn tstyche",
"test-with-type-info": "yarn jest e2e/__tests__/jest.config.ts.test.ts",

View File

@ -1,8 +0,0 @@
**/__mocks__/**
**/__tests__/**
__typetests__
src
tsconfig.json
tsconfig.tsbuildinfo
api-extractor.json
.eslintcache

View File

@ -1,13 +0,0 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = 'test';
}
require('..').repl();

View File

@ -1,13 +0,0 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = 'test';
}
require('..').runtime();

View File

@ -1,50 +0,0 @@
{
"name": "jest-repl",
"version": "30.0.0",
"repository": {
"type": "git",
"url": "https://github.com/jestjs/jest.git",
"directory": "packages/jest-repl"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"require": "./build/index.js",
"import": "./build/index.mjs",
"default": "./build/index.js"
},
"./package.json": "./package.json",
"./bin/jest-repl": "./bin/jest-repl.js",
"./bin/jest-runtime-cli": "./bin/jest-runtime-cli.js"
},
"dependencies": {
"@jest/console": "workspace:*",
"@jest/environment": "workspace:*",
"@jest/transform": "workspace:*",
"@jest/types": "workspace:*",
"chalk": "^4.1.2",
"jest-config": "workspace:*",
"jest-runtime": "workspace:*",
"jest-util": "workspace:*",
"jest-validate": "workspace:*",
"repl": "^0.1.3",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/yargs": "^17.0.33",
"execa": "^5.1.1"
},
"bin": {
"jest-repl": "./bin/jest-repl.js",
"jest-runtime": "./bin/jest-runtime-cli.js"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {spawnSync} from 'child_process';
import * as path from 'path';
const JEST_RUNTIME = require.resolve('../../bin/jest-repl.js');
describe('Repl', () => {
describe('cli', () => {
it('runs without errors', () => {
let command = JEST_RUNTIME;
const args = [];
// Windows can't handle hashbangs, so is the best we can do
if (process.platform === 'win32') {
args.push(command);
command = 'node';
}
const output = spawnSync(command, args, {
cwd: process.cwd(),
encoding: 'utf8',
env: process.env,
});
expect(output.stderr.trim()).toBe('');
expect(output.stdout.trim()).toMatch(//u);
});
});
});

View File

@ -1,53 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {sync as spawnSync} from 'execa';
const JEST_RUNTIME = require.resolve('../../bin/jest-runtime-cli.js');
const timeout = 10_000;
const run = args =>
spawnSync(JEST_RUNTIME, args, {
cwd: process.cwd(),
env: process.env,
reject: false,
timeout: timeout - 500,
});
describe('Runtime CLI', () => {
beforeAll(() => {
jest.retryTimes(3);
jest.setTimeout(timeout);
});
it('fails with no path', () => {
const expectedOutput =
'Please provide a path to a script. (See --help for details)';
expect(run([]).stdout).toBe(expectedOutput);
});
it('displays script output', () => {
const scriptPath = require.resolve('./test_root/logging.js');
expect(run([scriptPath, '--no-cache']).stdout).toMatch('Hello, world!');
});
it('always disables automocking', () => {
const scriptPath = require.resolve('./test_root/logging.js');
const output = run([
scriptPath,
'--no-cache',
`--config=${JSON.stringify({automock: true})}`,
]);
expect(output.stdout).toMatch('Hello, world!');
});
it('throws script errors', () => {
const scriptPath = require.resolve('./test_root/throwing.js');
expect(run([scriptPath, '--no-cache']).stderr).toMatch('Error: throwing');
});
});

View File

@ -1,16 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
if (
require('jest-runtime/src/__tests__/test_root/RegularModule').getModuleStateValue()
) {
console.log('Hello, world!');
} else {
console.log('Automocking is not properly disabled in jest-runtime.');
}

View File

@ -1,10 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
throw new Error('throwing');

View File

@ -1,5 +0,0 @@
{
"extends": "../../../../tsconfig.test.json",
"include": ["./**/*"],
"references": [{"path": "../../"}]
}

View File

@ -1,51 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {Options} from 'yargs';
export const usage = 'Usage: $0 [--config=<pathToConfigFile>]';
const runtimeCLIOptions: Record<
'cache' | 'config' | 'debug' | 'watchman',
Options
> = {
cache: {
default: true,
description:
'Whether to use the preprocessor cache. Disable ' +
'the cache using --no-cache.',
type: 'boolean',
},
config: {
alias: 'c',
description: 'The path to a Jest config file.',
type: 'string',
},
debug: {
description: 'Print debugging info about your jest config.',
type: 'boolean',
},
watchman: {
default: true,
description:
'Whether to use watchman for file crawling. Disable using ' +
'--no-watchman.',
type: 'boolean',
},
};
export const options: Record<string, Options> = {
...runtimeCLIOptions,
replname: {
alias: 'r',
description:
'The "name" of the file given to transformers to be ' +
'transformed. For example, "repl.ts" if using a TypeScript transformer.',
type: 'string',
},
};

View File

@ -1,30 +0,0 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import yargs from 'yargs';
import type {Config} from '@jest/types';
import {deprecationEntries} from 'jest-config';
import {validateCLIOptions} from 'jest-validate';
import * as args from './args';
import {run as runtimeCLI} from './runtime-cli';
import {VERSION} from './version';
const REPL_SCRIPT = require.resolve('./repl');
export async function run(): Promise<void> {
const argv = (await yargs(process.argv.slice(2))
.usage(args.usage)
.options(args.options).argv) as Config.Argv;
validateCLIOptions(argv, {...args.options, deprecationEntries});
argv._ = [REPL_SCRIPT];
return runtimeCLI(argv, [`Jest REPL v${VERSION}`]);
}

View File

@ -1,117 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
declare const jestGlobalConfig: Config.GlobalConfig;
declare const jestProjectConfig: Config.ProjectConfig;
import * as path from 'path';
import * as repl from 'repl';
import * as util from 'util';
import {runInThisContext} from 'vm';
import type {SyncTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import {interopRequireDefault} from 'jest-util';
// TODO: support async as well
let transformer: SyncTransformer | undefined;
let transformerConfig: unknown;
const evalCommand: repl.REPLEval = (
cmd: string,
_context: unknown,
_filename: string,
callback: (e: Error | null, result?: unknown) => void,
) => {
let result: unknown;
try {
if (transformer != null) {
const transformResult = transformer.process(
cmd,
jestGlobalConfig.replname ?? 'jest.js',
{
cacheFS: new Map<string, string>(),
config: jestProjectConfig,
configString: JSON.stringify(jestProjectConfig),
instrument: false,
supportsDynamicImport: false,
supportsExportNamespaceFrom: false,
supportsStaticESM: false,
supportsTopLevelAwait: false,
transformerConfig,
},
);
cmd =
typeof transformResult === 'string'
? transformResult
: transformResult.code;
}
result = runInThisContext(cmd) as unknown;
} catch (error: any) {
return callback(
isRecoverableError(error) ? new repl.Recoverable(error) : error,
);
}
return callback(null, result);
};
const isRecoverableError = (error: unknown) => {
if (!util.types.isNativeError(error)) {
return false;
}
if (error.name === 'SyntaxError') {
return [
'Unterminated template',
'Missing } in template expression',
'Unexpected end of input',
'missing ) after argument list',
'Unexpected token',
].some(exception => error.message.includes(exception));
}
return false;
};
if (jestProjectConfig.transform) {
let transformerPath = null;
for (const transform of jestProjectConfig.transform) {
if (new RegExp(transform[0]).test('foobar.js')) {
transformerPath = transform[1];
transformerConfig = transform[2];
break;
}
}
if (transformerPath != null) {
const transformerOrFactory = interopRequireDefault(
require(transformerPath),
).default;
if (typeof transformerOrFactory.createTransformer === 'function') {
transformer = transformerOrFactory.createTransformer(transformerConfig);
} else {
transformer = transformerOrFactory;
}
if (typeof transformer?.process !== 'function') {
throw new TypeError(
'Jest: a transformer must export a `process` function.',
);
}
}
}
const replInstance: repl.REPLServer = repl.start({
eval: evalCommand,
prompt: '\u203A ',
useGlobal: true,
});
replInstance.context.require = (moduleName: string) => {
if (/([./\\])/.test(moduleName)) {
moduleName = path.resolve(process.cwd(), moduleName);
}
return require(moduleName) as unknown;
};

View File

@ -1,144 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {availableParallelism} from 'os';
import * as path from 'path';
import * as util from 'util';
import chalk from 'chalk';
import yargs from 'yargs';
import {CustomConsole} from '@jest/console';
import type {JestEnvironment} from '@jest/environment';
import {createScriptTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import {deprecationEntries, readConfig} from 'jest-config';
import Runtime from 'jest-runtime';
import {setGlobal, tryRealpath} from 'jest-util';
import {validateCLIOptions} from 'jest-validate';
import * as args from './args';
import {VERSION} from './version';
export async function run(
cliArgv?: Config.Argv,
cliInfo?: Array<string>,
): Promise<void> {
let argv: Config.Argv;
if (cliArgv) {
argv = cliArgv;
} else {
argv = (await yargs(process.argv.slice(2))
.usage(args.usage)
.help(false)
.version(false)
.options(args.options).argv) as Config.Argv;
validateCLIOptions(argv, {...args.options, deprecationEntries});
}
if (argv.help === true) {
yargs().showHelp();
process.on('exit', () => (process.exitCode = 1));
return;
}
if (argv.version === true) {
console.log(`v${VERSION}\n`);
return;
}
if (argv._.length === 0) {
console.log('Please provide a path to a script. (See --help for details)');
process.on('exit', () => (process.exitCode = 1));
return;
}
const root = tryRealpath(process.cwd());
const filePath = path.resolve(root, argv._[0].toString());
if (argv.debug === true) {
const info = cliInfo ? `, ${cliInfo.join(', ')}` : '';
console.log(`Using Jest Runtime v${VERSION}${info}`);
}
const options = await readConfig(argv, root);
const globalConfig = options.globalConfig;
// Always disable automocking in scripts.
const projectConfig: Config.ProjectConfig = {
...options.projectConfig,
automock: false,
};
try {
const numCpus = availableParallelism();
const hasteMap = await Runtime.createContext(projectConfig, {
maxWorkers: Math.max(numCpus - 1, 1),
watchman: globalConfig.watchman,
});
const transformer = await createScriptTransformer(projectConfig);
const Environment: typeof JestEnvironment =
await transformer.requireAndTranspileModule(
projectConfig.testEnvironment,
);
const customConsole = new CustomConsole(process.stdout, process.stderr);
const environment = new Environment(
{
globalConfig,
projectConfig,
},
{console: customConsole, docblockPragmas: {}, testPath: filePath},
);
setGlobal(environment.global, 'console', customConsole, 'retain');
setGlobal(environment.global, 'jestProjectConfig', projectConfig, 'retain');
setGlobal(environment.global, 'jestGlobalConfig', globalConfig, 'retain');
const runtime = new Runtime(
projectConfig,
environment,
hasteMap.resolver,
transformer,
new Map(),
{
changedFiles: undefined,
collectCoverage: false,
collectCoverageFrom: [],
coverageProvider: 'v8',
sourcesRelatedToTestsInChangedFiles: undefined,
},
filePath,
globalConfig,
);
for (const path of projectConfig.setupFiles) {
const esm = runtime.unstable_shouldLoadAsEsm(path);
if (esm) {
await runtime.unstable_importModule(path);
} else {
const setupFile = runtime.requireModule(path);
if (typeof setupFile === 'function') {
await setupFile();
}
}
}
const esm = runtime.unstable_shouldLoadAsEsm(filePath);
if (esm) {
await runtime.unstable_importModule(filePath);
} else {
runtime.requireModule(filePath);
}
} catch (error: any) {
console.error(
chalk.red(util.types.isNativeError(error) ? error.stack : error),
);
process.on('exit', () => {
process.exitCode = 1;
});
}
}

View File

@ -1,11 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// For some reason, doing `require`ing here works, while inside `cli` fails
export const VERSION = (
require('../../package.json') as Record<string, unknown>
).version as string;

View File

@ -1,9 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export {run as repl} from './cli';
export {run as runtime} from './cli/runtime-cli';

View File

@ -1,19 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build"
},
"include": ["./src/**/*"],
"exclude": ["./**/__tests__/**/*"],
"references": [
{"path": "../jest-config"},
{"path": "../jest-console"},
{"path": "../jest-environment"},
{"path": "../jest-runtime"},
{"path": "../jest-transform"},
{"path": "../jest-types"},
{"path": "../jest-util"},
{"path": "../jest-validate"}
]
}

View File

@ -397,14 +397,6 @@ export default class Runtime {
});
}
static async runCLI(): Promise<never> {
throw new Error('The jest-runtime CLI has been moved into jest-repl');
}
static getCLIOptions(): never {
throw new Error('The jest-runtime CLI has been moved into jest-repl');
}
// unstable as it should be replaced by https://github.com/nodejs/modules/issues/393, and we don't want people to use it
unstable_shouldLoadAsEsm(modulePath: string): boolean {
return (

View File

@ -228,11 +228,9 @@ export function createBuildConfigs() {
'./src/setup_jest_globals.ts',
),
}
: pkg.name === 'jest-repl'
? {repl: path.resolve(packageDir, './src/cli/repl.ts')}
: pkg.name === 'jest-snapshot'
? {worker: path.resolve(packageDir, './src/worker.ts')}
: {};
: pkg.name === 'jest-snapshot'
? {worker: path.resolve(packageDir, './src/worker.ts')}
: {};
const extraEntryPoints =
// skip expect for now

View File

@ -50,7 +50,6 @@ const packagesNotToTest = [
'jest-pattern',
'jest-phabricator',
'jest-regex-util',
'jest-repl',
'jest-reporters',
'jest-resolve',
'jest-runner',

View File

@ -13760,29 +13760,6 @@ __metadata:
languageName: unknown
linkType: soft
"jest-repl@workspace:packages/jest-repl":
version: 0.0.0-use.local
resolution: "jest-repl@workspace:packages/jest-repl"
dependencies:
"@jest/console": "workspace:*"
"@jest/environment": "workspace:*"
"@jest/transform": "workspace:*"
"@jest/types": "workspace:*"
"@types/yargs": ^17.0.33
chalk: ^4.1.2
execa: ^5.1.1
jest-config: "workspace:*"
jest-runtime: "workspace:*"
jest-util: "workspace:*"
jest-validate: "workspace:*"
repl: ^0.1.3
yargs: ^17.7.2
bin:
jest-repl: ./bin/jest-repl.js
jest-runtime: ./bin/jest-runtime-cli.js
languageName: unknown
linkType: soft
"jest-resolve-dependencies@workspace:*, jest-resolve-dependencies@workspace:packages/jest-resolve-dependencies":
version: 0.0.0-use.local
resolution: "jest-resolve-dependencies@workspace:packages/jest-resolve-dependencies"
@ -19267,13 +19244,6 @@ __metadata:
languageName: node
linkType: hard
"repl@npm:^0.1.3":
version: 0.1.3
resolution: "repl@npm:0.1.3"
checksum: 3223b4f7ac600aba7776655339ed95cccfc7e4676769984186a314a5ba91c51696ed96183e7d63e2f58187339dc39e940b487d7e0a1e159c30844cc79d1d64e0
languageName: node
linkType: hard
"require-directory@npm:^2.1.1":
version: 2.1.1
resolution: "require-directory@npm:2.1.1"