From b01223008ba83f8d3e323996d0bdb4faa5d0243c Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 14:14:02 +0100 Subject: [PATCH 01/16] Increase reliability of exit code --- packages/cli-utils/src/term/write.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cli-utils/src/term/write.ts b/packages/cli-utils/src/term/write.ts index da388de1..4b122c76 100644 --- a/packages/cli-utils/src/term/write.ts +++ b/packages/cli-utils/src/term/write.ts @@ -23,14 +23,15 @@ export const stripAnsi = (input: string) => input.replace(ansiRegex, ''); export class CLIError extends Error { output: string; - exit: number; + exit: number | undefined; constructor(message: string, exitCode?: number) { super(stripAnsi(message)); this.output = message; - this.exit = exitCode || 1; + this.exit = exitCode; } toString() { + if (this.exit != null) process.exitCode = this.exit; return this.output; } } From 8361410dcfc5f2e3d3a9dcd000b2742aa4fd6bf4 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 14:31:07 +0100 Subject: [PATCH 02/16] Add support for Github annotation output --- packages/cli-utils/src/term/github.ts | 46 +++++++++++++++++++++++++++ packages/cli-utils/src/term/index.ts | 1 + packages/cli-utils/src/term/tty.ts | 8 +++-- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 packages/cli-utils/src/term/github.ts diff --git a/packages/cli-utils/src/term/github.ts b/packages/cli-utils/src/term/github.ts new file mode 100644 index 00000000..799c53d6 --- /dev/null +++ b/packages/cli-utils/src/term/github.ts @@ -0,0 +1,46 @@ +export const isGithubCI = !!process.env.GITHUB_ACTIONS; + +export interface AnnotationProperties { + title?: string; + file?: string; + line?: number; + endLine?: number; + col?: number; + endColumn?: number; +} + +const toCommandValue = (input: unknown): string => + typeof input == 'string' || input == null + ? '' + (input ? '' + input : '') + : JSON.stringify(input); + +const escapeData = (input: unknown) => + toCommandValue(s).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); + +const escapeProperty = (input: unknown): string => + toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); + +export function githubAnnotation( + kind: 'error' | 'warning' | 'notice', + message: string, + properties?: AnnotationProperties +) { + if (isGitHubCI) { + let out = `::${kind}`; + const propsOut = ''; + if (properties) { + for (const key in properties) { + if (properties) properties += ','; + if (properties[key]) cmdStr += `${key}=${escapeProperty(propreties[key])}`; + } + } + if (propsOut) out += ` ${propsOut}`; + out += `::${escapeData(message)}\n`; + process.stdout.write(out); + } +} diff --git a/packages/cli-utils/src/term/index.ts b/packages/cli-utils/src/term/index.ts index b0f6852d..eb2fc929 100644 --- a/packages/cli-utils/src/term/index.ts +++ b/packages/cli-utils/src/term/index.ts @@ -3,3 +3,4 @@ export { CLIError, text, error, compose } from './write'; export * from './csi'; export * from './symbols'; export * from './tty'; +export * from './github'; diff --git a/packages/cli-utils/src/term/tty.ts b/packages/cli-utils/src/term/tty.ts index 2a7985c4..f63c2218 100644 --- a/packages/cli-utils/src/term/tty.ts +++ b/packages/cli-utils/src/term/tty.ts @@ -19,6 +19,7 @@ import { emitKeypressEvents } from 'node:readline'; import type { ComposeInput, CLIError } from './write'; import { text, compose } from './write'; import { cmd, _setColor, CSI, Mode, PrivateMode } from './csi'; +import { isGithubCI } from './github'; export interface KeypressEvent { data?: string; @@ -79,7 +80,10 @@ export function initTTY(): TTY { let isTTY = process.env.TERM !== 'dumb' && !process.env.CI; let pipeTo: WriteStream | null = null; let output: WriteStream = process.stdout; - if (!output.isTTY && process.stderr.isTTY) { + if (isGithubCI) { + output = process.stderr; + if (!output.isTTY) pipeTo = process.stdout; + } else if (!output.isTTY && process.stderr.isTTY) { output = process.stderr; pipeTo = process.stdout; } else { @@ -88,7 +92,7 @@ export function initTTY(): TTY { const hasColorArg = process.argv.includes('--color'); const hasColorEnv = 'FORCE_COLOR' in process.env || (!process.env.NO_COLOR && !process.env.CI); - _setColor((isTTY && hasColorEnv) || hasColorArg); + _setColor((isTTY && hasColorEnv) || hasColorArg || isGithubCI); function _start() { _setColor((isTTY && hasColorEnv) || hasColorArg); From ff12fc461ad08a66f8de1b9b8c4e6da90a40eadb Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 14:53:49 +0100 Subject: [PATCH 03/16] Add missing tsconfig option to check command --- .../cli-utils/src/commands/check/index.ts | 4 +++ .../cli-utils/src/commands/check/runner.ts | 31 +++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/cli-utils/src/commands/check/index.ts b/packages/cli-utils/src/commands/check/index.ts index 38f0b3cc..423fe8c9 100644 --- a/packages/cli-utils/src/commands/check/index.ts +++ b/packages/cli-utils/src/commands/check/index.ts @@ -5,6 +5,10 @@ import { run } from './runner'; export class CheckCommand extends Command { static paths = [['check']]; + tsconfig = Option.String('--tsconfig,-c', { + description: 'Specify the `tsconfig.json` used to read', + }); + failOnWarn = Option.Boolean('--fail-on-warn,-w', false, { description: 'Triggers an error and a non-zero exit code if any warnings have been reported', }); diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index 8dd80ab9..615efe78 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -22,20 +22,21 @@ export interface FormattedDisplayableDiagnostic { export interface Options { failOnWarn: boolean | undefined; minSeverity: Severity; + tsconfig: string | undefined; } export async function run(opts: Options) { - const tsConfig = await getTsConfig(); - if (!tsConfig) { + const tsconfig = await getTsConfig(opts.tsconfig); + if (!tsconfig) { return; } - const config = getGraphQLSPConfig(tsConfig); + const config = getGraphQLSPConfig(tsconfig); if (!config) { return; } - const result = (await runDiagnostics(config)) || []; + const result = await runDiagnostics(opts, config); const errorDiagnostics = result.filter((d) => d.severity === 'error'); const warnDiagnostics = result.filter((d) => d.severity === 'warn'); const infoDiagnostics = result.filter((d) => d.severity === 'info'); @@ -79,19 +80,29 @@ export async function run(opts: Options) { } } -async function runDiagnostics(config: GraphQLSPConfig): Promise { - // TODO: leverage ts-morph tsconfig resolver - const projectName = path.resolve(process.cwd(), 'tsconfig.json'); - const rootPath = (await resolveTypeScriptRootDir(projectName)) || path.dirname(projectName); +const CWD = process.cwd(); + +async function runDiagnostics( + opts: Options, + config: GraphQLSPConfig +): Promise { + let tsconfigPath = opts.tsconfig || CWD; + tsconfigPath = + path.extname(tsconfigPath) !== '.json' + ? path.resolve(CWD, tsconfigPath, 'tsconfig.json') + : path.resolve(CWD, tsconfigPath); + + const projectPath = path.dirname(tsconfigPath); + const rootPath = (await resolveTypeScriptRootDir(tsconfigPath)) || tsconfigPath; const project = new Project({ - tsConfigFilePath: projectName, + tsConfigFilePath: tsconfigPath, }); init({ typescript: ts as any, }); - const pluginCreateInfo = createPluginInfo(project, config, projectName); + const pluginCreateInfo = createPluginInfo(project, config, projectPath); const sourceFiles = project.getSourceFiles(); const loader = load({ origin: config.schema, rootPath }); From f0480d907389bf748a1eb0e9cc4f11084604c17e Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 15:48:59 +0100 Subject: [PATCH 04/16] Implement initial threaded diagnostic checks --- .../cli-utils/src/commands/check/index.ts | 14 +- .../cli-utils/src/commands/check/logger.ts | 45 ++++++ .../cli-utils/src/commands/check/runner.ts | 144 ++---------------- .../cli-utils/src/commands/check/thread.ts | 92 +++++++++++ .../cli-utils/src/commands/check/types.ts | 17 +++ 5 files changed, 173 insertions(+), 139 deletions(-) create mode 100644 packages/cli-utils/src/commands/check/logger.ts create mode 100644 packages/cli-utils/src/commands/check/thread.ts create mode 100644 packages/cli-utils/src/commands/check/types.ts diff --git a/packages/cli-utils/src/commands/check/index.ts b/packages/cli-utils/src/commands/check/index.ts index 423fe8c9..86062008 100644 --- a/packages/cli-utils/src/commands/check/index.ts +++ b/packages/cli-utils/src/commands/check/index.ts @@ -1,5 +1,7 @@ import * as t from 'typanion'; import { Command, Option } from 'clipanion'; + +import { initTTY } from '../../term'; import { run } from './runner'; export class CheckCommand extends Command { @@ -20,9 +22,13 @@ export class CheckCommand extends Command { }) || 'error'; async execute() { - await run({ - failOnWarn: this.failOnWarn, - minSeverity: this.minSeverity, - }); + const result = await initTTY().start( + run({ + failOnWarn: this.failOnWarn, + minSeverity: this.minSeverity, + tsconfig: this.tsconfig, + }) + ); + return typeof result === 'object' ? result.exit : 0; } } diff --git a/packages/cli-utils/src/commands/check/logger.ts b/packages/cli-utils/src/commands/check/logger.ts new file mode 100644 index 00000000..8c635a20 --- /dev/null +++ b/packages/cli-utils/src/commands/check/logger.ts @@ -0,0 +1,45 @@ +import * as t from '../../term'; +import type { DiagnosticMessage } from './types'; + +export function diagnosticFile(filePath: string, messages: DiagnosticMessage[]) { + let output = t.text([ + t.cmd(t.CSI.Style, t.Style.Underline), + filePath, + t.cmd(t.CSI.Style, t.Style.NoUnderline), + '\n', + ]); + for (const message of messages) output += diagnosticMessage(message); + output += '\n'; + return output; +} + +export function diagnosticMessage(message: DiagnosticMessage) { + const indent = t.Chars.Space.repeat(2); + + let color = t.Style.Foreground; + if (message.severity === 'info') { + color = t.Style.BrightBlue; + } else if (message.severity === 'warn') { + color = t.Style.BrightYellow; + } else if (message.severity === 'error') { + color = t.Style.BrightRed; + } + + let text = message.message.trim(); + if (text.includes('\n')) { + text = text.split('\n').join(t.text([t.Chars.Newline, indent, t.Chars.Tab, t.Chars.Tab])); + } + + return t.text([ + indent, + t.cmd(t.CSI.Style, t.Style.BrightBlack), + `${message.line}:${message.col}`, + t.Chars.Tab, + t.cmd(t.CSI.Style, color), + message.severity, + t.Chars.Tab, + t.cmd(t.CSI.Style, t.Style.Foreground), + text, + t.Chars.Newline, + ]); +} diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index 615efe78..1845c3c2 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -1,12 +1,8 @@ -import { Project, ts } from 'ts-morph'; -import { init, getGraphQLDiagnostics } from '@0no-co/graphqlsp/api'; -import { load, resolveTypeScriptRootDir } from '@gql.tada/internal'; import path from 'node:path'; -import type { GraphQLSPConfig } from '../../lsp'; import { getGraphQLSPConfig } from '../../lsp'; import { getTsConfig } from '../../tsconfig'; -import { createPluginInfo } from '../../ts/project'; +import * as logger from './logger'; type Severity = 'error' | 'warn' | 'info'; const severities: Severity[] = ['error', 'warn', 'info']; @@ -25,7 +21,10 @@ export interface Options { tsconfig: string | undefined; } -export async function run(opts: Options) { +export async function* run(opts: Options) { + const CWD = process.cwd(); + const { runDiagnostics } = await import('./thread'); + const tsconfig = await getTsConfig(opts.tsconfig); if (!tsconfig) { return; @@ -36,142 +35,17 @@ export async function run(opts: Options) { return; } - const result = await runDiagnostics(opts, config); - const errorDiagnostics = result.filter((d) => d.severity === 'error'); - const warnDiagnostics = result.filter((d) => d.severity === 'warn'); - const infoDiagnostics = result.filter((d) => d.severity === 'info'); - - const minSeverityForReport = severities.indexOf(opts.minSeverity); - if ( - errorDiagnostics.length === 0 && - warnDiagnostics.length === 0 && - infoDiagnostics.length === 0 - ) { - // eslint-disable-next-line no-console - console.log('No issues found! 🎉'); - process.exit(0); - } else { - // TODO: report a summary at the top and then a list of files with diagnostics sorted by severity. - const errorReport = errorDiagnostics.length - ? `Found ${errorDiagnostics.length} Errors:\n${constructDiagnosticsPerFile(errorDiagnostics)}` - : ``; - const warningsReport = - minSeverityForReport >= severities.indexOf('warn') && warnDiagnostics.length - ? `Found ${warnDiagnostics.length} Warnings:\n${constructDiagnosticsPerFile( - warnDiagnostics - )}` - : ``; - const suggestionsReport = - minSeverityForReport >= severities.indexOf('info') && - infoDiagnostics.length && - warnDiagnostics.length && - errorDiagnostics.length - ? `Found ${infoDiagnostics.length} Suggestions:\n${constructDiagnosticsPerFile( - infoDiagnostics - )}` - : ``; - // eslint-disable-next-line no-console - console.log(`${errorReport}${warningsReport}${suggestionsReport}`); - if (errorDiagnostics.length || (opts.failOnWarn && warnDiagnostics.length)) { - process.exit(1); - } else { - process.exit(0); - } - } -} - -const CWD = process.cwd(); - -async function runDiagnostics( - opts: Options, - config: GraphQLSPConfig -): Promise { let tsconfigPath = opts.tsconfig || CWD; tsconfigPath = path.extname(tsconfigPath) !== '.json' ? path.resolve(CWD, tsconfigPath, 'tsconfig.json') : path.resolve(CWD, tsconfigPath); - const projectPath = path.dirname(tsconfigPath); - const rootPath = (await resolveTypeScriptRootDir(tsconfigPath)) || tsconfigPath; - const project = new Project({ - tsConfigFilePath: tsconfigPath, - }); - - init({ - typescript: ts as any, - }); - - const pluginCreateInfo = createPluginInfo(project, config, projectPath); - - const sourceFiles = project.getSourceFiles(); - const loader = load({ origin: config.schema, rootPath }); - let schema; - try { - const loaderResult = await loader.load(); - schema = loaderResult && loaderResult.schema; - if (!schema) { - throw new Error(`Failed to load schema`); - } - } catch (error) { - throw new Error(`Failed to load schema: ${error}`); - } - - return sourceFiles.flatMap((sourceFile) => { - const diag = - getGraphQLDiagnostics( - sourceFile.getFilePath(), - { current: schema, version: 1 }, - pluginCreateInfo - ) || []; - return diag.map((diag) => { - const text = diag.file && diag.file.getText(); - const start = diag.start; - const [line, col] = getLineCol(text || '', start || 0); - return { - severity: (diag.category === ts.DiagnosticCategory.Error - ? 'error' - : diag.category === ts.DiagnosticCategory.Warning - ? 'warn' - : 'info') as Severity, - message: diag.messageText as string, - file: diag.file && diag.file.fileName, - line, - col, - }; - }); - }); -} - -function getLineCol(text: string, start: number): [number, number] { - if (!text || !start) return [0, 0]; + const generator = runDiagnostics({ tsconfigPath, config }); - let counter = 0; - const parts = text.split('\n'); - for (let i = 0; i <= parts.length; i++) { - const line = parts[i]; - if (counter + line.length > start) { - return [i + 1, start + 1 - counter]; - } else { - counter = counter + (line.length + 1); - continue; + for await (const signal of generator) { + if (signal.messages.length) { + yield logger.diagnosticFile(signal.filePath, signal.messages); } } - - return [0, 0]; -} - -function constructDiagnosticsPerFile(diagnostics: FormattedDisplayableDiagnostic[]): string { - const diagnosticsByFile = diagnostics.reduce>((acc, diag) => { - const file = diag.file || ''; - if (!acc[file]) { - acc[file] = []; - } - acc[file].push(`[${diag.line}:${diag.col}] ${diag.message}`); - return acc; - }, {}); - - return Object.entries(diagnosticsByFile).reduce((acc, [fileName, diagnostics]) => { - return `${acc}\n${fileName}\n${diagnostics.join('\n')}\n`; - }, ''); } diff --git a/packages/cli-utils/src/commands/check/thread.ts b/packages/cli-utils/src/commands/check/thread.ts new file mode 100644 index 00000000..08339d5e --- /dev/null +++ b/packages/cli-utils/src/commands/check/thread.ts @@ -0,0 +1,92 @@ +import * as path from 'node:path'; +import { Project, ts } from 'ts-morph'; + +import { load, resolveTypeScriptRootDir } from '@gql.tada/internal'; +import { init, getGraphQLDiagnostics } from '@0no-co/graphqlsp/api'; + +import type { GraphQLSPConfig } from '../../lsp'; +import { createPluginInfo } from '../../ts/project'; +import { expose } from '../../threads'; + +import type { Severity, DiagnosticMessage, DiagnosticSignal } from './types'; + +const getLineCol = (text: string, start: number | undefined): [number, number] => { + if (text && start) { + let counter = 0; + const parts = text.split('\n'); + for (let i = 0; i <= parts.length; i++) { + const line = parts[i]; + if (counter + line.length > start) { + return [i + 1, start + 1 - counter]; + } else { + counter = counter + (line.length + 1); + continue; + } + } + } + return [0, 0]; +}; + +const loadSchema = async (rootPath: string, config: GraphQLSPConfig) => { + const loader = load({ origin: config.schema, rootPath }); + const result = await loader.load(); + if (!result) throw new Error('Failed to load schema'); + return { current: result.schema, version: 1 }; +}; + +export interface DiagnosticsParams { + config: GraphQLSPConfig; + tsconfigPath: string; +} + +async function* _runDiagnostics( + params: DiagnosticsParams +): AsyncIterableIterator { + init({ typescript: ts as any }); + const projectPath = path.dirname(params.tsconfigPath); + const rootPath = (await resolveTypeScriptRootDir(params.tsconfigPath)) || params.tsconfigPath; + const schemaRef = await loadSchema(rootPath, params.config); + const project = new Project({ tsConfigFilePath: params.tsconfigPath }); + const pluginInfo = createPluginInfo(project, params.config, projectPath); + const sourceFiles = project.getSourceFiles(); + + for (const sourceFile of sourceFiles) { + const filePath = sourceFile.getFilePath(); + const diagnostics = getGraphQLDiagnostics(filePath, schemaRef, pluginInfo); + const messages: DiagnosticMessage[] = []; + + if (diagnostics && diagnostics.length) { + const sourceText = sourceFile.getText(); + for (const diagnostic of diagnostics) { + if ( + !('messageText' in diagnostic) || + typeof diagnostic.messageText !== 'string' || + !diagnostic.file + ) + continue; + let severity: Severity = 'info'; + if (diagnostic.category === ts.DiagnosticCategory.Error) { + severity = 'error'; + } else if (diagnostic.category === ts.DiagnosticCategory.Warning) { + severity = 'warn'; + } + const [line, col] = getLineCol(sourceText, diagnostic.start); + messages.push({ + severity, + message: diagnostic.messageText, + file: diagnostic.file.fileName, + line, + col, + }); + } + } + + yield { + kind: 'FILE_DIAGNOSTICS', + filePath, + messages, + }; + } +} + +export const runDiagnostics = expose(_runDiagnostics); diff --git a/packages/cli-utils/src/commands/check/types.ts b/packages/cli-utils/src/commands/check/types.ts new file mode 100644 index 00000000..1daa4d78 --- /dev/null +++ b/packages/cli-utils/src/commands/check/types.ts @@ -0,0 +1,17 @@ +export type Severity = 'error' | 'warn' | 'info'; + +export interface DiagnosticMessage { + severity: Severity; + message: string; + file: string; + line: number; + col: number; +} + +export interface FileDiagnosticsSignal { + kind: 'FILE_DIAGNOSTICS'; + filePath: string; + messages: DiagnosticMessage[]; +} + +export type DiagnosticSignal = FileDiagnosticsSignal; From 8c0dbd070ccf2cbf7bd9adaf6ba2ee178fbf9ccf Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 16:22:50 +0100 Subject: [PATCH 05/16] Fix Github output --- packages/cli-utils/src/term/github.ts | 12 ++++++------ packages/cli-utils/src/term/symbols.ts | 2 +- packages/cli-utils/src/term/write.ts | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/cli-utils/src/term/github.ts b/packages/cli-utils/src/term/github.ts index 799c53d6..0e01657f 100644 --- a/packages/cli-utils/src/term/github.ts +++ b/packages/cli-utils/src/term/github.ts @@ -15,10 +15,10 @@ const toCommandValue = (input: unknown): string => : JSON.stringify(input); const escapeData = (input: unknown) => - toCommandValue(s).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); + toCommandValue(input).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); const escapeProperty = (input: unknown): string => - toCommandValue(s) + toCommandValue(input) .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') @@ -30,13 +30,13 @@ export function githubAnnotation( message: string, properties?: AnnotationProperties ) { - if (isGitHubCI) { + if (isGithubCI) { let out = `::${kind}`; - const propsOut = ''; + let propsOut = ''; if (properties) { for (const key in properties) { - if (properties) properties += ','; - if (properties[key]) cmdStr += `${key}=${escapeProperty(propreties[key])}`; + if (properties) propsOut += ','; + if (properties[key]) propsOut += `${key}=${escapeProperty(properties[key])}`; } } if (propsOut) out += ` ${propsOut}`; diff --git a/packages/cli-utils/src/term/symbols.ts b/packages/cli-utils/src/term/symbols.ts index e0915732..fdf2a67f 100644 --- a/packages/cli-utils/src/term/symbols.ts +++ b/packages/cli-utils/src/term/symbols.ts @@ -136,7 +136,7 @@ export const enum Heart { export const enum Icons { Tick = '✓', TickSwoosh = '✔', - Cross = '×', + Cross = '✖', CrossSwoosh = '✘', Home = '⌂', Note = '♪', diff --git a/packages/cli-utils/src/term/write.ts b/packages/cli-utils/src/term/write.ts index 4b122c76..4f3b7b78 100644 --- a/packages/cli-utils/src/term/write.ts +++ b/packages/cli-utils/src/term/write.ts @@ -23,15 +23,15 @@ export const stripAnsi = (input: string) => input.replace(ansiRegex, ''); export class CLIError extends Error { output: string; - exit: number | undefined; + exit: number; constructor(message: string, exitCode?: number) { super(stripAnsi(message)); this.output = message; - this.exit = exitCode; + this.exit = exitCode == null ? 0 : 1; } toString() { - if (this.exit != null) process.exitCode = this.exit; + if (this.exit) process.exitCode = this.exit; return this.output; } } From c2e147716413ba421ae8f79551f50dd6d34a4fdd Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 16:22:56 +0100 Subject: [PATCH 06/16] Add summary output --- .../cli-utils/src/commands/check/index.ts | 4 +- .../cli-utils/src/commands/check/logger.ts | 55 +++++++++++++++++-- .../cli-utils/src/commands/check/runner.ts | 38 +++++++++++-- .../cli-utils/src/commands/check/types.ts | 2 + .../cli-utils/src/commands/doctor/index.ts | 2 +- 5 files changed, 89 insertions(+), 12 deletions(-) diff --git a/packages/cli-utils/src/commands/check/index.ts b/packages/cli-utils/src/commands/check/index.ts index 86062008..68bd4a71 100644 --- a/packages/cli-utils/src/commands/check/index.ts +++ b/packages/cli-utils/src/commands/check/index.ts @@ -19,7 +19,7 @@ export class CheckCommand extends Command { Option.String('--level,-l', { description: 'The minimum severity of diagnostics to display (info, warn, error)', validator: t.isOneOf([t.isLiteral('info'), t.isLiteral('warn'), t.isLiteral('error')]), - }) || 'error'; + }) || 'info'; async execute() { const result = await initTTY().start( @@ -29,6 +29,6 @@ export class CheckCommand extends Command { tsconfig: this.tsconfig, }) ); - return typeof result === 'object' ? result.exit : 0; + return process.exitCode || (typeof result === 'object' ? result.exit : 0); } } diff --git a/packages/cli-utils/src/commands/check/logger.ts b/packages/cli-utils/src/commands/check/logger.ts index 8c635a20..53ca724f 100644 --- a/packages/cli-utils/src/commands/check/logger.ts +++ b/packages/cli-utils/src/commands/check/logger.ts @@ -1,16 +1,19 @@ +import * as path from 'node:path'; import * as t from '../../term'; import type { DiagnosticMessage } from './types'; +import type { SeveritySummary } from './types'; -export function diagnosticFile(filePath: string, messages: DiagnosticMessage[]) { - let output = t.text([ +const CWD = process.cwd(); + +export function diagnosticFile(filePath: string) { + const relativePath = path.relative(CWD, filePath); + if (!relativePath.startsWith('..')) filePath = relativePath; + return t.text([ t.cmd(t.CSI.Style, t.Style.Underline), filePath, t.cmd(t.CSI.Style, t.Style.NoUnderline), '\n', ]); - for (const message of messages) output += diagnosticMessage(message); - output += '\n'; - return output; } export function diagnosticMessage(message: DiagnosticMessage) { @@ -43,3 +46,45 @@ export function diagnosticMessage(message: DiagnosticMessage) { t.Chars.Newline, ]); } + +export function infoSummary(summary: SeveritySummary) { + const { info, error, warn } = summary; + let out = ''; + if (info) { + out += t.text([t.cmd(t.CSI.Style, t.Style.Blue), t.Icons.Info, ` ${info} notices\n`]); + } + if (error || warn) { + out += t.text([ + t.cmd(t.CSI.Style, t.Style.BrightYellow), + t.Icons.Warning, + ` ${error + warn} problems (${error} errors, ${warn} warnings)\n`, + ]); + } else { + out += t.text([t.cmd(t.CSI.Style, t.Style.BrightGreen), t.Icons.Tick, ` No problems found`]); + } + return out; +} + +export function problemsSummary(summary: SeveritySummary) { + const { info, error, warn } = summary; + let out = ''; + if (info) { + out += t.text([t.cmd(t.CSI.Style, t.Style.Blue), t.Icons.Info, ` ${info} notices\n`]); + } + out += t.text([ + t.cmd(t.CSI.Style, t.Style.Red), + t.Icons.Cross, + ` ${error + warn} problems (${error} errors, ${warn} warnings)\n`, + ]); + return t.error(out); +} + +export function diagnosticMessageGithub(message: DiagnosticMessage): void { + const kind = + message.severity === 'warn' ? 'warning' : message.severity === 'error' ? 'error' : 'notice'; + t.githubAnnotation(kind, message.message, { + file: message.file, + line: message.line, + col: message.col, + }); +} diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index 1845c3c2..cf7f0b5b 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -4,8 +4,18 @@ import { getGraphQLSPConfig } from '../../lsp'; import { getTsConfig } from '../../tsconfig'; import * as logger from './logger'; -type Severity = 'error' | 'warn' | 'info'; -const severities: Severity[] = ['error', 'warn', 'info']; +import type { Severity, SeveritySummary } from './types'; + +const isMinSeverity = (severity: Severity, minSeverity: Severity) => { + switch (severity) { + case 'info': + return minSeverity !== 'warn' && minSeverity !== 'error'; + case 'warn': + return minSeverity !== 'error'; + case 'error': + return true; + } +}; export interface FormattedDisplayableDiagnostic { severity: Severity; @@ -41,11 +51,31 @@ export async function* run(opts: Options) { ? path.resolve(CWD, tsconfigPath, 'tsconfig.json') : path.resolve(CWD, tsconfigPath); + const summary: SeveritySummary = { warn: 0, error: 0, info: 0 }; + const minSeverity = opts.minSeverity; const generator = runDiagnostics({ tsconfigPath, config }); for await (const signal of generator) { - if (signal.messages.length) { - yield logger.diagnosticFile(signal.filePath, signal.messages); + let buffer = ''; + for (const message of signal.messages) { + summary[message.severity]++; + if (isMinSeverity(message.severity, minSeverity)) { + buffer += logger.diagnosticMessage(message); + logger.diagnosticMessageGithub(message); + } + } + if (buffer) { + yield logger.diagnosticFile(signal.filePath); + yield buffer + '\n'; } } + + // Reset notice count if it's outside of min severity + if (minSeverity !== 'info') summary.info = 0; + + if ((opts.failOnWarn && summary.warn) || summary.error) { + throw logger.problemsSummary(summary); + } else { + yield logger.infoSummary(summary); + } } diff --git a/packages/cli-utils/src/commands/check/types.ts b/packages/cli-utils/src/commands/check/types.ts index 1daa4d78..7b5348ea 100644 --- a/packages/cli-utils/src/commands/check/types.ts +++ b/packages/cli-utils/src/commands/check/types.ts @@ -1,5 +1,7 @@ export type Severity = 'error' | 'warn' | 'info'; +export type SeveritySummary = Record; + export interface DiagnosticMessage { severity: Severity; message: string; diff --git a/packages/cli-utils/src/commands/doctor/index.ts b/packages/cli-utils/src/commands/doctor/index.ts index 5ae796e4..27b27b05 100644 --- a/packages/cli-utils/src/commands/doctor/index.ts +++ b/packages/cli-utils/src/commands/doctor/index.ts @@ -7,6 +7,6 @@ export class DoctorCommand extends Command { async execute() { const result = await initTTY().start(run()); - return typeof result === 'object' ? result.exit : 0; + return process.exitCode || (typeof result === 'object' ? result.exit : 0); } } From 2a62a4d73722124bbed2a8ae43f957abb44a62b9 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 18:18:43 +0100 Subject: [PATCH 07/16] Fix term sequencing with active sources --- packages/cli-utils/src/term/symbols.ts | 1 + packages/cli-utils/src/term/write.ts | 59 +++++++++++++++++--------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/cli-utils/src/term/symbols.ts b/packages/cli-utils/src/term/symbols.ts index fdf2a67f..76ecde9f 100644 --- a/packages/cli-utils/src/term/symbols.ts +++ b/packages/cli-utils/src/term/symbols.ts @@ -3,6 +3,7 @@ export const enum Chars { Newline = '\x0a', Tab = '\x09', Space = ' ', + Ellipsis = '…', } export const enum Box { diff --git a/packages/cli-utils/src/term/write.ts b/packages/cli-utils/src/term/write.ts index 4f3b7b78..76370f75 100644 --- a/packages/cli-utils/src/term/write.ts +++ b/packages/cli-utils/src/term/write.ts @@ -5,17 +5,21 @@ import { fromAsyncIterable, fromValue, concatMap, - sample, + never, merge, takeUntil, + takeLast, filter, + concat, + switchMap, + sample, + delay, share, - take, scan, map, } from 'wonka'; -import { cmd, CSI, Style } from './csi'; +import { cmd, CSI, EraseLine, Style } from './csi'; const ansiRegex = /([\x1B\x9B][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><])/g; @@ -65,13 +69,16 @@ function error(arg: readonly string[] | string | number, ...input: readonly stri } function clear(text: string) { - if (!text) return ''; let lines = 0; for (let index = 0; index < text.length; index++) if (text.charCodeAt(index) === 10 /*'\n'*/) lines++; - return ( - (lines > 0 ? cmd(CSI.PrevLine, lines) : cmd(CSI.ToColumn, 1)) + cmd(CSI.DeleteLines, lines + 1) - ); + if (lines) { + return cmd(CSI.PrevLine, lines) + cmd(CSI.DeleteLines, lines + 1); + } else if (stripAnsi(text)) { + return cmd(CSI.EraseLine, EraseLine.Backward), cmd(CSI.ToColumn, 1); + } else { + return ''; + } } type ComposeInput = undefined | string | CLIError | Source | AsyncIterable; @@ -85,33 +92,45 @@ async function* convertError(outputs: AsyncIterable): AsyncIterabl } function compose(outputs: AsyncIterable): Source { - const outputs$ = share(fromAsyncIterable(convertError(outputs))); const reset = cmd(CSI.Style, [Style.Reset, Style.NoInvert]); + const outputs$ = pipe( + fromAsyncIterable(convertError(outputs)), + concatMap((output) => { + return typeof output === 'object' && !(output instanceof CLIError) + ? compose(output) + : fromValue(output); + }), + filter((x: T): x is Exclude => x != null), + share + ); + return pipe( outputs$, - filter((x: T): x is Exclude => x != null), concatMap((output) => { - if (typeof output === 'object' && !(output instanceof CLIError)) { - return compose(output); - } - const output$ = share( - typeof output === 'string' || output instanceof CLIError ? fromValue(output) : output + const output$ = pipe( + typeof output === 'string' || output instanceof CLIError + ? fromValue(output) + : merge([output, never]), + takeUntil(outputs$), + share ); return pipe( merge([ pipe( output$, - sample(outputs$), - map((output) => (typeof output === 'string' && output.endsWith('\n') ? output : '')), - take(1) + takeLast(1), + map((output) => (typeof output === 'string' && !output.endsWith('\n') ? '' : output)) ), - pipe(output$, takeUntil(outputs$)), + output$, ]), scan((prev: CLIError | string, output) => { - return typeof output === 'string' ? clear('' + prev) + output + reset : output; + return typeof output === 'string' + ? clear(typeof prev === 'string' ? prev : '') + output + reset + : output; }, '') ); - }) + }), + takeUntil(pipe(outputs$, takeLast(1))) ); } From 48cdf2b99389632743d578b331c0e5c287d8b237 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 18:22:36 +0100 Subject: [PATCH 08/16] Add progress indicator --- .../cli-utils/src/commands/check/logger.ts | 20 +++++++++++++++++++ .../cli-utils/src/commands/check/runner.ts | 8 ++++++-- .../cli-utils/src/commands/doctor/logger.ts | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/cli-utils/src/commands/check/logger.ts b/packages/cli-utils/src/commands/check/logger.ts index 53ca724f..791695d3 100644 --- a/packages/cli-utils/src/commands/check/logger.ts +++ b/packages/cli-utils/src/commands/check/logger.ts @@ -1,3 +1,5 @@ +import { pipe, interval, map } from 'wonka'; + import * as path from 'node:path'; import * as t from '../../term'; import type { DiagnosticMessage } from './types'; @@ -88,3 +90,21 @@ export function diagnosticMessageGithub(message: DiagnosticMessage): void { col: message.col, }); } + +export function runningDiagnostics(file: number, ofFiles?: number) { + const progress = ofFiles ? `(${file} / ${ofFiles})` : `(${file})`; + return pipe( + interval(150), + map((state) => { + return t.text([ + t.cmd(t.CSI.Style, t.Style.Magenta), + t.dotSpinner[state % t.dotSpinner.length], + ' ', + t.cmd(t.CSI.Style, t.Style.Foreground), + `Checking files${t.Chars.Ellipsis} `, + t.cmd(t.CSI.Style, t.Style.BrightBlack), + progress, + ]); + }) + ); +} diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index cf7f0b5b..2e34ff25 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -55,6 +55,9 @@ export async function* run(opts: Options) { const minSeverity = opts.minSeverity; const generator = runDiagnostics({ tsconfigPath, config }); + let fileCount = 1; + yield logger.runningDiagnostics(++fileCount); + for await (const signal of generator) { let buffer = ''; for (const message of signal.messages) { @@ -65,9 +68,10 @@ export async function* run(opts: Options) { } } if (buffer) { - yield logger.diagnosticFile(signal.filePath); - yield buffer + '\n'; + yield logger.diagnosticFile(signal.filePath) + buffer + '\n'; } + + yield logger.runningDiagnostics(++fileCount); } // Reset notice count if it's outside of min severity diff --git a/packages/cli-utils/src/commands/doctor/logger.ts b/packages/cli-utils/src/commands/doctor/logger.ts index 6143085e..1d5b596f 100644 --- a/packages/cli-utils/src/commands/doctor/logger.ts +++ b/packages/cli-utils/src/commands/doctor/logger.ts @@ -96,7 +96,7 @@ export function runningTask(description: string) { t.circleSpinner[state % t.circleSpinner.length], ' ', t.cmd(t.CSI.Style, t.Style.Foreground), - description, + description.trim(), ]); }) ); From 61641a364615d82e2ac1ce136854335827365d11 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 18:29:47 +0100 Subject: [PATCH 09/16] Add total count to progress indicator --- packages/cli-utils/src/commands/check/logger.ts | 2 +- packages/cli-utils/src/commands/check/runner.ts | 11 ++++++++--- packages/cli-utils/src/commands/check/thread.ts | 5 +++++ packages/cli-utils/src/commands/check/types.ts | 7 ++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/cli-utils/src/commands/check/logger.ts b/packages/cli-utils/src/commands/check/logger.ts index 791695d3..7a980fb1 100644 --- a/packages/cli-utils/src/commands/check/logger.ts +++ b/packages/cli-utils/src/commands/check/logger.ts @@ -92,7 +92,7 @@ export function diagnosticMessageGithub(message: DiagnosticMessage): void { } export function runningDiagnostics(file: number, ofFiles?: number) { - const progress = ofFiles ? `(${file} / ${ofFiles})` : `(${file})`; + const progress = ofFiles ? `(${file}/${ofFiles})` : `(${file})`; return pipe( interval(150), map((state) => { diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index 2e34ff25..36fd13fc 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -55,10 +55,15 @@ export async function* run(opts: Options) { const minSeverity = opts.minSeverity; const generator = runDiagnostics({ tsconfigPath, config }); - let fileCount = 1; - yield logger.runningDiagnostics(++fileCount); + let totalFileCount = 0; + let fileCount = 0; for await (const signal of generator) { + if (signal.kind === 'FILE_COUNT') { + totalFileCount = signal.fileCount; + continue; + } + let buffer = ''; for (const message of signal.messages) { summary[message.severity]++; @@ -71,7 +76,7 @@ export async function* run(opts: Options) { yield logger.diagnosticFile(signal.filePath) + buffer + '\n'; } - yield logger.runningDiagnostics(++fileCount); + yield logger.runningDiagnostics(++fileCount, totalFileCount); } // Reset notice count if it's outside of min severity diff --git a/packages/cli-utils/src/commands/check/thread.ts b/packages/cli-utils/src/commands/check/thread.ts index 08339d5e..6f35204c 100644 --- a/packages/cli-utils/src/commands/check/thread.ts +++ b/packages/cli-utils/src/commands/check/thread.ts @@ -50,6 +50,11 @@ async function* _runDiagnostics( const pluginInfo = createPluginInfo(project, params.config, projectPath); const sourceFiles = project.getSourceFiles(); + yield { + kind: 'FILE_COUNT', + fileCount: sourceFiles.length, + }; + for (const sourceFile of sourceFiles) { const filePath = sourceFile.getFilePath(); const diagnostics = getGraphQLDiagnostics(filePath, schemaRef, pluginInfo); diff --git a/packages/cli-utils/src/commands/check/types.ts b/packages/cli-utils/src/commands/check/types.ts index 7b5348ea..f18347f7 100644 --- a/packages/cli-utils/src/commands/check/types.ts +++ b/packages/cli-utils/src/commands/check/types.ts @@ -16,4 +16,9 @@ export interface FileDiagnosticsSignal { messages: DiagnosticMessage[]; } -export type DiagnosticSignal = FileDiagnosticsSignal; +export interface FileCountSignal { + kind: 'FILE_COUNT'; + fileCount: number; +} + +export type DiagnosticSignal = FileDiagnosticsSignal | FileCountSignal; From bb1b407b754cc6b7ac74d910cb3bcb94c85a8879 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 18:31:47 +0100 Subject: [PATCH 10/16] Add basic error handling --- .../cli-utils/src/commands/check/logger.ts | 10 ++++++ .../cli-utils/src/commands/check/runner.ts | 36 ++++++++++--------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/packages/cli-utils/src/commands/check/logger.ts b/packages/cli-utils/src/commands/check/logger.ts index 7a980fb1..b43909f1 100644 --- a/packages/cli-utils/src/commands/check/logger.ts +++ b/packages/cli-utils/src/commands/check/logger.ts @@ -108,3 +108,13 @@ export function runningDiagnostics(file: number, ofFiles?: number) { }) ); } + +export function errorMessage(message: string) { + return t.error([ + '\n', + t.cmd(t.CSI.Style, [t.Style.Red, t.Style.Invert]), + ` ${t.Icons.Warning} Error `, + t.cmd(t.CSI.Style, t.Style.NoInvert), + `\n${message.trim()}\n`, + ]); +} diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index 36fd13fc..bc743969 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -58,25 +58,29 @@ export async function* run(opts: Options) { let totalFileCount = 0; let fileCount = 0; - for await (const signal of generator) { - if (signal.kind === 'FILE_COUNT') { - totalFileCount = signal.fileCount; - continue; - } + try { + for await (const signal of generator) { + if (signal.kind === 'FILE_COUNT') { + totalFileCount = signal.fileCount; + continue; + } - let buffer = ''; - for (const message of signal.messages) { - summary[message.severity]++; - if (isMinSeverity(message.severity, minSeverity)) { - buffer += logger.diagnosticMessage(message); - logger.diagnosticMessageGithub(message); + let buffer = ''; + for (const message of signal.messages) { + summary[message.severity]++; + if (isMinSeverity(message.severity, minSeverity)) { + buffer += logger.diagnosticMessage(message); + logger.diagnosticMessageGithub(message); + } + } + if (buffer) { + yield logger.diagnosticFile(signal.filePath) + buffer + '\n'; } - } - if (buffer) { - yield logger.diagnosticFile(signal.filePath) + buffer + '\n'; - } - yield logger.runningDiagnostics(++fileCount, totalFileCount); + yield logger.runningDiagnostics(++fileCount, totalFileCount); + } + } catch (error: any) { + throw logger.errorMessage(error.message || `${error}`); } // Reset notice count if it's outside of min severity From 9a17b0cc7e979247a3a4e2846541eaa622dd14c7 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 18:39:27 +0100 Subject: [PATCH 11/16] Add initial error messages --- packages/cli-utils/src/commands/check/logger.ts | 7 +++++++ packages/cli-utils/src/commands/check/runner.ts | 13 +++++++++++-- packages/cli-utils/src/term/write.ts | 4 ---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/cli-utils/src/commands/check/logger.ts b/packages/cli-utils/src/commands/check/logger.ts index b43909f1..3032be73 100644 --- a/packages/cli-utils/src/commands/check/logger.ts +++ b/packages/cli-utils/src/commands/check/logger.ts @@ -7,6 +7,13 @@ import type { SeveritySummary } from './types'; const CWD = process.cwd(); +export function code(text: string) { + return t.text`${t.cmd(t.CSI.Style, t.Style.Underline)}${text}${t.cmd( + t.CSI.Style, + t.Style.NoUnderline + )}`; +} + export function diagnosticFile(filePath: string) { const relativePath = path.relative(CWD, filePath); if (!relativePath.startsWith('..')) filePath = relativePath; diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index bc743969..c1c4ec7e 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -37,12 +37,21 @@ export async function* run(opts: Options) { const tsconfig = await getTsConfig(opts.tsconfig); if (!tsconfig) { - return; + const relative = opts.tsconfig + ? logger.code(path.relative(process.cwd(), opts.tsconfig)) + : 'the current working directory'; + throw logger.errorMessage( + `The ${logger.code('tsconfig.json')} file at ${relative} could not be loaded.\n` + ); } const config = getGraphQLSPConfig(tsconfig); if (!config) { - return; + throw logger.errorMessage( + `No ${logger.code('"@0no-co/graphqlsp"')} plugin was found in your ${logger.code( + 'tsconfig.json' + )}.\n` + ); } let tsconfigPath = opts.tsconfig || CWD; diff --git a/packages/cli-utils/src/term/write.ts b/packages/cli-utils/src/term/write.ts index 76370f75..3da53e28 100644 --- a/packages/cli-utils/src/term/write.ts +++ b/packages/cli-utils/src/term/write.ts @@ -10,10 +10,6 @@ import { takeUntil, takeLast, filter, - concat, - switchMap, - sample, - delay, share, scan, map, From 6cbcb7fc6555cb2f9892ede7582bca822c921a71 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 18:40:21 +0100 Subject: [PATCH 12/16] Add changeset --- .changeset/tender-keys-act.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-keys-act.md diff --git a/.changeset/tender-keys-act.md b/.changeset/tender-keys-act.md new file mode 100644 index 00000000..43154f01 --- /dev/null +++ b/.changeset/tender-keys-act.md @@ -0,0 +1,5 @@ +--- +"@gql.tada/cli-utils": minor +--- + +Add `--tsconfig` option to the `check` command, update its log output, and add support for GitHub Actions annotations to it. From 109b0e95d01439ee0d87a88a7a43b00e55eb87ff Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 18:41:46 +0100 Subject: [PATCH 13/16] Add missing newline --- packages/cli-utils/src/commands/check/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-utils/src/commands/check/logger.ts b/packages/cli-utils/src/commands/check/logger.ts index 3032be73..0a1d4cb4 100644 --- a/packages/cli-utils/src/commands/check/logger.ts +++ b/packages/cli-utils/src/commands/check/logger.ts @@ -69,7 +69,7 @@ export function infoSummary(summary: SeveritySummary) { ` ${error + warn} problems (${error} errors, ${warn} warnings)\n`, ]); } else { - out += t.text([t.cmd(t.CSI.Style, t.Style.BrightGreen), t.Icons.Tick, ` No problems found`]); + out += t.text([t.cmd(t.CSI.Style, t.Style.BrightGreen), t.Icons.Tick, ` No problems found\n`]); } return out; } From 4cf2fc21948506b80849376e1118f650c61490b2 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 19:01:05 +0100 Subject: [PATCH 14/16] Fix erase line clearing for single line updates --- packages/cli-utils/src/term/write.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-utils/src/term/write.ts b/packages/cli-utils/src/term/write.ts index 3da53e28..f2ac514c 100644 --- a/packages/cli-utils/src/term/write.ts +++ b/packages/cli-utils/src/term/write.ts @@ -71,7 +71,7 @@ function clear(text: string) { if (lines) { return cmd(CSI.PrevLine, lines) + cmd(CSI.DeleteLines, lines + 1); } else if (stripAnsi(text)) { - return cmd(CSI.EraseLine, EraseLine.Backward), cmd(CSI.ToColumn, 1); + return cmd(CSI.EraseLine, EraseLine.Backward) + cmd(CSI.ToColumn, 1); } else { return ''; } From e568673c90b97968a68e4c700451f817aebcf6b3 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 19:02:10 +0100 Subject: [PATCH 15/16] Add missing type annotation --- packages/cli-utils/src/commands/check/runner.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli-utils/src/commands/check/runner.ts b/packages/cli-utils/src/commands/check/runner.ts index c1c4ec7e..088a2ab3 100644 --- a/packages/cli-utils/src/commands/check/runner.ts +++ b/packages/cli-utils/src/commands/check/runner.ts @@ -4,6 +4,7 @@ import { getGraphQLSPConfig } from '../../lsp'; import { getTsConfig } from '../../tsconfig'; import * as logger from './logger'; +import type { ComposeInput } from '../../term'; import type { Severity, SeveritySummary } from './types'; const isMinSeverity = (severity: Severity, minSeverity: Severity) => { @@ -31,7 +32,7 @@ export interface Options { tsconfig: string | undefined; } -export async function* run(opts: Options) { +export async function* run(opts: Options): AsyncIterable { const CWD = process.cwd(); const { runDiagnostics } = await import('./thread'); From 8443463760213438762075e7343cdc1582180cf8 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sun, 14 Apr 2024 19:08:56 +0100 Subject: [PATCH 16/16] Instantiate workers lazily and unref immediately --- packages/cli-utils/src/threads/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cli-utils/src/threads/index.ts b/packages/cli-utils/src/threads/index.ts index 8d800e39..091c8ca2 100644 --- a/packages/cli-utils/src/threads/index.ts +++ b/packages/cli-utils/src/threads/index.ts @@ -61,9 +61,14 @@ export interface Generator { } function main(url: string | URL): Generator { - const worker = new Worker(url, workerOpts); + let worker: Worker; let ids = 0; return (...args: Args) => { + if (!worker) { + worker = new Worker(url, workerOpts); + worker.unref(); + } + const id = ++ids | 0; const buffer: ThreadMessage[] = []; @@ -79,7 +84,6 @@ function main(url: string | URL): Generator