This commit is contained in:
Elizabeth Craig 2024-11-18 00:05:56 -08:00
Родитель 49911efb69
Коммит 5eeac8f8fe
47 изменённых файлов: 489 добавлений и 116 удалений

17
.github/workflows/pr.yml поставляемый
Просмотреть файл

@ -13,8 +13,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Node.js from .nvmrc
uses: actions/setup-node@v3
@ -33,3 +31,18 @@ jobs:
- name: Check for modified files
uses: ecraig12345/beachball-actions/check-for-modified-files@v1
test-node-18:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 18
uses: actions/setup-node@v3
with:
node-version: 18
- run: yarn
- run: yarn test

Просмотреть файл

@ -2,10 +2,10 @@
"changes": [
{
"type": "minor",
"comment": "`nodeExecTask`: add `enableTypeScript: esm` setting to run the task in `ts-node` with ESM support",
"comment": "`nodeExecTask`: add new setting `executor: 'ts-node' | 'ts-node-esm' | 'tsx'` to enable running the task with TS support in an ES module context (or in any context with `tsx`). Also use use `module`/`moduleResolution` `\"Node16\"` if the local TS version supports it.",
"packageName": "just-scripts",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}
]
}
}

Просмотреть файл

@ -0,0 +1,18 @@
{
"changes": [
{
"type": "minor",
"comment": "Add a new binary `just-scripts-esm` which uses `tsx` or `ts-node` (depending on which is present) to run the CLI",
"packageName": "just-scripts",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
},
{
"type": "minor",
"comment": "Add a new binary `just-esm` which uses `tsx` or `ts-node` (depending on which is present) to run the CLI",
"packageName": "just-task",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}
]
}

Просмотреть файл

@ -2,10 +2,10 @@
"changes": [
{
"type": "patch",
"comment": "Detect if already running in ts-node or tsx and skip call to ts-node register",
"comment": "Detect if already running in `ts-node` or `tsx` and skip call to `ts-node` `register()`",
"packageName": "just-task",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}
]
}
}

Просмотреть файл

@ -2,10 +2,10 @@
"changes": [
{
"type": "minor",
"comment": "For ts-node, use `module`/`moduleResolution` `\"Node16\"` if the local TS version supports it",
"comment": "For `ts-node`, use `module`/`moduleResolution` `\"Node16\"` if the local TS version supports it",
"packageName": "just-task",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}
]
}
}

Просмотреть файл

@ -2,7 +2,7 @@
"changes": [
{
"type": "minor",
"comment": "Partial support for ESM packages: load `just.config.js` as ESM if package has `type: \"module\"`, and support explicit `.cjs`, `.mjs`, and `.cts` extensions. (`.ts` configs in packages with `type: \"module\"` are not supported due to limitations with `ts-node` `register()`.)",
"comment": "Add support for more config extensions (`.cjs`, `.mjs`, `.cts`, `.mts`) and load ESM configs with `import()`",
"packageName": "just-task",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"

35
notes.md Normal file
Просмотреть файл

@ -0,0 +1,35 @@
Update the config loading approach and introduce new binaries to support ESM configs. This addresses part of #572 and #686 as much as is possible without a potential breaking change.
### Allow more extensions for config files
- `.cjs` and `.mjs` configs will "just work"
- `.cts` configs will work with `ts-node` 10 (which added `.cts` and `.mts` support)
- `.mts` is allowed, but will ONLY work if with the new `just-esm`/`just-scripts-esm` binaries (next section)
- This restriction also applies to `.ts` configs in a **module context** (`type: "module"` in package.json)
### Add new binaries to support TS ESM config files (+ `tsx` support)
Add two new binaries which **spawn a new process** to run the Just CLI via `tsx` or `ts-node-esm` (depending on what's installed in the repo):
- `just-scripts-esm` (`just-scripts` package)
- `just-esm` (`just-task` package)
You **must** use one of these binaries if you want to use a `.ts` or `.mts` config file **in a module context** (`type: "module"` in package.json).
They should also work in a CJS context, like if you just want to use `tsx` instead of `ts-node`. (It unfortunately doesn't appear to be possible to enable `tsx` or `ts-node-esm` with an after-the-fact `register()` call like with traditional `ts-node`.)
### Load ESM configs with `import()`
Update the config loading logic to use `import()` for ESM configs. A config is considered ESM if one of the following is true:
- It has a `.mjs` or `.mts` extension
- The package.json has `"type": "module"` and the config has a `.js` or `.ts` extension (but see notes below)
This required updating the repo version of `typescript` to 4.7 so that I could use the `module`/`moduleResolution` `"Node16"` setting to prevent the `import()` from being transpiled to `require()`.
### Updates to `ts-node` setup
Also made some updates to the `ts-node` `register()` logic, for the previous TS loading approach
- Check the local `typescript` version and if it's >= 4.7, set `ts-node`'s `module`/`moduleResolution` to `"Node16"` (to prevent dynamic `import()` from being transpiled to `require()`)
- Skip `ts-node` `register()` if already running in `ts-node` or `tsx` (from the new binaries)

Просмотреть файл

@ -85,13 +85,27 @@
"**"
],
"isIgnored": true
},
{
"label": "test-* just-scripts",
"dependencies": [
"just-scripts"
],
"packages": [
"test-*"
]
}
]
},
"workspaces": {
"packages": [
"packages/*",
"scripts"
"scripts",
"test-scenarios/*"
],
"nohoist": [
"**/ts-node",
"**/tsx"
]
}
}

Просмотреть файл

@ -1,3 +0,0 @@
This package has `"type": "module"` in its `package.json`, so its `just.config.js` must be loaded as ESM.
The just config also defines a `nodeExecTask` which must be handled as ESM.

Просмотреть файл

@ -1,7 +0,0 @@
import { nodeExecTask, tscTask, task, parallel } from 'just-scripts';
task('typescript', tscTask({}));
task('customNodeTask', nodeExecTask({ args: ['./tasks/customTask.js'] }));
task('build', parallel('customNodeTask', 'typescript'));

Просмотреть файл

@ -1,14 +0,0 @@
{
"name": "example-lib-esm",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "just-scripts build"
},
"license": "MIT",
"devDependencies": {
"just-scripts": ">=2.2.3 <3.0.0",
"ts-node": "^9.1.1"
}
}

Просмотреть файл

@ -1 +0,0 @@
const a = 5;

Просмотреть файл

@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"declarationMap": true,
"outDir": "lib",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": []
},
"include": ["src"]
}

Просмотреть файл

@ -0,0 +1,2 @@
#!/usr/bin/env node
require('../lib/cli-esm');

Просмотреть файл

@ -12,7 +12,8 @@
"author": "",
"main": "./lib/index.js",
"bin": {
"just-scripts": "bin/just-scripts.js"
"just-scripts": "bin/just-scripts.js",
"just-scripts-esm": "bin/just-scripts-esm.js"
},
"scripts": {
"api": "api-extractor run",
@ -45,8 +46,10 @@
"@types/prompts": "^2.4.2",
"@types/run-parallel-limit": "^1.0.0",
"@types/supports-color": "^8.1.1",
"@types/tmp": "^0.2.6",
"@types/webpack": "^4.41.33",
"async-done": "^2.0.0",
"esbuild": "^0.9.6"
"esbuild": "^0.9.6",
"tmp": "^0.2.3"
}
}

Просмотреть файл

@ -0,0 +1,146 @@
import { spawnSync, SpawnSyncReturns } from 'child_process';
import * as fse from 'fs-extra';
import * as path from 'path';
import * as tmp from 'tmp';
tmp.setGracefulCleanup();
const newNodeTest = Number(process.version.slice(1).split('.')[0]) >= 18 ? test : test.skip;
const justScriptsRoot = path.resolve(__dirname, '../..');
// const packageJson = require(path.join(justScriptsRoot, 'package.json')) as { bin: Record<string, string> };
// const oldCli = require.resolve('just-scripts/lib/cli');
// const newCli = require.resolve('just-scripts/lib/cli-esm');
// function getSpawnSyncError(params: { command: string; cwd: string; result: SpawnSyncReturns<string> }) {
// const { command, cwd, result } = params;
// return [`${command} failed in ${cwd}:`, 'stdout:', result.stdout, 'stderr:', result.stderr].join('\n\n');
// }
type FixtureOptions = {
// configFile: string;
packageName: string;
configExt: string[];
configContent: string;
packageType?: 'module' | 'commonjs';
nodeExecTask?: { filename: string; content: string };
// configSyntax: 'import' | 'require';
// nodeExecTask?: NodeExecTaskOptions & { filename: string; content: string };
dependencies?: Record<string, string>;
};
const basicTestTask = `task('test', () => console.log('hello'));`;
const basicCjsConfig = `const { task } = require('just-scripts'); ${basicTestTask}`;
const basicEsmConfig = `import { task } from 'just-scripts'; ${basicTestTask}`;
const jsFixtures: Record<string, FixtureOptions> = {
'CJS syntax config in CJS package': {
packageName: 'cjs-config-in-cjs-package',
configExt: ['.js', '.cjs'],
configContent: basicCjsConfig,
},
'ESM syntax config in ESM package': {
packageName: 'esm-config-in-esm-package',
configExt: ['.js'],
configContent: basicEsmConfig,
packageType: 'module',
},
'.cjs config in ESM package': {
packageName: 'cjs-config-in-esm-package',
configExt: ['.cjs'],
configContent: basicCjsConfig,
packageType: 'module',
},
'.mjs config in CJS package': {
packageName: 'mjs-config-in-cjs-package',
configExt: ['.mjs'],
configContent: basicEsmConfig,
},
};
function createFixture(params: FixtureOptions) {
const { configFile, configContent, nodeExecTask, packageType, dependencies } = params;
const testFolderPath = tmp.dirSync({
prefix: 'just-scripts-test-',
// "Unsafe" means try to delete on exit even if it still contains files...which actually is
// safe for purposes of most tests
unsafeCleanup: true,
}).name;
process.chdir(testFolderPath);
fse.writeJsonSync(path.join(testFolderPath, 'package.json'), {
name: 'test',
version: '1.0.0',
dependencies,
type: packageType,
});
fse.writeFileSync(path.join(testFolderPath, configFile), configContent);
if (nodeExecTask) {
fse.writeFileSync(path.join(testFolderPath, nodeExecTask.filename), nodeExecTask.content);
}
if (dependencies) {
const yarnCmd = process.platform === 'win32' ? 'yarn.cmd' : 'yarn';
const result = spawnSync(yarnCmd, { cwd: testFolderPath, encoding: 'utf8' });
expect(result).toMatchObject({ status: 0 });
// if (result.status !== 0) {
// throw new Error(getSpawnSyncError({ command: 'yarn', cwd: testFolderPath, result }));
// }
} else {
fse.mkdirSync(path.join(testFolderPath, 'node_modules/.bin'), { recursive: true });
}
fse.symlinkSync(justScriptsRoot, path.join(testFolderPath, 'node_modules/just-scripts'));
// for (const binName of Object.keys(packageJson.bin)) {
// fse.symlinkSync(
// path.join(justScriptsRoot, packageJson.bin[binName]),
// path.join(testFolderPath, 'node_modules/.bin', binName),
// );
// }
return testFolderPath;
}
function runCli(params: { args: string[]; cwd: string }) {
const { cwd, args } = params;
const result = spawnSync(process.execPath, args, { cwd, encoding: 'utf8' });
const stderr = result.stderr
.split('\n')
// remove "Debugger attached" etc when debugging
.filter(line => !line.toLowerCase().includes('debugger'))
.join('\n');
return { ...result, stderr };
}
describe.each([['cli'], ['cli-esm']])('%s', cliName => {
const jsOnlyTest = cliName === 'cli-esm' ? test.skip : test;
const cli = require.resolve('just-scripts/lib/' + cliName);
let tmpdir = '';
const cwd = process.cwd();
afterEach(() => {
process.chdir(cwd);
fse.removeSync(tmpdir);
});
jsOnlyTest('JS config', () => {
tmpdir = createFixture({
configFile: 'just.config.js',
configContent: `
const { task } = require('just-scripts');
task('test', () => console.log('hello'));`,
});
const result = runCli({ args: [cli, 'test'], cwd: tmpdir });
expect(result).toMatchObject({
status: 0,
stdout: expect.stringContaining('hello'),
stderr: '',
});
});
});

Просмотреть файл

@ -0,0 +1 @@
import 'just-task/lib/cli-esm';

Просмотреть файл

@ -1,26 +1,30 @@
import { SpawnOptions } from 'child_process';
import { spawn } from 'just-scripts-utils';
import { logger, TaskFunction } from 'just-task';
import { resolveCwd, _tryResolve } from 'just-task/lib/resolve';
import { logger, _spawnWithTS, TaskFunction, TSExecutor } from 'just-task';
import { getTsNodeEnv } from '../typescript/getTsNodeEnv';
export interface NodeExecTaskOptions {
/**
* Arguments to be passed into a spawn call for webpack dev server. This can be used to do things
* like increase the heap space for the JS engine to address out of memory issues.
* Arguments to be passed to the spawn call.
*/
args?: string[];
/**
* Environment variables to be passed to the webpack-cli
* Environment variables to be passed to the spawn call.
*/
env?: NodeJS.ProcessEnv;
/**
* Whether this nodeExec task should use ts-node to execute the binary.
* If set to `esm`, it will use `ts-node/esm` instead of `ts-node/register`.
* Whether this task should use ts-node to execute the binary.
* NOTE: To use `ts-node/esm` or `tsx`, set `executor` instead.
*/
enableTypeScript?: boolean | 'esm';
enableTypeScript?: boolean;
/**
* The TS executor to use for running the task.
* If neither this nor `enableTypeScript` is set, runs as standard JS.
*/
executor?: TSExecutor;
/**
* The tsconfig file to pass to ts-node for Typescript config
@ -28,7 +32,8 @@ export interface NodeExecTaskOptions {
tsconfig?: string;
/**
* Transpile the config only
* Only transpile the task file (don't type check)
* @default true
*/
transpileOnly?: boolean;
@ -40,23 +45,24 @@ export interface NodeExecTaskOptions {
export function nodeExecTask(options: NodeExecTaskOptions): TaskFunction {
return function () {
const { spawnOptions, enableTypeScript, tsconfig, transpileOnly } = options;
const { enableTypeScript, tsconfig, transpileOnly, executor } = options;
const args = [...(options.args || [])];
const env = { ...options.env };
const esm = enableTypeScript === 'esm';
const tsNodeHelper = resolveCwd(esm ? 'ts-node/esm.mjs' : 'ts-node/register');
const spawnOpts: SpawnOptions = { stdio: 'inherit', ...options.spawnOptions, env };
const nodeExecPath = process.execPath;
if (enableTypeScript && tsNodeHelper) {
args.unshift(esm ? '--loader' : '-r', tsNodeHelper);
Object.assign(env, getTsNodeEnv(tsconfig, transpileOnly, esm));
logger.info('Executing [TS]: ' + [nodeExecPath, ...args].join(' '));
} else {
if (!enableTypeScript && !executor) {
logger.info('Executing: ' + [nodeExecPath, ...args].join(' '));
return spawn(nodeExecPath, args, spawnOpts);
}
return spawn(nodeExecPath, args, { stdio: 'inherit', env, ...spawnOptions });
Object.assign(env, getTsNodeEnv(tsconfig, transpileOnly));
return _spawnWithTS({
cmd: nodeExecPath,
args,
opts: spawnOpts,
executor: executor || 'ts-node',
});
};
}

Просмотреть файл

@ -1,29 +1,38 @@
import { logger } from 'just-task';
import { logger, TSExecutor, _tsSupportsModernResolution } from 'just-task';
/**
* Get environment variables for `ts-node` or `tsx`.
*/
export function getTsNodeEnv(
tsconfig?: string,
transpileOnly?: boolean,
esm?: boolean,
executor: TSExecutor = 'ts-node',
): { [key: string]: string | undefined } {
const env: { [key: string]: string | undefined } = {};
if (tsconfig) {
logger.info(`[TS] Using ${tsconfig}`);
env.TS_NODE_PROJECT = tsconfig;
} else {
if (executor === 'tsx') {
env.TSX_TSCONFIG_PATH = tsconfig;
} else {
env.TS_NODE_PROJECT = tsconfig;
}
} else if (executor.startsWith('ts-node')) {
const supportsNode16 = _tsSupportsModernResolution();
const compilerOptions = JSON.stringify({
target: 'es2017',
moduleResolution: esm ? 'NodeNext' : 'node',
module: esm ? 'NodeNext' : 'commonjs',
moduleResolution: supportsNode16 ? 'Node16' : 'node',
module: supportsNode16 ? 'Node16' : 'commonjs',
skipLibCheck: true,
});
logger.info(`[TS] Using these compilerOptions: ${compilerOptions}`);
env.TS_NODE_COMPILER_OPTIONS = compilerOptions;
}
if (transpileOnly !== false) {
if (executor.startsWith('ts-node') && transpileOnly !== false) {
logger.info('[TS] Using transpileOnly mode');
env.TS_NODE_TRANSPILE_ONLY = 'true';
// tsx always transpiles only (no type checking)
}
return env;

Просмотреть файл

@ -0,0 +1,29 @@
#!/usr/bin/env node
const path = require('path');
const configIndex = process.argv.indexOf('--config');
let configFilePath = '';
if (configIndex > -1 && process.argv.length >= configIndex + 2) {
const configFile = process.argv[configIndex + 1];
configFilePath = path.resolve(path.dirname(configFile));
}
const resolvePath = configFilePath || process.cwd();
let localCmd = null;
try {
localCmd = require.resolve('just-task/lib/cli-esm.js', { paths: [resolvePath, __dirname] });
} catch (e) {
console.error('Please install a local copy of just-task.');
process.exit(1);
}
try {
require(localCmd);
} catch (e) {
console.log('Just encountered an error', e);
process.exit(1);
}

Просмотреть файл

@ -12,6 +12,7 @@ import type { FSWatcher } from 'chokidar';
import { Logger } from 'just-task-logger';
import { logger } from 'just-task-logger';
import { mark } from 'just-task-logger';
import { SpawnOptions } from 'child_process';
import type { Stats } from 'fs';
import { TaskFunction as TaskFunction_2 } from 'undertaker';
import { TaskFunctionParams } from 'undertaker';
@ -91,6 +92,14 @@ interface ResolveOptions {
// @public (undocumented)
export function series(...tasks: Task[]): Undertaker.TaskFunction;
// @internal
export function _spawnWithTS(params: {
cmd: string;
args?: ReadonlyArray<string>;
opts?: SpawnOptions;
executor?: TSExecutor | TSExecutor[];
}): Promise<void>;
// @public (undocumented)
export type Task = string | TaskFunction;
@ -115,6 +124,12 @@ export interface TaskFunction extends TaskFunctionParams {
description?: string;
}
// @public (undocumented)
export type TSExecutor = 'ts-node' | 'ts-node-esm' | 'tsx';
// @internal
export function _tsSupportsModernResolution(): boolean;
// @public (undocumented)
export const undertaker: Undertaker;

Просмотреть файл

@ -12,7 +12,8 @@
"author": "Ken Chau <kchau@microsoft.com>",
"main": "lib/index.js",
"bin": {
"just": "bin/just.js"
"just": "bin/just.js",
"just-esm": "bin/just-esm.js"
},
"scripts": {
"api": "api-extractor run",

Просмотреть файл

@ -0,0 +1,24 @@
import { logger } from 'just-task-logger';
import * as path from 'path';
import { _spawnWithTS } from './spawnWithTS';
_spawnWithTS({
cmd: path.join(__dirname, 'cli.js'),
args: process.argv.slice(2),
executor: ['tsx', 'ts-node-esm'],
opts: {
stdio: 'inherit',
env: {
...process.env,
NODE_OPTIONS: [
// Preserve any existing NODE_OPTIONS that were passed as a variable
...(process.env.NODE_OPTIONS ? [process.env.NODE_OPTIONS] : []),
// Also pass through the node options that were passed to this process
...process.execArgv,
].join(' '),
},
},
}).catch(err => {
logger.error(err);
process.exit(1);
});

Просмотреть файл

@ -41,6 +41,14 @@ export async function readConfig(): Promise<{ [key: string]: TaskFunction } | vo
const packageIsESM = packageJson?.type === 'module';
const ext = path.extname(configFile).toLowerCase();
if (!process.env.JUST_TASK_TS && (ext === '.mts' || (packageIsESM && ext === '.ts'))) {
const configType = ext === '.mts' ? 'explicit .mts config' : '.ts config in an ESM package';
const binName = path.basename(process.argv[1]).split('.')[0];
logger.error(
`To use a ${configType}, you must use ${binName}-esm. (Alternatively, you can change the config to .cts.)`,
);
process.exit(1);
}
if (/^\.[cm]?ts$/.test(ext)) {
const tsSuccess = enableTypeScript({ transpileOnly: true, configFile });
@ -59,19 +67,6 @@ export async function readConfig(): Promise<{ [key: string]: TaskFunction } | vo
} catch (e) {
logger.error(`Error loading configuration file: ${configFile}`);
logger.error((e as Error).stack || (e as Error).message || e);
if (ext === '.mts' || (packageIsESM && ext === '.ts')) {
// We can't directly support these with ts-node because we're calling register() rather than
// creating a child process with the custom --loader.
// (Related: https://typestrong.org/ts-node/docs/imports/)
const binPath = path.relative(process.cwd(), process.argv[1]);
logger.error('');
logger.error(
'Just does not directly support ESM TypeScript configuration files. You must either ' +
`use a .cts file, or call the just binary (${binPath}) via ts-node or tsx.`,
);
}
process.exit(1);
}
@ -81,8 +76,8 @@ export async function readConfig(): Promise<{ [key: string]: TaskFunction } | vo
try {
await configModule();
} catch (e) {
logger.error(`Invalid configuration file: ${configFile}`);
logger.error(`Error running config function: ${(e as Error).stack || (e as Error).message || e}`);
logger.error(`Error running function from configuration file: ${configFile}`);
logger.error((e as Error).stack || (e as Error).message || e);
process.exit(1);
}
}

Просмотреть файл

@ -1,7 +1,7 @@
import * as fse from 'fs-extra';
import * as path from 'path';
import { resolve } from './resolve';
import { logger } from 'just-task-logger';
import { _tsSupportsModernResolution } from './tsSupportsModernResolution';
/**
* Enable typescript support with ts-node.
@ -10,18 +10,8 @@ import { logger } from 'just-task-logger';
export function enableTypeScript(params: { transpileOnly?: boolean; configFile?: string }): boolean {
const { transpileOnly = true, configFile = '' } = params;
// Try to determine if the user is already running with a known transpiler.
// ts-node makes this easy by setting process.env.TS_NODE.
// tsx doesn't set a variable, so check a few places it might show up.
const contextVals = [
...process.argv,
...process.execArgv,
process.env._,
process.env.npm_lifecycle_event,
process.env.npm_config_argv,
];
if (process.env.TS_NODE || contextVals.some(val => /[^.]tsx\b/.test(val || ''))) {
// It appears the user ran the just CLI with tsx or ts-node, so allow this.
if (process.env.JUST_TASK_TS) {
// Already running with a TS loader (this env is set by spawnWithTS, used by just-scripts-esm binary)
return true;
}
@ -33,15 +23,9 @@ export function enableTypeScript(params: { transpileOnly?: boolean; configFile?:
}
// Use module/moduleResolution "node16" if supported for broadest compatibility
let supportsNode16Setting = false;
const typescriptPackageJson = resolve('typescript/package.json');
if (typescriptPackageJson) {
const typescriptVersion = fse.readJsonSync(typescriptPackageJson).version as string;
const [major, minor] = typescriptVersion.split('.').map(Number);
supportsNode16Setting = major > 4 || (major === 4 && minor >= 7);
}
const supportsNode16Setting = _tsSupportsModernResolution();
const tsNode = require(tsNodeModule) as typeof import('ts-node');
const tsNode = require(tsNodeModule);
const tsNodeMajor = Number(String(tsNode.VERSION || '0').split('.')[0]);
const ext = path.extname(configFile);
if (tsNodeMajor < 10 && ext !== '.ts') {

Просмотреть файл

@ -8,3 +8,5 @@ export { clearCache } from './cache';
export { Logger, logger, mark } from './logger';
export { chain } from './chain';
export { watch } from './watch';
export { _spawnWithTS, TSExecutor } from './spawnWithTS';
export { _tsSupportsModernResolution } from './tsSupportsModernResolution';

Просмотреть файл

@ -0,0 +1,54 @@
import { SpawnOptions } from 'child_process';
import * as fse from 'fs-extra';
import { logger, spawn } from 'just-scripts-utils';
import { resolve } from './resolve';
export type TSExecutor = 'ts-node' | 'ts-node-esm' | 'tsx';
/**
* Run something with a TS executor.
* @internal Exported for use by `just-scripts` only.
*/
export function _spawnWithTS(params: {
cmd: string;
args?: ReadonlyArray<string>;
opts?: SpawnOptions;
/**
* TS executor to use, or order of preference to check for.
* Defaults to `['ts-node', 'tsx']` for compatibility.
*/
executor?: TSExecutor | TSExecutor[];
}): Promise<void> {
const { cmd, opts = {}, executor = ['ts-node', 'tsx'] } = params;
const checkExecutors = typeof executor === 'string' ? [executor] : executor;
let executorBin: string | undefined;
for (const executor of checkExecutors) {
const packageJsonPath = resolve(`${executor === 'ts-node-esm' ? 'ts-node' : executor}/package.json`);
if (!packageJsonPath) {
continue;
}
const packageJson = fse.readJsonSync(packageJsonPath) as { bin: string | Record<string, string> };
const binPath = typeof packageJson.bin === 'string' ? packageJson.bin : packageJson.bin[executor];
if (binPath) {
executorBin = binPath;
break;
}
}
if (!executorBin) {
throw new Error(`Could not find ${checkExecutors.join(' or ')}`);
}
const args = [executorBin, cmd, ...(params.args || [])];
logger.info('Executing [TS]:', process.execPath, args.join(' '));
return spawn(process.execPath, args, {
stdio: 'inherit',
...opts,
env: {
...opts.env,
JUST_TASK_TS: 'true',
},
});
}

Просмотреть файл

@ -0,0 +1,17 @@
import * as fse from 'fs-extra';
import { resolve } from './resolve';
/**
* Returns whether the local version of typescript supports modern resolution settings like `NodeNext`.
* Defaults to false (for compatibility with before this check was added) if typescript is not found.
* @internal Exported for use by `just-scripts` only.
*/
export function _tsSupportsModernResolution(): boolean {
const typescriptPackageJson = resolve('typescript/package.json');
if (typescriptPackageJson) {
const typescriptVersion = fse.readJsonSync(typescriptPackageJson).version as string;
const [major, minor] = typescriptVersion.split('.').map(Number);
return major > 4 || (major === 4 && minor >= 7);
}
return false;
}

Просмотреть файл

@ -0,0 +1,3 @@
const { nodeExecTask, task } = require('just-scripts');
task('test', nodeExecTask({ args: ['./tasks/customTask.mjs'] }));

Просмотреть файл

@ -0,0 +1,11 @@
{
"name": "test-mjs",
"private": true,
"version": "1.0.0",
"scripts": {
"test": "just-scripts test"
},
"devDependencies": {
"just-scripts": "*"
}
}

Просмотреть файл

@ -0,0 +1,7 @@
import * as path from 'path';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
const dirname = path.dirname(fileURLToPath(import.meta.url));
export const packageJson = fs.readFileSync(path.resolve(dirname, '../package.json'), 'utf-8');

Просмотреть файл

@ -0,0 +1,3 @@
import { nodeExecTask, task } from 'just-scripts';
task('test', nodeExecTask({ args: ['./customTask.mjs'] }));

Просмотреть файл

@ -0,0 +1,11 @@
{
"name": "test-config-mjs",
"private": true,
"version": "1.0.0",
"scripts": {
"test": "just-scripts test"
},
"devDependencies": {
"just-scripts": "*"
}
}

Просмотреть файл

@ -1778,6 +1778,11 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
"@types/tmp@^0.2.6":
version "0.2.6"
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.6.tgz#d785ee90c52d7cc020e249c948c36f7b32d1e217"
integrity sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==
"@types/uglify-js@*":
version "3.17.2"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.2.tgz#a2ba86fd524f6281a7655463338c546f845b29c3"
@ -10928,6 +10933,11 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==
tmp@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"