-
Notifications
You must be signed in to change notification settings - Fork 73
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
feat: read extended tsconfigs #845
Changes from 15 commits
4411daf
2575765
09f4b91
1350dca
82d3620
9cc9126
b8bd6e8
b025f67
b64b9d7
8904dd2
36b660a
dd7bb5f
a01e14c
9809042
72e2c41
ca475f5
b10562d
2131d2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import {sync} from 'globby' | ||
import globby from 'globby' | ||
import {join, parse, relative, sep} from 'node:path' | ||
import {inspect} from 'node:util' | ||
|
||
|
@@ -13,7 +13,7 @@ import {OCLIF_MARKER_OWNER, Performance} from '../performance' | |
import {cacheCommand} from '../util/cache-command' | ||
import {findRoot} from '../util/find-root' | ||
import {readJson, requireJson} from '../util/fs' | ||
import {castArray, compact, isProd, mapValues} from '../util/util' | ||
import {castArray, compact, isProd} from '../util/util' | ||
import {tsPath} from './ts-node' | ||
import {Debug, getCommandIdPermutations} from './util' | ||
|
||
|
@@ -41,15 +41,34 @@ const search = (cmd: any) => { | |
return Object.values(cmd).find((cmd: any) => typeof cmd.run === 'function') | ||
} | ||
|
||
const GLOB_PATTERNS = [ | ||
'**/*.+(js|cjs|mjs|ts|tsx|mts|cts)', | ||
'!**/*.+(d.ts|test.ts|test.js|spec.ts|spec.js|d.mts|d.cts)?(x)', | ||
] | ||
|
||
function processCommandIds(files: string[]): string[] { | ||
return files.map((file) => { | ||
const p = parse(file) | ||
const topics = p.dir.split('/') | ||
const command = p.name !== 'index' && p.name | ||
const id = [...topics, command].filter(Boolean).join(':') | ||
return id === '' ? '.' : id | ||
}) | ||
} | ||
|
||
export class Plugin implements IPlugin { | ||
alias!: string | ||
|
||
alreadyLoaded = false | ||
|
||
children: Plugin[] = [] | ||
|
||
commandIDs: string[] = [] | ||
|
||
commands!: Command.Loadable[] | ||
|
||
commandsDir!: string | undefined | ||
|
||
hasManifest = false | ||
|
||
hooks!: {[k: string]: string[]} | ||
|
@@ -80,44 +99,13 @@ export class Plugin implements IPlugin { | |
|
||
_base = `${_pjson.name}@${_pjson.version}` | ||
|
||
private _commandsDir!: string | undefined | ||
|
||
// eslint-disable-next-line new-cap | ||
protected _debug = Debug() | ||
|
||
private flexibleTaxonomy!: boolean | ||
|
||
constructor(public options: PluginOptions) {} | ||
|
||
public get commandIDs(): string[] { | ||
if (!this.commandsDir) return [] | ||
|
||
const marker = Performance.mark(OCLIF_MARKER_OWNER, `plugin.commandIDs#${this.name}`, {plugin: this.name}) | ||
this._debug(`loading IDs from ${this.commandsDir}`) | ||
const patterns = [ | ||
'**/*.+(js|cjs|mjs|ts|tsx|mts|cts)', | ||
'!**/*.+(d.ts|test.ts|test.js|spec.ts|spec.js|d.mts|d.cts)?(x)', | ||
] | ||
const ids = sync(patterns, {cwd: this.commandsDir}).map((file) => { | ||
const p = parse(file) | ||
const topics = p.dir.split('/') | ||
const command = p.name !== 'index' && p.name | ||
const id = [...topics, command].filter(Boolean).join(':') | ||
return id === '' ? '.' : id | ||
}) | ||
this._debug('found commands', ids) | ||
marker?.addDetails({count: ids.length}) | ||
marker?.stop() | ||
return ids | ||
} | ||
|
||
public get commandsDir(): string | undefined { | ||
if (this._commandsDir) return this._commandsDir | ||
|
||
this._commandsDir = tsPath(this.root, this.pjson.oclif.commands, this) | ||
return this._commandsDir | ||
} | ||
|
||
public get topics(): Topic[] { | ||
return topicsToArray(this.pjson.oclif.topics || {}) | ||
} | ||
|
@@ -163,7 +151,7 @@ export class Plugin implements IPlugin { | |
} | ||
|
||
public async load(): Promise<void> { | ||
this.type = this.options.type || 'core' | ||
this.type = this.options.type ?? 'core' | ||
this.tag = this.options.tag | ||
this.isRoot = this.options.isRoot ?? false | ||
if (this.options.parent) this.parent = this.options.parent as Plugin | ||
|
@@ -192,7 +180,14 @@ export class Plugin implements IPlugin { | |
this.pjson.oclif = this.pjson['cli-engine'] || {} | ||
} | ||
|
||
this.hooks = mapValues(this.pjson.oclif.hooks ?? {}, (i) => castArray(i).map((i) => tsPath(this.root, i, this))) | ||
this.commandsDir = await this.getCommandsDir() | ||
this.commandIDs = await this.getCommandIDs() | ||
|
||
this.hooks = {} | ||
for (const [k, v] of Object.entries(this.pjson.oclif.hooks ?? {})) { | ||
// eslint-disable-next-line no-await-in-loop | ||
this.hooks[k] = await Promise.all(castArray(v).map(async (i) => tsPath(this.root, i, this))) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a good sense for how long There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this.hooks = Object.fromEntries(await Promise.all(
Object.entries(this.pjson.oclif.hooks ?? {})
.map(async ([k, v]) => [k, await Promise.all(
castArray(v).map(async (i) => tsPath(this.root, i, this))
)])
)) |
||
|
||
this.manifest = await this._manifest() | ||
this.commands = Object.entries(this.manifest.commands) | ||
|
@@ -292,6 +287,23 @@ export class Plugin implements IPlugin { | |
return err | ||
} | ||
|
||
private async getCommandIDs(): Promise<string[]> { | ||
if (!this.commandsDir) return [] | ||
|
||
const marker = Performance.mark(OCLIF_MARKER_OWNER, `plugin.getCommandIDs#${this.name}`, {plugin: this.name}) | ||
this._debug(`loading IDs from ${this.commandsDir}`) | ||
const files = await globby(GLOB_PATTERNS, {cwd: this.commandsDir}) | ||
const ids = processCommandIds(files) | ||
this._debug('found commands', ids) | ||
marker?.addDetails({count: ids.length}) | ||
marker?.stop() | ||
return ids | ||
} | ||
|
||
private async getCommandsDir(): Promise<string | undefined> { | ||
return tsPath(this.root, this.pjson.oclif.commands, this) | ||
} | ||
|
||
private warn(err: CLIError | Error | string, scope?: string): void { | ||
if (this.warned) return | ||
if (typeof err === 'string') err = new Error(err) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,7 @@ export async function load<T = any>(config: IConfig | IPlugin, modulePath: strin | |
let filePath: string | undefined | ||
let isESM: boolean | undefined | ||
try { | ||
;({filePath, isESM} = resolvePath(config, modulePath)) | ||
;({filePath, isESM} = await resolvePath(config, modulePath)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the semicolon for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure to be honest - it's a default prettier thing |
||
return (isESM ? await import(pathToFileURL(filePath).href) : require(filePath)) as T | ||
} catch (error: any) { | ||
if (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND') { | ||
|
@@ -73,7 +73,7 @@ export async function loadWithData<T = any>( | |
let filePath: string | undefined | ||
let isESM: boolean | undefined | ||
try { | ||
;({filePath, isESM} = resolvePath(config, modulePath)) | ||
;({filePath, isESM} = await resolvePath(config, modulePath)) | ||
const module = isESM ? await import(pathToFileURL(filePath).href) : require(filePath) | ||
return {filePath, isESM, module} | ||
} catch (error: any) { | ||
|
@@ -172,7 +172,7 @@ export function isPathModule(filePath: string): boolean { | |
* | ||
* @returns {{isESM: boolean, filePath: string}} An object including file path and whether the module is ESM. | ||
*/ | ||
function resolvePath(config: IConfig | IPlugin, modulePath: string): {filePath: string; isESM: boolean} { | ||
async function resolvePath(config: IConfig | IPlugin, modulePath: string): Promise<{filePath: string; isESM: boolean}> { | ||
let isESM: boolean | ||
let filePath: string | undefined | ||
|
||
|
@@ -181,7 +181,8 @@ function resolvePath(config: IConfig | IPlugin, modulePath: string): {filePath: | |
isESM = isPathModule(filePath) | ||
} catch { | ||
filePath = | ||
(isPlugin(config) ? tsPath(config.root, modulePath, config) : tsPath(config.root, modulePath)) ?? modulePath | ||
(isPlugin(config) ? await tsPath(config.root, modulePath, config) : await tsPath(config.root, modulePath)) ?? | ||
modulePath | ||
|
||
let fileExists = false | ||
let isDirectory = false | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what
!
means if there's alsostring | undefined
. Seems like an contradiction?