Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ts-node -p 123 equivalent to -pe 123 #2019

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
191 changes: 109 additions & 82 deletions src/bin.ts
Expand Up @@ -94,90 +94,101 @@
return phase4(state);
}

function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
/** @internal */
export type ParsedArgv = ReturnType<typeof parseArgv>;

/** @internal */
export function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
arg ??= require('arg');

// Attempt parsing w/two specs to support `-p 123` or `-pe 123`
const StringArray = [String] as [StringConstructor];
const spec1 = {
// Node.js-like options.
'--eval': String,
'--interactive': Boolean,
'--print': String,
'--require': StringArray,

// CLI options.
'--help': Boolean,
'--cwdMode': Boolean,
'--scriptMode': Boolean,
'--version': arg.COUNT,
'--showConfig': Boolean,
'--esm': Boolean,

// Project options.
'--cwd': String,
'--files': Boolean,
'--compiler': String,
'--compilerOptions': parse,
'--project': String,
'--ignoreDiagnostics': StringArray,
'--ignore': StringArray,
'--transpileOnly': Boolean,
'--transpiler': String,
'--swc': Boolean,
'--typeCheck': Boolean,
'--compilerHost': Boolean,
'--pretty': Boolean,
'--skipProject': Boolean,
'--skipIgnore': Boolean,
'--preferTsExts': Boolean,
'--logError': Boolean,
'--emit': Boolean,
'--scope': Boolean,
'--scopeDir': String,
'--noExperimentalReplAwait': Boolean,
'--experimentalSpecifierResolution': String,

// Aliases.
'-e': '--eval',
'-i': '--interactive',
'-p': '--print',
'-r': '--require',
'-h': '--help',
'-s': '--script-mode',
'-v': '--version',
'-T': '--transpileOnly',
'-H': '--compilerHost',
'-I': '--ignore',
'-P': '--project',
'-C': '--compiler',
'-D': '--ignoreDiagnostics',
'-O': '--compilerOptions',
'--dir': '--cwd',

// Support both tsc-style camelCase and node-style hypen-case for *all* flags
'--cwd-mode': '--cwdMode',
'--script-mode': '--scriptMode',
'--show-config': '--showConfig',
'--compiler-options': '--compilerOptions',
'--ignore-diagnostics': '--ignoreDiagnostics',
'--transpile-only': '--transpileOnly',
'--type-check': '--typeCheck',
'--compiler-host': '--compilerHost',
'--skip-project': '--skipProject',
'--skip-ignore': '--skipIgnore',
'--prefer-ts-exts': '--preferTsExts',
'--log-error': '--logError',
'--scope-dir': '--scopeDir',
'--no-experimental-repl-await': '--noExperimentalReplAwait',
'--experimental-specifier-resolution': '--experimentalSpecifierResolution',
};
const spec2 = {
...spec1,
'--print': Boolean,
};
let argParse;
try {
argParse = arg(spec1, { argv, stopAtPositional: true });
} catch (e) {
argParse = arg(spec2, { argv, stopAtPositional: true });
}
const args = {
...entrypointArgs,
...arg(
{
// Node.js-like options.
'--eval': String,
'--interactive': Boolean,
'--print': Boolean,
'--require': [String],

// CLI options.
'--help': Boolean,
'--cwdMode': Boolean,
'--scriptMode': Boolean,
'--version': arg.COUNT,
'--showConfig': Boolean,
'--esm': Boolean,

// Project options.
'--cwd': String,
'--files': Boolean,
'--compiler': String,
'--compilerOptions': parse,
'--project': String,
'--ignoreDiagnostics': [String],
'--ignore': [String],
'--transpileOnly': Boolean,
'--transpiler': String,
'--swc': Boolean,
'--typeCheck': Boolean,
'--compilerHost': Boolean,
'--pretty': Boolean,
'--skipProject': Boolean,
'--skipIgnore': Boolean,
'--preferTsExts': Boolean,
'--logError': Boolean,
'--emit': Boolean,
'--scope': Boolean,
'--scopeDir': String,
'--noExperimentalReplAwait': Boolean,
'--experimentalSpecifierResolution': String,

// Aliases.
'-e': '--eval',
'-i': '--interactive',
'-p': '--print',
'-r': '--require',
'-h': '--help',
'-s': '--script-mode',
'-v': '--version',
'-T': '--transpileOnly',
'-H': '--compilerHost',
'-I': '--ignore',
'-P': '--project',
'-C': '--compiler',
'-D': '--ignoreDiagnostics',
'-O': '--compilerOptions',
'--dir': '--cwd',

// Support both tsc-style camelCase and node-style hypen-case for *all* flags
'--cwd-mode': '--cwdMode',
'--script-mode': '--scriptMode',
'--show-config': '--showConfig',
'--compiler-options': '--compilerOptions',
'--ignore-diagnostics': '--ignoreDiagnostics',
'--transpile-only': '--transpileOnly',
'--type-check': '--typeCheck',
'--compiler-host': '--compilerHost',
'--skip-project': '--skipProject',
'--skip-ignore': '--skipIgnore',
'--prefer-ts-exts': '--preferTsExts',
'--log-error': '--logError',
'--scope-dir': '--scopeDir',
'--no-experimental-repl-await': '--noExperimentalReplAwait',
'--experimental-specifier-resolution': '--experimentalSpecifierResolution',
},
{
argv,
stopAtPositional: true,
}
),
...argParse,
};

// Only setting defaults for CLI-specific flags
Expand All @@ -191,8 +202,8 @@
'--version': version = 0,
'--showConfig': showConfig,
'--require': argsRequire = [],
'--eval': code = undefined,
'--print': print = false,
'--eval': _code = undefined,
'--print': _print = false,
'--interactive': interactive = false,
'--files': files,
'--compiler': compiler,
Expand All @@ -218,6 +229,22 @@
'--esm': esm,
_: restArgs,
} = args;

let print: boolean;
let code: string | undefined;
if (typeof _print === 'string') {
// Reject `-p 123 -e 456`
if (_code != null) {
throw new Error('Conflicting options -p and -e. Hint: to specify tsconfig, use -P instead of -p');

Check warning on line 238 in src/bin.ts

View check run for this annotation

Codecov / codecov/patch

src/bin.ts#L238

Added line #L238 was not covered by tests
}
code = _print;
print = true;
} else {
// Deliberately allow -p withotu any code
print = _print;
code = _code;
}

return {
// Note: argv and restArgs may be overwritten by child process
argv: process.argv,
Expand Down
32 changes: 32 additions & 0 deletions src/test/cli-args.spec.ts
@@ -0,0 +1,32 @@
import { context } from './testlib';
import { ctxTsNode, testsDirRequire } from './helpers';
import type { ParsedArgv } from '../bin';
const test = context(ctxTsNode);

const argParseMacro = test.macro(
(args: string[], entrypointArgs: Record<string, any> | undefined, expectation: Partial<ParsedArgv>) => [
() => `"${args.join(' ')}"${entrypointArgs ? ` w/entrypoint args: ${JSON.stringify(entrypointArgs)}` : ``}`,
async (t) => {
const parsedArgs = t.context.tsNodeBin.parseArgv(args, entrypointArgs ?? {});
t.like(parsedArgs, expectation);
},
]
);

test(argParseMacro, ['-pe', '123'], undefined, {
print: true,
code: '123',
restArgs: [],
});

test(argParseMacro, ['-p', '123'], undefined, {
print: true,
code: '123',
restArgs: [],
});

test(argParseMacro, ['-e', '123'], undefined, {
print: false,
code: '123',
restArgs: [],
});
4 changes: 3 additions & 1 deletion src/test/helpers/ctx-ts-node.ts
Expand Up @@ -6,14 +6,16 @@ import { join } from 'path';
import type { ExecutionContext } from '../testlib';
import { sync as rimrafSync } from 'rimraf';
import { TEST_DIR } from './paths';
import { testsDirRequire, tsNodeTypes } from './misc';
import { testsDirRequire, tsNodeBinTypes, tsNodeTypes } from './misc';

/** Pass to `test.context()` to get access to the ts-node API under test */
export async function ctxTsNode() {
await installTsNode();
const tsNodeUnderTest: typeof tsNodeTypes = testsDirRequire('ts-node');
const tsNodeBin: typeof tsNodeBinTypes = testsDirRequire('ts-node/dist/bin');
return {
tsNodeUnderTest,
tsNodeBin,
};
}
export namespace ctxTsNode {
Expand Down
3 changes: 2 additions & 1 deletion src/test/helpers/misc.ts
@@ -1,10 +1,11 @@
/** types from ts-node under test */
import type * as tsNodeTypes from '../../index';
import type * as tsNodeBinTypes from '../../bin';
import { TEST_DIR } from './paths';
import { join } from 'path';
import { promisify } from 'util';
import { createRequire } from 'module';
export { tsNodeTypes };
export { tsNodeTypes, tsNodeBinTypes };

export const testsDirRequire = createRequire(join(TEST_DIR, 'index.js'));

Expand Down
4 changes: 2 additions & 2 deletions src/test/repl/repl.spec.ts
Expand Up @@ -222,8 +222,8 @@ test.suite('top level await', ({ contextEach }) => {
});

test('should pass upstream test cases', async (t) => {
const { tsNodeUnderTest } = t.context;
await upstreamTopLevelAwaitTests({ TEST_DIR, tsNodeUnderTest });
const { tsNodeUnderTest, tsNodeBin } = t.context;
await upstreamTopLevelAwaitTests({ TEST_DIR, tsNodeUnderTest, tsNodeBin });
});
});

Expand Down