diff --git a/package.json b/package.json index 6523dd29b..3db9249fc 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "@types/rimraf": "^3.0.0", "@types/semver": "^7.1.0", "@types/source-map-support": "^0.5.0", + "@yarnpkg/fslib": "^2.4.0", "ava": "^3.15.0", "axios": "^0.21.1", "chai": "^4.0.1", @@ -139,6 +140,7 @@ "peerDependencies": { "@swc/core": ">=1.2.45", "@swc/wasm": ">=1.2.45", + "@types/node": "*", "typescript": ">=2.7" }, "peerDependenciesMeta": { diff --git a/src/index.spec.ts b/src/index.spec.ts index 3f52167f9..3dc32c939 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -12,6 +12,7 @@ import semver = require('semver'); import ts = require('typescript'); import proxyquire = require('proxyquire'); import type * as tsNodeTypes from './index'; +import * as fs from 'fs'; import { unlinkSync, existsSync, @@ -21,6 +22,7 @@ import { copyFileSync, writeFileSync, } from 'fs'; +import { NodeFS, npath } from '@yarnpkg/fslib'; import * as promisify from 'util.promisify'; import { sync as rimrafSync } from 'rimraf'; import type _createRequire from 'create-require'; @@ -31,6 +33,8 @@ import { PassThrough } from 'stream'; import * as getStream from 'get-stream'; import { once } from 'lodash'; +const xfs = new NodeFS(fs); + type TestExecReturn = { stdout: string; stderr: string; @@ -715,6 +719,38 @@ test.suite('ts-node', (test) => { ); }); } + test('implicitly loads @types/node even when not installed within local directory', async ({ + context: { tempDir }, + }) => { + const { err, stdout, stderr } = await exec( + `${BIN_PATH} -pe process.env.foo`, + { + cwd: tempDir, + env: { ...process.env, foo: 'hello world' }, + } + ); + expect(err).to.equal(null); + expect(stdout).to.equal('hello world\n'); + }); + test('implicitly loads local @types/node', async ({ + context: { tempDir }, + }) => { + await xfs.copyPromise( + npath.toPortablePath(tempDir), + npath.toPortablePath(join(TEST_DIR, 'local-types-node')) + ); + const { err, stdout, stderr } = await exec( + `${BIN_PATH} -pe process.env.foo`, + { + cwd: tempDir, + env: { ...process.env, foo: 'hello world' }, + } + ); + expect(err).to.not.equal(null); + expect(stderr).to.contain( + "Property 'env' does not exist on type 'LocalNodeTypes_Process'" + ); + }); } ); diff --git a/src/index.ts b/src/index.ts index df02a8b1a..7428d51a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -855,7 +855,7 @@ export function create(rawOptions: CreateOptions = {}): Service { ): (_ts.ResolvedTypeReferenceDirective | undefined)[] => { // Note: seems to be called with empty typeDirectiveNames array for all files. return typeDirectiveNames.map((typeDirectiveName) => { - const { + let { resolvedTypeReferenceDirective, } = ts.resolveTypeReferenceDirective( typeDirectiveName, @@ -864,6 +864,28 @@ export function create(rawOptions: CreateOptions = {}): Service { serviceHost, redirectedReference ); + if (typeDirectiveName === 'node' && !resolvedTypeReferenceDirective) { + // Resolve @types/node relative to project first, then __dirname (copy logic from elsewhere / refactor into reusable function) + const typesNodePackageJsonPath = require.resolve( + '@types/node/package.json', + { + paths: [configFilePath ?? cwd, __dirname], + } + ); + const typeRoots = [resolve(typesNodePackageJsonPath, '../..')]; + ({ + resolvedTypeReferenceDirective, + } = ts.resolveTypeReferenceDirective( + typeDirectiveName, + containingFile, + { + ...config.options, + typeRoots, + }, + serviceHost, + redirectedReference + )); + } if (resolvedTypeReferenceDirective) { fixupResolvedModule(resolvedTypeReferenceDirective); } @@ -1504,7 +1526,10 @@ function readConfig( const skipDefaultCompilerOptions = configFilePath != null; const defaultCompilerOptionsForNodeVersion = skipDefaultCompilerOptions ? undefined - : getDefaultTsconfigJsonForNodeVersion(ts).compilerOptions; + : { + ...getDefaultTsconfigJsonForNodeVersion(ts).compilerOptions, + types: ['node'], + }; // Merge compilerOptions from all sources config.compilerOptions = Object.assign( diff --git a/tests/local-types-node/node_modules/@types/node/index.d.ts b/tests/local-types-node/node_modules/@types/node/index.d.ts new file mode 100644 index 000000000..acc6b1da5 --- /dev/null +++ b/tests/local-types-node/node_modules/@types/node/index.d.ts @@ -0,0 +1,4 @@ +declare const process: LocalNodeTypes_Process; +declare interface LocalNodeTypes_Process { + /* empty */ +} diff --git a/tests/local-types-node/node_modules/@types/node/package.json b/tests/local-types-node/node_modules/@types/node/package.json new file mode 100644 index 000000000..b6d8ad997 --- /dev/null +++ b/tests/local-types-node/node_modules/@types/node/package.json @@ -0,0 +1,3 @@ +{ + "name": "@types/node" +}