-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
nx-update-ts-references: Support CLI usage
- Loading branch information
Showing
10 changed files
with
323 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nx-update-ts-references": minor | ||
--- | ||
|
||
Support CLI usage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/usr/bin/env node | ||
|
||
export { default } from './dist/bin.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,13 @@ | ||
import configGenerator from '@leyman/eslint-config'; | ||
import packageJson from './package.json' with { type: 'json' }; | ||
|
||
export default configGenerator({ configUrl: import.meta.url, packageJson }); | ||
export default [ | ||
...configGenerator({ configUrl: import.meta.url, packageJson }), | ||
{ | ||
languageOptions: { | ||
globals: { | ||
process: 'readonly', | ||
}, | ||
}, | ||
}, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { EntryScript } from 'npm-entry-script'; | ||
import { bind, singletonScope } from 'npm-haywire'; | ||
import { launch } from 'npm-haywire-launcher'; | ||
import { UpdateTsReferencesCli } from './cli.js'; | ||
import { commandsId, commandsModule } from './commands/index.js'; | ||
import { consoleErrorId, consoleLogId, exitCodeId } from './commands/lib/dependencies.js'; | ||
import { dependenciesModule } from './lib/dependencies-module.js'; | ||
|
||
export default launch( | ||
commandsModule | ||
.mergeModule(dependenciesModule) | ||
.addBinding( | ||
bind(EntryScript) | ||
.withDependencies([UpdateTsReferencesCli]) | ||
.withProvider(cli => cli) | ||
.scoped(singletonScope) | ||
) | ||
.addBinding( | ||
bind(UpdateTsReferencesCli) | ||
.withDependencies([commandsId.supplier(), consoleLogId, consoleErrorId, exitCodeId]) | ||
.withConstructorProvider() | ||
.scoped(singletonScope) | ||
) | ||
.toContainer() | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { createRequire } from 'node:module'; | ||
import { defaultImport } from 'npm-default-import'; | ||
import { EntryScript } from 'npm-entry-script'; | ||
import type { Supplier } from 'npm-haywire'; | ||
import yargsDefault, { type Argv } from 'yargs'; | ||
import type { ConsoleLog, ExitCode } from './commands/lib/dependencies.js'; | ||
import type { AbstractCommand } from './commands/lib/types.js'; | ||
|
||
const packageJson = createRequire(import.meta.url)('../package.json') as { version: string }; | ||
const yargs = defaultImport(yargsDefault) as Argv; | ||
|
||
/** | ||
* UpdateTsReferences CLI. Run `./bin.mjs --help` for options. | ||
* | ||
* Uses `yargs` package for command line parsing and logic flow. | ||
*/ | ||
export class UpdateTsReferencesCli extends EntryScript { | ||
readonly #getCommands: Supplier<AbstractCommand[]>; | ||
readonly #logger: ConsoleLog; | ||
readonly #errorLogger: ConsoleLog; | ||
readonly #exitCode: ExitCode; | ||
|
||
public constructor( | ||
getCommands: Supplier<AbstractCommand[]>, | ||
logger: ConsoleLog, | ||
errorLogger: ConsoleLog, | ||
exitCode: ExitCode | ||
) { | ||
super(); | ||
this.#getCommands = getCommands; | ||
this.#logger = logger; | ||
this.#errorLogger = errorLogger; | ||
this.#exitCode = exitCode; | ||
} | ||
|
||
/** | ||
* Entry point to CLI script. | ||
* | ||
* Sets high level Yargs settings. Command/options logic is implemented in individual command modules. | ||
* | ||
* @param argv - process arguments | ||
*/ | ||
public override async main(argv: string[]): Promise<void> { | ||
const yarg = yargs() | ||
.scriptName('update-ts-references') | ||
.option({ | ||
packageRoot: { | ||
type: 'string', | ||
default: '.', | ||
describe: 'Directory containing tsconfig.json and project.json', | ||
alias: 'projectRoot', | ||
}, | ||
}) | ||
.strict() | ||
.help() | ||
.alias('help', 'info') | ||
.version(packageJson.version); | ||
|
||
for (const command of this.#getCommands()) { | ||
yarg.command(command); | ||
} | ||
|
||
await yarg.parseAsync(argv, {}, this.#yargsOutput.bind(this)); | ||
} | ||
|
||
#yargsOutput(e: unknown, _argv: unknown, log: string): void { | ||
if (e) { | ||
this.#exitCode(1); | ||
if (log) { | ||
this.#errorLogger(log); | ||
} | ||
} else if (log) { | ||
this.#logger(log); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { bind, identifier } from 'npm-haywire'; | ||
import { readFileId } from '../lib/dependencies-module.js'; | ||
import { updateTsReferencesId } from '../lib/update-ts-references.js'; | ||
import { dependenciesModule, parseCwdId, projectGraphId } from './lib/dependencies.js'; | ||
import type { AbstractCommand } from './lib/types.js'; | ||
import { UpdateTsReferencesCommand } from './update-ts-references-command.js'; | ||
|
||
export const commandsId = identifier<AbstractCommand[]>(); | ||
|
||
export const commandsModule = dependenciesModule | ||
.addBinding( | ||
bind(commandsId) | ||
.withDependencies([UpdateTsReferencesCommand]) | ||
.withProvider((...commands) => commands) | ||
) | ||
.addBinding( | ||
bind(UpdateTsReferencesCommand) | ||
.withDependencies([ | ||
updateTsReferencesId, | ||
parseCwdId, | ||
projectGraphId.supplier('async'), | ||
readFileId, | ||
]) | ||
.withProvider((...args) => new UpdateTsReferencesCommand(...args)) | ||
); |
39 changes: 39 additions & 0 deletions
39
apps/nx-update-ts-references/src/commands/lib/dependencies.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { createProjectGraphAsync, type ProjectGraph } from '@nx/devkit'; | ||
import { bind, createModule, identifier, singletonScope } from 'npm-haywire'; | ||
import { type Directory, parseCwd } from 'npm-parse-cwd'; | ||
|
||
export type ConsoleLog = (log: unknown) => void; | ||
export const consoleLogId = identifier<ConsoleLog>().named('log'); | ||
export const consoleErrorId = identifier<ConsoleLog>().named('error'); | ||
|
||
export type ExitCode = (code: number) => void; | ||
export const exitCodeId = identifier<ExitCode>(); | ||
|
||
export type ParseCwd = (dir?: Directory) => Promise<string>; | ||
export const parseCwdId = identifier<ParseCwd>(); | ||
|
||
export const projectGraphId = identifier<ProjectGraph>(); | ||
|
||
export const dependenciesModule = createModule( | ||
bind(consoleLogId).withInstance( | ||
// eslint-disable-next-line no-console | ||
console.log | ||
) | ||
) | ||
.addBinding( | ||
bind(consoleErrorId).withInstance( | ||
// eslint-disable-next-line no-console | ||
console.error | ||
) | ||
) | ||
.addBinding( | ||
bind(exitCodeId).withInstance(code => { | ||
process.exitCode = code; | ||
}) | ||
) | ||
.addBinding(bind(parseCwdId).withInstance(parseCwd)) | ||
.addBinding( | ||
bind(projectGraphId) | ||
.withAsyncGenerator(async () => createProjectGraphAsync()) | ||
.scoped(singletonScope) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { CommandModule } from 'yargs'; | ||
|
||
export interface UpdateTsReferencesCommandInput { | ||
packageRoot: string; | ||
} | ||
|
||
export type Command<ExtendedInput extends UpdateTsReferencesCommandInput> = Pick< | ||
CommandModule<UpdateTsReferencesCommandInput, ExtendedInput>, | ||
'builder' | 'command' | 'describe' | 'handler' | ||
>; | ||
|
||
export interface AbstractCommand | ||
extends Pick< | ||
CommandModule<UpdateTsReferencesCommandInput, UpdateTsReferencesCommandInput>, | ||
'builder' | 'command' | 'describe' | ||
> { | ||
handler: (args: any) => Promise<void>; | ||
} |
102 changes: 102 additions & 0 deletions
102
apps/nx-update-ts-references/src/commands/update-ts-references-command.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import type { readFile as ReadFile } from 'node:fs/promises'; | ||
import Path from 'node:path'; | ||
import type { ProjectGraph } from '@nx/devkit'; | ||
import { isCI } from 'ci-info'; | ||
import type { AsyncSupplier } from 'npm-haywire'; | ||
import type { Argv } from 'yargs'; | ||
import { isProjectJson } from '../lib/project-json-validator.js'; | ||
import type { IUpdateTsReferences } from '../lib/update-ts-references.js'; | ||
import type { ParseCwd } from './lib/dependencies.js'; | ||
import type { Command, UpdateTsReferencesCommandInput } from './lib/types.js'; | ||
|
||
interface UpdateTsReferencesCommandExtendedInput extends UpdateTsReferencesCommandInput { | ||
ci: boolean; | ||
dryRun: boolean; | ||
} | ||
|
||
/** | ||
* Main `update-ts-references` command | ||
*/ | ||
export class UpdateTsReferencesCommand implements Command<UpdateTsReferencesCommandExtendedInput> { | ||
public readonly command = ['$0', 'update-ts-references']; | ||
public readonly describe = | ||
"Write tsconfig.json's references field based on Nx detected dependencies"; | ||
|
||
readonly #updateTsReferences: IUpdateTsReferences; | ||
readonly #parseCwd: ParseCwd; | ||
readonly #projectGraph: AsyncSupplier<ProjectGraph>; | ||
readonly #readFile: typeof ReadFile; | ||
|
||
public constructor( | ||
updateTsReferences: IUpdateTsReferences, | ||
parseCwd: ParseCwd, | ||
projectGraph: AsyncSupplier<ProjectGraph>, | ||
readFile: typeof ReadFile | ||
) { | ||
this.#updateTsReferences = updateTsReferences; | ||
this.#parseCwd = parseCwd; | ||
this.#projectGraph = projectGraph; | ||
this.#readFile = readFile; | ||
|
||
this.handler = this.handler.bind(this); | ||
} | ||
|
||
public builder( | ||
yargs: Argv<UpdateTsReferencesCommandInput> | ||
): Argv<UpdateTsReferencesCommandExtendedInput> { | ||
return yargs | ||
.options({ | ||
ci: { | ||
describe: 'Fail if file is not up to date. Implies --dry-run', | ||
type: 'boolean', | ||
default: isCI, | ||
alias: 'check', | ||
}, | ||
dryRun: { | ||
describe: 'Do not write file', | ||
type: 'boolean', | ||
default: false, | ||
}, | ||
}) | ||
.strict(); | ||
} | ||
|
||
public async handler(options: UpdateTsReferencesCommandExtendedInput): Promise<void> { | ||
const packageRoot = await this.#parseCwd(options.packageRoot); | ||
|
||
const dependencyRootPaths = await this.#getDependencyRootPaths(packageRoot); | ||
|
||
const changed = await this.#updateTsReferences({ | ||
packageRoot, | ||
dependencyRootPaths, | ||
dryRun: options.ci || options.dryRun, | ||
tsConfigPath: Path.resolve(packageRoot, 'tsconfig.json'), | ||
}); | ||
|
||
if (options.ci && changed) { | ||
throw new Error('tsconfig.json is not built'); | ||
} | ||
} | ||
|
||
async #getDependencyRootPaths(packageRoot: string): Promise<string[]> { | ||
const [projectGraph, rawProjectJson] = await Promise.all([ | ||
this.#projectGraph(), | ||
this.#readFile(Path.join(packageRoot, 'project.json'), 'utf8'), | ||
]); | ||
|
||
const projectJson: unknown = JSON.parse(rawProjectJson); | ||
|
||
if (!isProjectJson(projectJson)) { | ||
throw new Error('Cannot parse project.json name'); | ||
} | ||
|
||
const { root } = projectGraph.nodes[projectJson.name]!.data; | ||
|
||
return projectGraph.dependencies[projectJson.name]!.map( | ||
({ target }) => projectGraph.nodes[target] | ||
) | ||
.filter(node => !!node) | ||
.map(node => Path.relative(root, node.data.root)) | ||
.map(path => Path.resolve(packageRoot, path, 'tsconfig.json')); | ||
} | ||
} |