From d34a9f34e57af9d4563cb37a3aec345866217b09 Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Sat, 2 May 2026 08:06:23 +0900 Subject: [PATCH 1/5] fix: avoid duplicate oxlint in verify Co-authored-by: WillBooster (Codex CLI) --- packages/wb/src/commands/lint.ts | 149 +++++++++++++++++-------- packages/wb/src/commands/verifyCode.ts | 18 +-- 2 files changed, 107 insertions(+), 60 deletions(-) diff --git a/packages/wb/src/commands/lint.ts b/packages/wb/src/commands/lint.ts index 7a6e612e..ec4a1dbd 100644 --- a/packages/wb/src/commands/lint.ts +++ b/packages/wb/src/commands/lint.ts @@ -96,6 +96,10 @@ const prettierFixtureIgnorePattern = '!**/test{-,/}fixtures/**'; type BufferedLintRunResult = BufferedRunResult & { command: string; cwd: string }; type LintRunResult = BufferedLintRunResult | { exitCode: number }; +interface LintRunCommand { + command: string; + project: Project; +} export const lintCommand: CommandModule = { command: 'lint [files...]', @@ -210,56 +214,83 @@ export async function lint(argv: LintCommandArgv): Promise { sortPackageJsonArgs = projects.descendants.map((p) => p.packageJsonPath); } - const lintPromises: Promise[] = []; + const formatterCommands: LintRunCommand[] = []; + const linterCommands: LintRunCommand[] = []; const lintRunOptions = { exitIfFailed: false, forceColor: !argv.printAllOutput } as const; + const shouldRunFormatters = Boolean(argv.format); + const shouldRunLinters = !argv.format || Boolean(argv.fix); if (files.length > 0) { - for (const [project, lintFilePaths] of lintFilePathsByProject) { - const lintCommand = buildLintCommand(project, argv, lintFilePaths); - if (!lintCommand) continue; + if (shouldRunLinters) { + for (const [project, lintFilePaths] of lintFilePathsByProject) { + const lintCommand = buildLintCommand(project, argv, lintFilePaths); + if (!lintCommand) continue; - lintPromises.push(runLintCommand(lintCommand, project, argv, lintRunOptions)); + linterCommands.push({ command: lintCommand, project }); + } + for (const [project, pythonFilePaths] of pythonFilePathsByProject) { + linterCommands.push({ command: buildPoetryLintCommand(argv, pythonFilePaths), project }); + } + for (const [project, dartFilePaths] of dartFilePathsByProject) { + linterCommands.push({ command: buildDartLintCommand(dartFilePaths), project }); + } } - if (argv.format) { + if (shouldRunFormatters) { for (const [project, oxfmtFilePaths] of oxfmtFilePathsByProject) { - lintPromises.push(runLintCommand(buildOxfmtCommand(oxfmtFilePaths), project, argv, lintRunOptions)); + formatterCommands.push({ command: buildOxfmtCommand(oxfmtFilePaths), project }); + } + for (const [project, pythonFilePaths] of pythonFilePathsByProject) { + formatterCommands.push({ command: buildPoetryFormatCommand(pythonFilePaths), project }); + } + for (const [project, dartFilePaths] of dartFilePathsByProject) { + formatterCommands.push({ command: buildDartFormatCommand(dartFilePaths), project }); } - } - for (const [project, pythonFilePaths] of pythonFilePathsByProject) { - lintPromises.push(runLintCommand(buildPoetryCommand(argv, pythonFilePaths), project, argv, lintRunOptions)); - } - for (const [project, dartFilePaths] of dartFilePathsByProject) { - lintPromises.push(runLintCommand(buildDartCommand(argv, dartFilePaths), project, argv, lintRunOptions)); } } else { - for (const project of projects.descendants) { - if (project.packageJson.workspaces && !project.hasSourceCode) continue; + if (shouldRunLinters) { + for (const project of projects.descendants) { + if (project.packageJson.workspaces && !project.hasSourceCode) continue; - const lintCommand = buildLintCommand(project, argv); - if (!lintCommand) continue; + const lintCommand = buildLintCommand(project, argv); + if (!lintCommand) continue; - lintPromises.push(runLintCommand(lintCommand, project, argv, lintRunOptions)); + linterCommands.push({ command: lintCommand, project }); + } } for (const project of projects.descendants) { - if (project.hasPoetryLock) { - lintPromises.push(runLintCommand(buildPoetryCommand(argv), project, argv, lintRunOptions)); + if (project.hasPoetryLock && shouldRunLinters) { + linterCommands.push({ command: buildPoetryLintCommand(argv), project }); + } + if (project.hasPubspecYaml && shouldRunLinters) { + linterCommands.push({ command: buildDartLintCommand(), project }); + } + if (project.hasOxfmt && shouldRunFormatters) { + formatterCommands.push({ command: buildOxfmtCommand(), project }); } - if (project.hasPubspecYaml) { - lintPromises.push(runLintCommand(buildDartCommand(argv), project, argv, lintRunOptions)); + if (project.hasPoetryLock && shouldRunFormatters) { + formatterCommands.push({ command: buildPoetryFormatCommand(), project }); } - if (project.hasOxfmt && argv.format) { - lintPromises.push(runLintCommand(buildOxfmtCommand(), project, argv, lintRunOptions)); + if (project.hasPubspecYaml && shouldRunFormatters) { + formatterCommands.push({ command: buildDartFormatCommand(), project }); } } } - const lintResults = await Promise.all(lintPromises); - printSilentLintOutputs(lintResults, argv); - const lintExitCodes = lintResults.map((result) => result.exitCode); + const lintExitCodes: number[] = []; - if (missingLintToolForExplicitFiles || lintExitCodes.some((exitCode) => exitCode !== 0)) { + if (shouldRunFormatters) { + const formatterResults = await runLintCommands(formatterCommands, argv, lintRunOptions); + printSilentLintOutputs(formatterResults, argv); + lintExitCodes.push(...formatterResults.map((result) => result.exitCode)); + + if (lintExitCodes.some((exitCode) => exitCode !== 0)) { + return 1; + } + } + + if (missingLintToolForExplicitFiles) { return 1; } - if (argv.format) { + if (shouldRunFormatters) { if (prettierArgs.length > 0 && projects.self.hasPrettier) { const prettierResult = await runLintCommand( buildShellCommand([ @@ -289,11 +320,29 @@ export async function lint(argv: LintCommandArgv): Promise { printSilentLintOutputs([sortPackageJsonResult], argv); lintExitCodes.push(sortPackageJsonResult.exitCode); } + + if (lintExitCodes.some((exitCode) => exitCode !== 0)) { + return 1; + } + } + + if (shouldRunLinters) { + const linterResults = await runLintCommands(linterCommands, argv, lintRunOptions); + printSilentLintOutputs(linterResults, argv); + lintExitCodes.push(...linterResults.map((result) => result.exitCode)); } return lintExitCodes.some((exitCode) => exitCode !== 0) ? 1 : 0; } +function runLintCommands( + commands: LintRunCommand[], + argv: LintCommandArgv, + options: Parameters[3] +): Promise { + return Promise.all(commands.map(({ command, project }) => runLintCommand(command, project, argv, options))); +} + function runLintCommand( command: string, project: Project, @@ -389,29 +438,41 @@ export function buildPoetryCommand( argv: Pick & Partial>, files?: string[] ): string { + return argv.fix || argv.format + ? `${buildPoetryFormatCommand(files)} && ${buildPoetryLintCommand(argv, files)}` + : buildPoetryLintCommand(argv, files); +} + +function buildPoetryFormatCommand(files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; - const commands = - argv.fix || argv.format - ? [ - buildShellCommand(['poetry', 'run', 'isort', '--profile', 'black', '--filter-files', ...targets]), - buildShellCommand(['poetry', 'run', 'black', ...targets]), - buildShellCommand(['poetry', 'run', 'flake8', ...(argv.quiet ? ['-q'] : []), ...targets]), - ] - : [buildShellCommand(['poetry', 'run', 'flake8', ...(argv.quiet ? ['-q'] : []), ...targets])]; - return commands.join(' && '); + return [ + buildShellCommand(['poetry', 'run', 'isort', '--profile', 'black', '--filter-files', ...targets]), + buildShellCommand(['poetry', 'run', 'black', ...targets]), + ].join(' && '); +} + +function buildPoetryLintCommand(argv: Partial>, files?: string[]): string { + const targets = files && files.length > 0 ? files : ['.']; + return buildShellCommand(['poetry', 'run', 'flake8', ...(argv.quiet ? ['-q'] : []), ...targets]); } export function buildDartCommand( argv: Pick & Partial>, files?: string[] ): string { + return argv.fix || argv.format + ? `${buildDartFormatCommand(files)} && ${buildDartLintCommand(files)}` + : buildDartLintCommand(files); +} + +function buildDartFormatCommand(files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; - const commands: string[] = []; - if (argv.fix || argv.format) { - commands.push(buildShellCommand(['dart', 'format', ...targets])); - } - commands.push(buildShellCommand(['dart', 'analyze', ...targets])); - return commands.join(' && '); + return buildShellCommand(['dart', 'format', ...targets]); +} + +function buildDartLintCommand(files?: string[]): string { + const targets = files && files.length > 0 ? files : ['.']; + return buildShellCommand(['dart', 'analyze', ...targets]); } export function buildPrettierArgs( diff --git a/packages/wb/src/commands/verifyCode.ts b/packages/wb/src/commands/verifyCode.ts index e25490a2..43dda6a5 100644 --- a/packages/wb/src/commands/verifyCode.ts +++ b/packages/wb/src/commands/verifyCode.ts @@ -60,27 +60,13 @@ async function verifyCode(project: Project, argv: VerifyCodeCommandArgv): Promis await runPackageCommand(`${packageManager} gen-code`, project, argv); } await runInProcessCommand( - 'format', - () => - lint({ - ...argv, - _: ['lint'], - format: true, - printAllOutput: true, - silent: true, - } as unknown as LintCommandArgv), - { - allowFailure: true, - silent: true, - } - ); - await runInProcessCommand( - 'lint-fix', + 'cleanup', () => lint({ ...argv, _: ['lint'], fix: true, + format: true, printAllOutput: true, quiet: true, silent: true, From 17c1417654f589e2de385b524a9740959a62c892 Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Sat, 2 May 2026 08:13:25 +0900 Subject: [PATCH 2/5] refactor: drop dead poetry/dart wrapper builders and merge descendant loop Co-authored-by: WillBooster (Claude Code) --- packages/wb/src/commands/lint.ts | 58 ++++++++------------------------ packages/wb/test/lint.test.ts | 16 +++++---- 2 files changed, 23 insertions(+), 51 deletions(-) diff --git a/packages/wb/src/commands/lint.ts b/packages/wb/src/commands/lint.ts index ec4a1dbd..d737832b 100644 --- a/packages/wb/src/commands/lint.ts +++ b/packages/wb/src/commands/lint.ts @@ -246,31 +246,19 @@ export async function lint(argv: LintCommandArgv): Promise { } } } else { - if (shouldRunLinters) { - for (const project of projects.descendants) { - if (project.packageJson.workspaces && !project.hasSourceCode) continue; - - const lintCommand = buildLintCommand(project, argv); - if (!lintCommand) continue; - - linterCommands.push({ command: lintCommand, project }); - } - } for (const project of projects.descendants) { - if (project.hasPoetryLock && shouldRunLinters) { - linterCommands.push({ command: buildPoetryLintCommand(argv), project }); - } - if (project.hasPubspecYaml && shouldRunLinters) { - linterCommands.push({ command: buildDartLintCommand(), project }); - } - if (project.hasOxfmt && shouldRunFormatters) { - formatterCommands.push({ command: buildOxfmtCommand(), project }); + if (shouldRunLinters && !(project.packageJson.workspaces && !project.hasSourceCode)) { + const lintCommand = buildLintCommand(project, argv); + if (lintCommand) linterCommands.push({ command: lintCommand, project }); } - if (project.hasPoetryLock && shouldRunFormatters) { - formatterCommands.push({ command: buildPoetryFormatCommand(), project }); + if (shouldRunLinters) { + if (project.hasPoetryLock) linterCommands.push({ command: buildPoetryLintCommand(argv), project }); + if (project.hasPubspecYaml) linterCommands.push({ command: buildDartLintCommand(), project }); } - if (project.hasPubspecYaml && shouldRunFormatters) { - formatterCommands.push({ command: buildDartFormatCommand(), project }); + if (shouldRunFormatters) { + if (project.hasOxfmt) formatterCommands.push({ command: buildOxfmtCommand(), project }); + if (project.hasPoetryLock) formatterCommands.push({ command: buildPoetryFormatCommand(), project }); + if (project.hasPubspecYaml) formatterCommands.push({ command: buildDartFormatCommand(), project }); } } } @@ -434,16 +422,7 @@ export function buildOxfmtCommand(files?: string[]): string { ]); } -export function buildPoetryCommand( - argv: Pick & Partial>, - files?: string[] -): string { - return argv.fix || argv.format - ? `${buildPoetryFormatCommand(files)} && ${buildPoetryLintCommand(argv, files)}` - : buildPoetryLintCommand(argv, files); -} - -function buildPoetryFormatCommand(files?: string[]): string { +export function buildPoetryFormatCommand(files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; return [ buildShellCommand(['poetry', 'run', 'isort', '--profile', 'black', '--filter-files', ...targets]), @@ -451,26 +430,17 @@ function buildPoetryFormatCommand(files?: string[]): string { ].join(' && '); } -function buildPoetryLintCommand(argv: Partial>, files?: string[]): string { +export function buildPoetryLintCommand(argv: Partial>, files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; return buildShellCommand(['poetry', 'run', 'flake8', ...(argv.quiet ? ['-q'] : []), ...targets]); } -export function buildDartCommand( - argv: Pick & Partial>, - files?: string[] -): string { - return argv.fix || argv.format - ? `${buildDartFormatCommand(files)} && ${buildDartLintCommand(files)}` - : buildDartLintCommand(files); -} - -function buildDartFormatCommand(files?: string[]): string { +export function buildDartFormatCommand(files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; return buildShellCommand(['dart', 'format', ...targets]); } -function buildDartLintCommand(files?: string[]): string { +export function buildDartLintCommand(files?: string[]): string { const targets = files && files.length > 0 ? files : ['.']; return buildShellCommand(['dart', 'analyze', ...targets]); } diff --git a/packages/wb/test/lint.test.ts b/packages/wb/test/lint.test.ts index 51c2503d..4091cb43 100644 --- a/packages/wb/test/lint.test.ts +++ b/packages/wb/test/lint.test.ts @@ -5,11 +5,13 @@ import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { - buildDartCommand, + buildDartFormatCommand, + buildDartLintCommand, buildExplicitFormatterArgs, buildLintCommand, buildOxfmtCommand, - buildPoetryCommand, + buildPoetryFormatCommand, + buildPoetryLintCommand, buildPrettierArgs, getExplicitLintTargets, getLintTargetFileKind, @@ -32,15 +34,15 @@ describe('lint', () => { }); it('builds poetry commands for explicit python files', () => { - expect(buildPoetryCommand({ fix: true, format: true }, ['/tmp/example.py'])).toBe( - 'poetry run isort --profile black --filter-files /tmp/example.py && poetry run black /tmp/example.py && poetry run flake8 /tmp/example.py' + expect(buildPoetryFormatCommand(['/tmp/example.py'])).toBe( + 'poetry run isort --profile black --filter-files /tmp/example.py && poetry run black /tmp/example.py' ); + expect(buildPoetryLintCommand({}, ['/tmp/example.py'])).toBe('poetry run flake8 /tmp/example.py'); }); it('builds dart commands for explicit dart files', () => { - expect(buildDartCommand({ fix: true, format: true }, ['/tmp/example.dart'])).toBe( - 'dart format /tmp/example.dart && dart analyze /tmp/example.dart' - ); + expect(buildDartFormatCommand(['/tmp/example.dart'])).toBe('dart format /tmp/example.dart'); + expect(buildDartLintCommand(['/tmp/example.dart'])).toBe('dart analyze /tmp/example.dart'); }); it('builds an oxfmt command for explicit files', () => { From aa09531ba34894a9e8b1de9d6cdba190e3dc8b1c Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Sat, 2 May 2026 08:16:09 +0900 Subject: [PATCH 3/5] refactor: merge explicit lint command loops Co-authored-by: WillBooster (Codex CLI) --- packages/wb/src/commands/lint.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/wb/src/commands/lint.ts b/packages/wb/src/commands/lint.ts index d737832b..edab9d6c 100644 --- a/packages/wb/src/commands/lint.ts +++ b/packages/wb/src/commands/lint.ts @@ -227,23 +227,27 @@ export async function lint(argv: LintCommandArgv): Promise { linterCommands.push({ command: lintCommand, project }); } - for (const [project, pythonFilePaths] of pythonFilePathsByProject) { + } + for (const [project, pythonFilePaths] of pythonFilePathsByProject) { + if (shouldRunLinters) { linterCommands.push({ command: buildPoetryLintCommand(argv, pythonFilePaths), project }); } - for (const [project, dartFilePaths] of dartFilePathsByProject) { + if (shouldRunFormatters) { + formatterCommands.push({ command: buildPoetryFormatCommand(pythonFilePaths), project }); + } + } + for (const [project, dartFilePaths] of dartFilePathsByProject) { + if (shouldRunLinters) { linterCommands.push({ command: buildDartLintCommand(dartFilePaths), project }); } + if (shouldRunFormatters) { + formatterCommands.push({ command: buildDartFormatCommand(dartFilePaths), project }); + } } if (shouldRunFormatters) { for (const [project, oxfmtFilePaths] of oxfmtFilePathsByProject) { formatterCommands.push({ command: buildOxfmtCommand(oxfmtFilePaths), project }); } - for (const [project, pythonFilePaths] of pythonFilePathsByProject) { - formatterCommands.push({ command: buildPoetryFormatCommand(pythonFilePaths), project }); - } - for (const [project, dartFilePaths] of dartFilePathsByProject) { - formatterCommands.push({ command: buildDartFormatCommand(dartFilePaths), project }); - } } } else { for (const project of projects.descendants) { From 31dd8473bf4a39e9b62ce274c19a39c3cee7bb1e Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Sat, 2 May 2026 08:22:52 +0900 Subject: [PATCH 4/5] refactor: combine descendant linter checks Co-authored-by: WillBooster (Codex CLI) --- packages/wb/src/commands/lint.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/wb/src/commands/lint.ts b/packages/wb/src/commands/lint.ts index edab9d6c..0db83329 100644 --- a/packages/wb/src/commands/lint.ts +++ b/packages/wb/src/commands/lint.ts @@ -251,11 +251,11 @@ export async function lint(argv: LintCommandArgv): Promise { } } else { for (const project of projects.descendants) { - if (shouldRunLinters && !(project.packageJson.workspaces && !project.hasSourceCode)) { - const lintCommand = buildLintCommand(project, argv); - if (lintCommand) linterCommands.push({ command: lintCommand, project }); - } if (shouldRunLinters) { + if (!(project.packageJson.workspaces && !project.hasSourceCode)) { + const lintCommand = buildLintCommand(project, argv); + if (lintCommand) linterCommands.push({ command: lintCommand, project }); + } if (project.hasPoetryLock) linterCommands.push({ command: buildPoetryLintCommand(argv), project }); if (project.hasPubspecYaml) linterCommands.push({ command: buildDartLintCommand(), project }); } From c5e67d9ea4d8b6d0d58983d3bd07ac30411149d8 Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Sat, 2 May 2026 08:29:27 +0900 Subject: [PATCH 5/5] refactor: simplify linter scheduling predicates Co-authored-by: WillBooster (Codex CLI) --- packages/wb/src/commands/lint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wb/src/commands/lint.ts b/packages/wb/src/commands/lint.ts index 0db83329..d20fe812 100644 --- a/packages/wb/src/commands/lint.ts +++ b/packages/wb/src/commands/lint.ts @@ -218,7 +218,7 @@ export async function lint(argv: LintCommandArgv): Promise { const linterCommands: LintRunCommand[] = []; const lintRunOptions = { exitIfFailed: false, forceColor: !argv.printAllOutput } as const; const shouldRunFormatters = Boolean(argv.format); - const shouldRunLinters = !argv.format || Boolean(argv.fix); + const shouldRunLinters = !argv.format || argv.fix; if (files.length > 0) { if (shouldRunLinters) { for (const [project, lintFilePaths] of lintFilePathsByProject) { @@ -252,7 +252,7 @@ export async function lint(argv: LintCommandArgv): Promise { } else { for (const project of projects.descendants) { if (shouldRunLinters) { - if (!(project.packageJson.workspaces && !project.hasSourceCode)) { + if (!project.packageJson.workspaces || project.hasSourceCode) { const lintCommand = buildLintCommand(project, argv); if (lintCommand) linterCommands.push({ command: lintCommand, project }); }