|
| 1 | +/** |
| 2 | + * Shared helper for Deno subcommand execution |
| 3 | + * |
| 4 | + * Used by fmt, lint, and check commands to run corresponding deno commands |
| 5 | + * on discovered scenario files. |
| 6 | + * |
| 7 | + * @module |
| 8 | + */ |
| 9 | + |
| 10 | +import { parseArgs } from "@std/cli"; |
| 11 | +import { resolve } from "@std/path"; |
| 12 | +import { configureLogging, getLogger, type LogLevel } from "@probitas/logger"; |
| 13 | +import { discoverScenarioFiles } from "@probitas/discover"; |
| 14 | +import { EXIT_CODE } from "../constants.ts"; |
| 15 | +import { findProbitasConfigFile, loadConfig } from "../config.ts"; |
| 16 | +import { createDiscoveryProgress } from "../progress.ts"; |
| 17 | +import { readAsset } from "../utils.ts"; |
| 18 | + |
| 19 | +const logger = getLogger("probitas", "cli", "deno"); |
| 20 | + |
| 21 | +/** |
| 22 | + * Options for running a Deno subcommand |
| 23 | + */ |
| 24 | +export interface DenoSubcommandOptions { |
| 25 | + /** Asset file name for help text */ |
| 26 | + usageAsset: string; |
| 27 | + /** Extra arguments to pass to the deno command */ |
| 28 | + extraArgs?: readonly string[]; |
| 29 | +} |
| 30 | + |
| 31 | +/** |
| 32 | + * Run a Deno subcommand on discovered scenario files |
| 33 | + * |
| 34 | + * @param subcommand - Deno subcommand to run (fmt, lint, check) |
| 35 | + * @param args - Command-line arguments |
| 36 | + * @param cwd - Current working directory |
| 37 | + * @param options - Subcommand options |
| 38 | + * @returns Exit code from the deno command |
| 39 | + */ |
| 40 | +export async function runDenoSubcommand( |
| 41 | + subcommand: string, |
| 42 | + args: string[], |
| 43 | + cwd: string, |
| 44 | + options: DenoSubcommandOptions, |
| 45 | +): Promise<number> { |
| 46 | + try { |
| 47 | + const parsed = parseArgs(args, { |
| 48 | + string: ["config", "include", "exclude"], |
| 49 | + boolean: ["help", "quiet", "verbose", "debug"], |
| 50 | + collect: ["include", "exclude"], |
| 51 | + alias: { |
| 52 | + h: "help", |
| 53 | + v: "verbose", |
| 54 | + q: "quiet", |
| 55 | + d: "debug", |
| 56 | + }, |
| 57 | + default: { |
| 58 | + include: undefined, |
| 59 | + exclude: undefined, |
| 60 | + }, |
| 61 | + }); |
| 62 | + |
| 63 | + if (parsed.help) { |
| 64 | + const helpText = await readAsset(options.usageAsset); |
| 65 | + console.log(helpText); |
| 66 | + return EXIT_CODE.SUCCESS; |
| 67 | + } |
| 68 | + |
| 69 | + // Determine log level |
| 70 | + const logLevel: LogLevel = parsed.debug |
| 71 | + ? "debug" |
| 72 | + : parsed.verbose |
| 73 | + ? "info" |
| 74 | + : parsed.quiet |
| 75 | + ? "fatal" |
| 76 | + : "warning"; |
| 77 | + |
| 78 | + try { |
| 79 | + await configureLogging(logLevel); |
| 80 | + } catch { |
| 81 | + // Ignore - logging may already be configured |
| 82 | + } |
| 83 | + |
| 84 | + // Load probitas config (NOT deno.json) |
| 85 | + const configPath = parsed.config ?? |
| 86 | + await findProbitasConfigFile(cwd, { parentLookup: true }); |
| 87 | + const config = configPath ? await loadConfig(configPath) : {}; |
| 88 | + |
| 89 | + // CLI > config |
| 90 | + const includes = parsed.include ?? config?.includes; |
| 91 | + const excludes = parsed.exclude ?? config?.excludes; |
| 92 | + |
| 93 | + // Discover scenario files |
| 94 | + const paths = parsed._.map(String).map((p) => resolve(cwd, p)); |
| 95 | + const discoveryProgress = parsed.quiet ? null : createDiscoveryProgress(); |
| 96 | + const scenarioFiles = await discoverScenarioFiles( |
| 97 | + paths.length ? paths : [cwd], |
| 98 | + { includes, excludes, onProgress: discoveryProgress?.onProgress }, |
| 99 | + ); |
| 100 | + discoveryProgress?.complete(scenarioFiles.length); |
| 101 | + |
| 102 | + if (scenarioFiles.length === 0) { |
| 103 | + console.warn("No scenario files found"); |
| 104 | + return EXIT_CODE.NOT_FOUND; |
| 105 | + } |
| 106 | + |
| 107 | + logger.debug(`Running deno ${subcommand}`, { |
| 108 | + fileCount: scenarioFiles.length, |
| 109 | + }); |
| 110 | + |
| 111 | + // Run deno command with --no-config |
| 112 | + const denoArgs = [ |
| 113 | + subcommand, |
| 114 | + "--no-config", |
| 115 | + ...(options.extraArgs ?? []), |
| 116 | + ...scenarioFiles, |
| 117 | + ]; |
| 118 | + |
| 119 | + const command = new Deno.Command("deno", { |
| 120 | + args: denoArgs, |
| 121 | + cwd, |
| 122 | + stdin: "inherit", |
| 123 | + stdout: "inherit", |
| 124 | + stderr: "inherit", |
| 125 | + }); |
| 126 | + |
| 127 | + const { code } = await command.output(); |
| 128 | + return code; |
| 129 | + } catch (err: unknown) { |
| 130 | + logger.error(`deno ${subcommand} failed`, { error: err }); |
| 131 | + const m = err instanceof Error ? err.message : String(err); |
| 132 | + console.error(`Error: ${m}`); |
| 133 | + return EXIT_CODE.FAILURE; |
| 134 | + } |
| 135 | +} |
0 commit comments