зеркало из https://github.com/microsoft/just.git
wip 2024-03
This commit is contained in:
Родитель
49911efb69
Коммит
5eeac8f8fe
|
@ -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"
|
||||
|
|
|
@ -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)
|
16
package.json
16
package.json
|
@ -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": "*"
|
||||
}
|
||||
}
|
10
yarn.lock
10
yarn.lock
|
@ -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"
|
||||
|
|
Загрузка…
Ссылка в новой задаче