diff --git a/package.json b/package.json index b9fa0de0..69078db3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "mocha": "^8.2.1", "proxyquire": "^2.1.0", "ts-node": "^9.0.0", - "typescript": "3.8.3" + "typescript": "4.2.3" }, "engines": { "node": ">=8.0.0" diff --git a/src/config.ts b/src/config.ts index 6d6f957f..01e6f770 100644 --- a/src/config.ts +++ b/src/config.ts @@ -247,18 +247,18 @@ export class Config implements IConfig { s3.templates = { ...s3.templates, target: { + ...s3.templates && s3.templates.target, baseDir: '<%- bin %>', unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-<%- platform %>-<%- arch %><%- ext %>", versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %><%- ext %>", manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- platform %>-<%- arch %>", - ...s3.templates && s3.templates.target, }, vanilla: { + ...s3.templates && s3.templates.vanilla, unversioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %><%- ext %>", versioned: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %><%- bin %>-v<%- version %>/<%- bin %>-v<%- version %><%- ext %>", baseDir: '<%- bin %>', manifest: "<%- channel === 'stable' ? '' : 'channels/' + channel + '/' %>version", - ...s3.templates && s3.templates.vanilla, }, } @@ -327,7 +327,7 @@ export class Config implements IConfig { return Promise.all((p.hooks[event] || []) .map(async hook => { try { - const f = tsPath(p.root, hook) + const f = tsPath(p.root, hook, this.pjson.oclif.tsConfig) debug('start', f) const search = (m: any): Hook => { if (typeof m === 'function') return m diff --git a/src/pjson.ts b/src/pjson.ts index e0b8e6a0..23e387a3 100644 --- a/src/pjson.ts +++ b/src/pjson.ts @@ -1,8 +1,9 @@ export interface PJSON { [k: string]: any; - dependencies?: {[name: string]: string}; + dependencies?: { [name: string]: string }; oclif: { schema?: number; + tsConfig?: string; }; } @@ -14,7 +15,7 @@ export namespace PJSON { schema?: number; title?: string; description?: string; - hooks?: { [name: string]: (string | string[]) }; + hooks?: { [name: string]: string | string[] }; commands?: string; plugins?: string[]; devPlugins?: string[]; @@ -76,10 +77,14 @@ export namespace PJSON { export interface User extends PJSON { private?: boolean; oclif: PJSON['oclif'] & { - plugins?: (string | PluginTypes.User | PluginTypes.Link)[]; }; + plugins?: (string | PluginTypes.User | PluginTypes.Link)[]; + }; } - export type PluginTypes = PluginTypes.User | PluginTypes.Link | {root: string} + export type PluginTypes = + | PluginTypes.User + | PluginTypes.Link + | { root: string }; export namespace PluginTypes { export interface User { type: 'user'; diff --git a/src/plugin.ts b/src/plugin.ts index 5dc82363..5d532177 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -211,7 +211,7 @@ export class Plugin implements IPlugin { } get commandsDir() { - return tsPath(this.root, this.pjson.oclif.commands) + return tsPath(this.root, this.pjson.oclif.commands, this.pjson.oclif.tsConfig) } get commandIDs() { diff --git a/src/ts-node.ts b/src/ts-node.ts index 34be09b5..231bd944 100644 --- a/src/ts-node.ts +++ b/src/ts-node.ts @@ -1,6 +1,7 @@ import * as fs from 'fs' import * as path from 'path' import * as TSNode from 'ts-node' +import type * as TS from 'typescript' import Debug from './debug' // eslint-disable-next-line new-cap @@ -11,20 +12,11 @@ const rootDirs: string[] = [] const typeRoots = [`${__dirname}/../node_modules/@types`] export interface TSConfig { - compilerOptions: { - rootDir?: string; - rootDirs?: string[]; - outDir?: string; - target?: string; - esModuleInterop?: boolean; - experimentalDecorators?: boolean; - emitDecoratorMetadata?: boolean; - }; + compilerOptions: TS.CompilerOptions; } -function loadTSConfig(root: string): TSConfig | undefined { - const tsconfigPath = path.join(root, 'tsconfig.json') - let typescript: typeof import('typescript') | undefined +function loadTSConfig(root: string, configPath = 'tsconfig.json'): TSConfig | undefined { + let typescript: typeof TS | undefined try { typescript = require('typescript') } catch { @@ -32,25 +24,39 @@ function loadTSConfig(root: string): TSConfig | undefined { typescript = require(root + '/node_modules/typescript') } catch { } } - - if (fs.existsSync(tsconfigPath) && typescript) { - const tsconfig = typescript.parseConfigFileTextToJson( - tsconfigPath, - fs.readFileSync(tsconfigPath, 'utf8'), - ).config - if (!tsconfig || !tsconfig.compilerOptions) { - throw new Error( - `Could not read and parse tsconfig.json at ${tsconfigPath}, or it ` + + if (typescript) { + const {findConfigFile, readConfigFile, parseJsonConfigFileContent, sys} = typescript + const tsconfigPath = findConfigFile( + root, + sys.fileExists, + configPath, + ) + if (tsconfigPath) { + // Read the user's raw tsconfig file + const readFile = (path: string): string | undefined => fs.readFileSync(path).toString('utf8') + const tsconfig = readConfigFile(tsconfigPath, readFile).config + // Parse the raw config, resolving any configuration files it extends from + const compilerOptions = parseJsonConfigFileContent( + tsconfig, + sys, + root, + ).options + console.log({root, tsconfig, compilerOptions}) + if (!tsconfig || !compilerOptions) { + throw new Error( + `Could not read and parse tsconfig.json at ${tsconfigPath}, or it ` + 'did not contain a "compilerOptions" section.') + } + // Return only the combined compiler options + return {compilerOptions} } - return tsconfig } } -function registerTSNode(root: string) { +function registerTSNode(root: string, configPath?: string) { if (process.env.OCLIF_TS_NODE === '0') return if (tsconfigs[root]) return - const tsconfig = loadTSConfig(root) + const tsconfig = loadTSConfig(root, configPath) if (!tsconfig) return debug('registering ts-node at', root) const tsNodePath = require.resolve('ts-node', {paths: [root, __dirname]}) @@ -71,6 +77,7 @@ function registerTSNode(root: string) { // cache: false, // typeCheck: true, compilerOptions: { + ...tsconfig.compilerOptions, esModuleInterop: tsconfig.compilerOptions.esModuleInterop, target: tsconfig.compilerOptions.target || 'es2017', experimentalDecorators: tsconfig.compilerOptions.experimentalDecorators || false, @@ -92,13 +99,13 @@ function registerTSNode(root: string) { * this is for developing typescript plugins/CLIs * if there is a tsconfig and the original sources exist, it attempts to require ts- */ -export function tsPath(root: string, orig: string): string -export function tsPath(root: string, orig: string | undefined): string | undefined -export function tsPath(root: string, orig: string | undefined): string | undefined { +export function tsPath(root: string, orig: string, configPath?: string): string +export function tsPath(root: string, orig: string | undefined, configPath?: string): string | undefined +export function tsPath(root: string, orig: string | undefined, configPath?: string): string | undefined { if (!orig) return orig orig = path.join(root, orig) try { - registerTSNode(root) + registerTSNode(root, configPath) const tsconfig = tsconfigs[root] if (!tsconfig) return orig const {rootDir, rootDirs, outDir} = tsconfig.compilerOptions diff --git a/test/fixtures/typescript/tsconfig.custom.json b/test/fixtures/typescript/tsconfig.custom.json new file mode 100644 index 00000000..3c9ab9ac --- /dev/null +++ b/test/fixtures/typescript/tsconfig.custom.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json" +} + \ No newline at end of file diff --git a/test/test.ts b/test/test.ts index 175eb103..c1b88343 100644 --- a/test/test.ts +++ b/test/test.ts @@ -5,6 +5,7 @@ import * as Config from '../src' export const fancy = base .register('resetConfig', () => ({ run(ctx: {config: Config.IConfig}) { + // @ts-expect-error delete ctx.config }, })) diff --git a/test/ts-node.test.ts b/test/ts-node.test.ts index 590f3e10..f9d89d64 100644 --- a/test/ts-node.test.ts +++ b/test/ts-node.test.ts @@ -2,7 +2,7 @@ import * as path from 'path' import * as proxyquire from 'proxyquire' import * as tsNode from 'ts-node' -import {TSConfig} from '../src/ts-node' +import {TSConfig, tsPath} from '../src/ts-node' import {expect, fancy} from './test' @@ -40,6 +40,11 @@ describe('tsPath', () => { expect(result).to.equal(path.join(root, orig)) }) + it('should resolve a .ts file using custom config using "extends"', () => { + const result = tsPath(root, orig, 'tsconfig.custom.json') + expect(result).to.equal(path.join(root, orig)) + }) + withMockTsConfig() .it('should leave esModuleInterop undefined by default', ctx => { ctx.tsNodePlugin.tsPath(root, orig) diff --git a/yarn.lock b/yarn.lock index e110bdad..dd5d4246 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1706,12 +1706,11 @@ lodash.zip@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA= -lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: +lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.20: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - log-symbols@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" @@ -2776,10 +2775,10 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typescript@3.8.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" - integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== +typescript@4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" + integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== uglify-js@^3.1.4: version "3.7.3"