-
Notifications
You must be signed in to change notification settings - Fork 208
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor cli: Organize in different actions (#2174)
This split up unrelated content in the cli into different files. This PR keeps it to a minimum of moving content around. I have a follow up where all actions should try to reuse the logger/diagnostics instead of managing errors themself.
- Loading branch information
1 parent
140d6ce
commit 6fae63c
Showing
13 changed files
with
553 additions
and
467 deletions.
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
common/changes/@typespec/compiler/cleanup-cli_2023-07-11-22-07.json
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,10 @@ | ||
{ | ||
"changes": [ | ||
{ | ||
"packageName": "@typespec/compiler", | ||
"comment": "Internal: Refactoring of cli code", | ||
"type": "none" | ||
} | ||
], | ||
"packageName": "@typespec/compiler" | ||
} |
16 changes: 8 additions & 8 deletions
16
packages/compiler/src/core/cli/args.ts → ...iler/src/core/cli/actions/compile/args.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
148 changes: 148 additions & 0 deletions
148
packages/compiler/src/core/cli/actions/compile/compile.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,148 @@ | ||
import watch from "node-watch"; | ||
import { resolve } from "path"; | ||
import { logDiagnostics } from "../../../diagnostics.js"; | ||
import { resolveTypeSpecEntrypoint } from "../../../entrypoint-resolution.js"; | ||
import { CompilerOptions } from "../../../options.js"; | ||
import { getAnyExtensionFromPath, resolvePath } from "../../../path-utils.js"; | ||
import { Program, compile as compileProgram } from "../../../program.js"; | ||
import { CompilerHost, Diagnostic } from "../../../types.js"; | ||
import { | ||
createCLICompilerHost, | ||
handleInternalCompilerError, | ||
logDiagnosticCount, | ||
} from "../../utils.js"; | ||
import { CompileCliArgs, getCompilerOptions } from "./args.js"; | ||
|
||
export async function compileAction(args: CompileCliArgs & { path: string; pretty?: boolean }) { | ||
const host = createCLICompilerHost(args); | ||
const diagnostics: Diagnostic[] = []; | ||
const entrypoint = await resolveTypeSpecEntrypoint( | ||
host, | ||
resolvePath(process.cwd(), args.path), | ||
(diag) => diagnostics.push(diag) | ||
); | ||
if (entrypoint === undefined || diagnostics.length > 0) { | ||
logDiagnostics(diagnostics, host.logSink); | ||
process.exit(1); | ||
} | ||
const cliOptions = await getCompilerOptionsOrExit(host, entrypoint, args); | ||
|
||
const program = await compileInput(host, entrypoint, cliOptions); | ||
if (program.hasError()) { | ||
process.exit(1); | ||
} | ||
if (program.emitters.length === 0 && !program.compilerOptions.noEmit) { | ||
// eslint-disable-next-line no-console | ||
console.log( | ||
"No emitter was configured, no output was generated. Use `--emit <emitterName>` to pick emitter or specify it in the typespec config." | ||
); | ||
} | ||
} | ||
|
||
async function getCompilerOptionsOrExit( | ||
host: CompilerHost, | ||
entrypoint: string, | ||
args: CompileCliArgs | ||
): Promise<CompilerOptions> { | ||
const [options, diagnostics] = await getCompilerOptions( | ||
host, | ||
entrypoint, | ||
process.cwd(), | ||
args, | ||
process.env | ||
); | ||
if (diagnostics.length > 0) { | ||
logDiagnostics(diagnostics, host.logSink); | ||
} | ||
if (options === undefined) { | ||
logDiagnosticCount(diagnostics); | ||
process.exit(1); | ||
} | ||
|
||
return options; | ||
} | ||
|
||
function compileInput( | ||
host: CompilerHost, | ||
path: string, | ||
compilerOptions: CompilerOptions, | ||
printSuccess = true | ||
): Promise<Program> { | ||
let compileRequested: boolean = false; | ||
let currentCompilePromise: Promise<Program> | undefined = undefined; | ||
const log = (message?: any, ...optionalParams: any[]) => { | ||
const prefix = compilerOptions.watchForChanges ? `[${new Date().toLocaleTimeString()}] ` : ""; | ||
// eslint-disable-next-line no-console | ||
console.log(`${prefix}${message}`, ...optionalParams); | ||
}; | ||
|
||
const runCompilePromise = () => { | ||
// Don't run the compiler if it's already running | ||
if (!currentCompilePromise) { | ||
// Clear the console before compiling in watch mode | ||
if (compilerOptions.watchForChanges) { | ||
// eslint-disable-next-line no-console | ||
console.clear(); | ||
} | ||
|
||
currentCompilePromise = compileProgram(host, resolve(path), compilerOptions) | ||
.then(onCompileFinished) | ||
.catch(handleInternalCompilerError); | ||
} else { | ||
compileRequested = true; | ||
} | ||
|
||
return currentCompilePromise; | ||
}; | ||
|
||
const runCompile = () => void runCompilePromise(); | ||
|
||
const onCompileFinished = (program: Program) => { | ||
if (program.diagnostics.length > 0) { | ||
log("Diagnostics were reported during compilation:\n"); | ||
logDiagnostics(program.diagnostics, host.logSink); | ||
logDiagnosticCount(program.diagnostics); | ||
} else { | ||
if (printSuccess) { | ||
log("Compilation completed successfully."); | ||
} | ||
} | ||
|
||
// eslint-disable-next-line no-console | ||
console.log(); // Insert a newline | ||
currentCompilePromise = undefined; | ||
if (compilerOptions.watchForChanges && compileRequested) { | ||
compileRequested = false; | ||
runCompile(); | ||
} | ||
|
||
return program; | ||
}; | ||
|
||
if (compilerOptions.watchForChanges) { | ||
runCompile(); | ||
return new Promise((resolve, reject) => { | ||
const watcher = (watch as any)( | ||
path, | ||
{ | ||
recursive: true, | ||
filter: (f: string) => | ||
[".js", ".tsp", ".cadl"].indexOf(getAnyExtensionFromPath(f)) > -1 && | ||
!/node_modules/.test(f), | ||
}, | ||
(e: any, name: string) => { | ||
runCompile(); | ||
} | ||
); | ||
|
||
// Handle Ctrl+C for termination | ||
process.on("SIGINT", () => { | ||
watcher.close(); | ||
// eslint-disable-next-line no-console | ||
console.info("Terminating watcher...\n"); | ||
}); | ||
}); | ||
} else { | ||
return runCompilePromise(); | ||
} | ||
} |
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,30 @@ | ||
import { findUnformattedTypeSpecFiles, formatTypeSpecFiles } from "../../formatter-fs.js"; | ||
|
||
export interface FormatArgs { | ||
include: string[]; | ||
exclude?: string[]; | ||
debug?: boolean; | ||
check?: boolean; | ||
} | ||
export async function formatAction(args: FormatArgs) { | ||
if (args["check"]) { | ||
const unformatted = await findUnformattedTypeSpecFiles(args["include"], { | ||
exclude: args["exclude"], | ||
debug: args.debug, | ||
}); | ||
if (unformatted.length > 0) { | ||
// eslint-disable-next-line no-console | ||
console.log(`Found ${unformatted.length} unformatted files:`); | ||
for (const file of unformatted) { | ||
// eslint-disable-next-line no-console | ||
console.log(` - ${file}`); | ||
} | ||
process.exit(1); | ||
} | ||
} else { | ||
await formatTypeSpecFiles(args["include"], { | ||
exclude: args["exclude"], | ||
debug: args.debug, | ||
}); | ||
} | ||
} |
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,30 @@ | ||
/* eslint-disable no-console */ | ||
import { fileURLToPath } from "url"; | ||
import { loadTypeSpecConfigForPath } from "../../../config/config-loader.js"; | ||
import { logDiagnostics } from "../../diagnostics.js"; | ||
import { CompilerHost } from "../../types.js"; | ||
import { logDiagnosticCount } from "../utils.js"; | ||
|
||
/** | ||
* Print the resolved TypeSpec configuration. | ||
*/ | ||
export async function printInfoAction(host: CompilerHost) { | ||
const cwd = process.cwd(); | ||
console.log(`Module: ${fileURLToPath(import.meta.url)}`); | ||
|
||
const config = await loadTypeSpecConfigForPath(host, cwd); | ||
const jsyaml = await import("js-yaml"); | ||
const excluded = ["diagnostics", "filename"]; | ||
const replacer = (emitter: string, value: any) => | ||
excluded.includes(emitter) ? undefined : value; | ||
|
||
console.log(`User Config: ${config.filename ?? "No config file found"}`); | ||
console.log("-----------"); | ||
console.log(jsyaml.dump(config, { replacer })); | ||
console.log("-----------"); | ||
logDiagnostics(config.diagnostics, host.logSink); | ||
logDiagnosticCount(config.diagnostics); | ||
if (config.diagnostics.some((d) => d.severity === "error")) { | ||
process.exit(1); | ||
} | ||
} |
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,21 @@ | ||
import { InitTemplateError, initTypeSpecProject } from "../../../init/init.js"; | ||
import { logDiagnostics } from "../../diagnostics.js"; | ||
import { createCLICompilerHost } from "../utils.js"; | ||
|
||
export interface InitArgs { | ||
templatesUrl?: string; | ||
pretty?: boolean; | ||
} | ||
|
||
export async function initAction(args: InitArgs) { | ||
const host = createCLICompilerHost(args); | ||
try { | ||
await initTypeSpecProject(host, process.cwd(), args.templatesUrl); | ||
} catch (e) { | ||
if (e instanceof InitTemplateError) { | ||
logDiagnostics(e.diagnostics, host.logSink); | ||
process.exit(1); | ||
} | ||
throw e; | ||
} | ||
} |
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,72 @@ | ||
import { joinPaths } from "../../path-utils.js"; | ||
import { installVsix } from "../install-vsix.js"; | ||
import { run } from "../utils.js"; | ||
|
||
const VSIX_ALREADY_INSTALLED = 1001; | ||
const VSIX_NOT_INSTALLED = 1002; | ||
const VSIX_USER_CANCELED = 2005; | ||
const VS_SUPPORTED_VERSION_RANGE = "[17.0,)"; | ||
|
||
export async function installVSExtension(debug: boolean) { | ||
const vsixInstaller = getVsixInstallerPath(); | ||
|
||
if (!isVSInstalled(VS_SUPPORTED_VERSION_RANGE)) { | ||
// eslint-disable-next-line no-console | ||
console.error("error: No compatible version of Visual Studio found."); | ||
process.exit(1); | ||
} | ||
|
||
await installVsix( | ||
"typespec-vs", | ||
(vsixPaths) => { | ||
for (const vsix of vsixPaths) { | ||
// eslint-disable-next-line no-console | ||
console.log(`Installing extension for Visual Studio...`); | ||
run(vsixInstaller, [vsix], { | ||
allowedExitCodes: [VSIX_ALREADY_INSTALLED, VSIX_USER_CANCELED], | ||
}); | ||
} | ||
}, | ||
debug | ||
); | ||
} | ||
|
||
export async function uninstallVSExtension() { | ||
const vsixInstaller = getVsixInstallerPath(); | ||
run(vsixInstaller, ["/uninstall:88b9492f-c019-492c-8aeb-f325a7e4cf23"], { | ||
allowedExitCodes: [VSIX_NOT_INSTALLED, VSIX_USER_CANCELED], | ||
}); | ||
} | ||
|
||
function getVsixInstallerPath(): string { | ||
return getVSInstallerPath( | ||
"resources/app/ServiceHub/Services/Microsoft.VisualStudio.Setup.Service/VSIXInstaller.exe" | ||
); | ||
} | ||
|
||
function getVSWherePath(): string { | ||
return getVSInstallerPath("vswhere.exe"); | ||
} | ||
|
||
function getVSInstallerPath(relativePath: string) { | ||
if (process.platform !== "win32") { | ||
// eslint-disable-next-line no-console | ||
console.error("error: Visual Studio extension is not supported on non-Windows."); | ||
process.exit(1); | ||
} | ||
|
||
return joinPaths( | ||
process.env["ProgramFiles(x86)"] ?? "", | ||
"Microsoft Visual Studio/Installer", | ||
relativePath | ||
); | ||
} | ||
|
||
function isVSInstalled(versionRange: string) { | ||
const vswhere = getVSWherePath(); | ||
const proc = run(vswhere, ["-property", "instanceid", "-prerelease", "-version", versionRange], { | ||
stdio: [null, "pipe", "inherit"], | ||
allowNotFound: true, | ||
}); | ||
return proc.status === 0 && proc.stdout; | ||
} |
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,43 @@ | ||
import { installVsix } from "../install-vsix.js"; | ||
import { run } from "../utils.js"; | ||
|
||
export async function installVSCodeExtension(insiders: boolean, debug: boolean) { | ||
await installVsix( | ||
"typespec-vscode", | ||
(vsixPaths) => { | ||
runCode(["--install-extension", vsixPaths[0]], insiders, debug); | ||
}, | ||
debug | ||
); | ||
} | ||
|
||
export async function uninstallVSCodeExtension(insiders: boolean, debug: boolean) { | ||
await runCode(["--uninstall-extension", "microsoft.typespec-vscode"], insiders, debug); | ||
} | ||
|
||
function runCode(codeArgs: string[], insiders: boolean, debug: boolean) { | ||
try { | ||
run(insiders ? "code-insiders" : "code", codeArgs, { | ||
// VS Code's CLI emits node warnings that we can't do anything about. Suppress them. | ||
extraEnv: { NODE_NO_WARNINGS: "1" }, | ||
debug, | ||
allowNotFound: true, | ||
}); | ||
} catch (error: any) { | ||
if (error.code === "ENOENT") { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
`error: Couldn't find VS Code 'code' command in PATH. Make sure you have the VS Code executable added to the system PATH.` | ||
); | ||
if (process.platform === "darwin") { | ||
// eslint-disable-next-line no-console | ||
console.log("See instruction for Mac OS here https://code.visualstudio.com/docs/setup/mac"); | ||
} | ||
if (debug) { | ||
// eslint-disable-next-line no-console | ||
console.log(error.stack); | ||
} | ||
process.exit(1); | ||
} | ||
} | ||
} |
Oops, something went wrong.