diff --git a/assets/usage-check.txt b/assets/usage-check.txt new file mode 100644 index 0000000..85acd61 --- /dev/null +++ b/assets/usage-check.txt @@ -0,0 +1,27 @@ +probitas check - Type-check scenario files + +Usage: probitas check [options] [paths...] + +Arguments: + [paths...] Scenario files or directories + Defaults to current directory + +Options: + -h, --help Show help message + --include Include pattern for file discovery + --exclude Exclude pattern for file discovery + --config Path to probitas config file + -v, --verbose Verbose output + -q, --quiet Suppress output + -d, --debug Debug output + +Note: + Runs `deno check --no-config` on discovered scenario files. + Uses includes/excludes from probitas config (same as run/list). + +Examples: + probitas check # Check all scenario files + probitas check api/ # Check scenarios in api directory + probitas check --include "e2e/**/*.probitas.ts" + +Documentation: https://jsr-probitas.github.io/documents/ diff --git a/assets/usage-fmt.txt b/assets/usage-fmt.txt new file mode 100644 index 0000000..a3627a3 --- /dev/null +++ b/assets/usage-fmt.txt @@ -0,0 +1,27 @@ +probitas fmt - Format scenario files + +Usage: probitas fmt [options] [paths...] + +Arguments: + [paths...] Scenario files or directories + Defaults to current directory + +Options: + -h, --help Show help message + --include Include pattern for file discovery + --exclude Exclude pattern for file discovery + --config Path to probitas config file + -v, --verbose Verbose output + -q, --quiet Suppress output + -d, --debug Debug output + +Note: + Runs `deno fmt --no-config` on discovered scenario files. + Uses includes/excludes from probitas config (same as run/list). + +Examples: + probitas fmt # Format all scenario files + probitas fmt api/ # Format scenarios in api directory + probitas fmt --include "e2e/**/*.probitas.ts" + +Documentation: https://jsr-probitas.github.io/documents/ diff --git a/assets/usage-lint.txt b/assets/usage-lint.txt new file mode 100644 index 0000000..faaec98 --- /dev/null +++ b/assets/usage-lint.txt @@ -0,0 +1,28 @@ +probitas lint - Lint scenario files + +Usage: probitas lint [options] [paths...] + +Arguments: + [paths...] Scenario files or directories + Defaults to current directory + +Options: + -h, --help Show help message + --include Include pattern for file discovery + --exclude Exclude pattern for file discovery + --config Path to probitas config file + -v, --verbose Verbose output + -q, --quiet Suppress output + -d, --debug Debug output + +Note: + Runs `deno lint --no-config` on discovered scenario files. + Uses includes/excludes from probitas config (same as run/list). + Automatically excludes rules: no-import-prefix, no-unversioned-import + +Examples: + probitas lint # Lint all scenario files + probitas lint api/ # Lint scenarios in api directory + probitas lint --include "e2e/**/*.probitas.ts" + +Documentation: https://jsr-probitas.github.io/documents/ diff --git a/assets/usage.txt b/assets/usage.txt index f08e888..3c7bc72 100644 --- a/assets/usage.txt +++ b/assets/usage.txt @@ -6,6 +6,9 @@ Commands: init Initialize a new probitas project run Run scenarios list List available scenarios + fmt Format scenario files (runs deno fmt) + lint Lint scenario files (runs deno lint) + check Type-check scenario files (runs deno check) Options: -h, --help Show help message diff --git a/src/commands/_deno.ts b/src/commands/_deno.ts new file mode 100644 index 0000000..e363120 --- /dev/null +++ b/src/commands/_deno.ts @@ -0,0 +1,135 @@ +/** + * Shared helper for Deno subcommand execution + * + * Used by fmt, lint, and check commands to run corresponding deno commands + * on discovered scenario files. + * + * @module + */ + +import { parseArgs } from "@std/cli"; +import { resolve } from "@std/path"; +import { configureLogging, getLogger, type LogLevel } from "@probitas/logger"; +import { discoverScenarioFiles } from "@probitas/discover"; +import { EXIT_CODE } from "../constants.ts"; +import { findProbitasConfigFile, loadConfig } from "../config.ts"; +import { createDiscoveryProgress } from "../progress.ts"; +import { readAsset } from "../utils.ts"; + +const logger = getLogger("probitas", "cli", "deno"); + +/** + * Options for running a Deno subcommand + */ +export interface DenoSubcommandOptions { + /** Asset file name for help text */ + usageAsset: string; + /** Extra arguments to pass to the deno command */ + extraArgs?: readonly string[]; +} + +/** + * Run a Deno subcommand on discovered scenario files + * + * @param subcommand - Deno subcommand to run (fmt, lint, check) + * @param args - Command-line arguments + * @param cwd - Current working directory + * @param options - Subcommand options + * @returns Exit code from the deno command + */ +export async function runDenoSubcommand( + subcommand: string, + args: string[], + cwd: string, + options: DenoSubcommandOptions, +): Promise { + try { + const parsed = parseArgs(args, { + string: ["config", "include", "exclude"], + boolean: ["help", "quiet", "verbose", "debug"], + collect: ["include", "exclude"], + alias: { + h: "help", + v: "verbose", + q: "quiet", + d: "debug", + }, + default: { + include: undefined, + exclude: undefined, + }, + }); + + if (parsed.help) { + const helpText = await readAsset(options.usageAsset); + console.log(helpText); + return EXIT_CODE.SUCCESS; + } + + // Determine log level + const logLevel: LogLevel = parsed.debug + ? "debug" + : parsed.verbose + ? "info" + : parsed.quiet + ? "fatal" + : "warning"; + + try { + await configureLogging(logLevel); + } catch { + // Ignore - logging may already be configured + } + + // Load probitas config (NOT deno.json) + const configPath = parsed.config ?? + await findProbitasConfigFile(cwd, { parentLookup: true }); + const config = configPath ? await loadConfig(configPath) : {}; + + // CLI > config + const includes = parsed.include ?? config?.includes; + const excludes = parsed.exclude ?? config?.excludes; + + // Discover scenario files + const paths = parsed._.map(String).map((p) => resolve(cwd, p)); + const discoveryProgress = parsed.quiet ? null : createDiscoveryProgress(); + const scenarioFiles = await discoverScenarioFiles( + paths.length ? paths : [cwd], + { includes, excludes, onProgress: discoveryProgress?.onProgress }, + ); + discoveryProgress?.complete(scenarioFiles.length); + + if (scenarioFiles.length === 0) { + console.warn("No scenario files found"); + return EXIT_CODE.NOT_FOUND; + } + + logger.debug(`Running deno ${subcommand}`, { + fileCount: scenarioFiles.length, + }); + + // Run deno command with --no-config + const denoArgs = [ + subcommand, + "--no-config", + ...(options.extraArgs ?? []), + ...scenarioFiles, + ]; + + const command = new Deno.Command("deno", { + args: denoArgs, + cwd, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + + const { code } = await command.output(); + return code; + } catch (err: unknown) { + logger.error(`deno ${subcommand} failed`, { error: err }); + const m = err instanceof Error ? err.message : String(err); + console.error(`Error: ${m}`); + return EXIT_CODE.FAILURE; + } +} diff --git a/src/commands/check.ts b/src/commands/check.ts new file mode 100644 index 0000000..209267d --- /dev/null +++ b/src/commands/check.ts @@ -0,0 +1,25 @@ +/** + * Type-check command for Probitas CLI + * + * Runs `deno check --no-config` on discovered scenario files. + * + * @module + */ + +import { runDenoSubcommand } from "./_deno.ts"; + +/** + * Run the check command + * + * @param args - Command-line arguments + * @param cwd - Current working directory + * @returns Exit code + */ +export async function checkCommand( + args: string[], + cwd: string, +): Promise { + return await runDenoSubcommand("check", args, cwd, { + usageAsset: "usage-check.txt", + }); +} diff --git a/src/commands/fmt.ts b/src/commands/fmt.ts new file mode 100644 index 0000000..edc9b78 --- /dev/null +++ b/src/commands/fmt.ts @@ -0,0 +1,25 @@ +/** + * Format command for Probitas CLI + * + * Runs `deno fmt --no-config` on discovered scenario files. + * + * @module + */ + +import { runDenoSubcommand } from "./_deno.ts"; + +/** + * Run the fmt command + * + * @param args - Command-line arguments + * @param cwd - Current working directory + * @returns Exit code + */ +export async function fmtCommand( + args: string[], + cwd: string, +): Promise { + return await runDenoSubcommand("fmt", args, cwd, { + usageAsset: "usage-fmt.txt", + }); +} diff --git a/src/commands/lint.ts b/src/commands/lint.ts new file mode 100644 index 0000000..386817e --- /dev/null +++ b/src/commands/lint.ts @@ -0,0 +1,27 @@ +/** + * Lint command for Probitas CLI + * + * Runs `deno lint --no-config` on discovered scenario files. + * Automatically excludes rules that conflict with scenario imports. + * + * @module + */ + +import { runDenoSubcommand } from "./_deno.ts"; + +/** + * Run the lint command + * + * @param args - Command-line arguments + * @param cwd - Current working directory + * @returns Exit code + */ +export async function lintCommand( + args: string[], + cwd: string, +): Promise { + return await runDenoSubcommand("lint", args, cwd, { + usageAsset: "usage-lint.txt", + extraArgs: ["--rules-exclude=no-import-prefix,no-unversioned-import"], + }); +} diff --git a/src/commands/mod.ts b/src/commands/mod.ts index 6f7a8bd..f433415 100644 --- a/src/commands/mod.ts +++ b/src/commands/mod.ts @@ -4,6 +4,9 @@ * @module */ +export { checkCommand } from "./check.ts"; +export { fmtCommand } from "./fmt.ts"; export { initCommand } from "./init.ts"; +export { lintCommand } from "./lint.ts"; export { listCommand } from "./list.ts"; export { runCommand } from "./run.ts"; diff --git a/src/main.ts b/src/main.ts index fbd913d..5d119b8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,10 @@ import { parseArgs } from "@std/cli"; import { getLogger } from "@probitas/logger"; import { EXIT_CODE } from "./constants.ts"; +import { checkCommand } from "./commands/check.ts"; +import { fmtCommand } from "./commands/fmt.ts"; import { initCommand } from "./commands/init.ts"; +import { lintCommand } from "./commands/lint.ts"; import { listCommand } from "./commands/list.ts"; import { runCommand } from "./commands/run.ts"; import { getVersionInfo, readAsset } from "./utils.ts"; @@ -78,6 +81,15 @@ export async function main(args: string[]): Promise { case "list": return await listCommand(commandArgs, cwd); + case "fmt": + return await fmtCommand(commandArgs, cwd); + + case "lint": + return await lintCommand(commandArgs, cwd); + + case "check": + return await checkCommand(commandArgs, cwd); + default: console.warn(`Unknown command: ${command}`); console.warn("Run 'probitas --help' for usage information");