diff --git a/scripts/features/lib/args.mjs b/scripts/features/lib/args.mjs new file mode 100644 index 0000000000..61c2a589a0 --- /dev/null +++ b/scripts/features/lib/args.mjs @@ -0,0 +1,110 @@ +/** + * Args parser for feature-triage CLI. + * Precedence: CLI flag > env var > default. + */ + +const DEFAULTS = { + quarantineDays: 14, + overrideThumbs: 5, + overrideCommenters: 3, + staleNeedsDays: 30, + staleDeferDays: 90, + ideiaDir: "_ideia", + changelog: "CHANGELOG.md", + output: null, + dryRun: false, + verbose: false, + onlyIssues: [], +}; + +const ENV_MAP = { + quarantineDays: "FEATURE_QUARANTINE_DAYS", + overrideThumbs: "FEATURE_OVERRIDE_THUMBS", + overrideCommenters: "FEATURE_OVERRIDE_COMMENTERS", + staleNeedsDays: "FEATURE_STALE_NEEDS_DAYS", + staleDeferDays: "FEATURE_STALE_DEFER_DAYS", +}; + +function takeNext(argv, i) { + if (i + 1 >= argv.length) { + throw new Error(`${argv[i]} requires a value`); + } + return argv[i + 1]; +} + +export function parseArgs(argv, env = process.env) { + const out = { ...DEFAULTS, owner: null, repo: null }; + + for (const [key, envKey] of Object.entries(ENV_MAP)) { + if (env[envKey] !== undefined) { + const n = Number(env[envKey]); + if (Number.isFinite(n)) out[key] = n; + } + } + + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + switch (a) { + case "--owner": + out.owner = takeNext(argv, i); + i++; + break; + case "--repo": + out.repo = takeNext(argv, i); + i++; + break; + case "--quarantine-days": + out.quarantineDays = Number(takeNext(argv, i)); + i++; + break; + case "--override-thumbs": + out.overrideThumbs = Number(takeNext(argv, i)); + i++; + break; + case "--override-commenters": + out.overrideCommenters = Number(takeNext(argv, i)); + i++; + break; + case "--stale-needs-days": + out.staleNeedsDays = Number(takeNext(argv, i)); + i++; + break; + case "--stale-defer-days": + out.staleDeferDays = Number(takeNext(argv, i)); + i++; + break; + case "--ideia-dir": + out.ideiaDir = takeNext(argv, i); + i++; + break; + case "--changelog": + out.changelog = takeNext(argv, i); + i++; + break; + case "--output": + out.output = takeNext(argv, i); + i++; + break; + case "--dry-run": + out.dryRun = true; + break; + case "--verbose": + out.verbose = true; + break; + case "--only-issues": + out.onlyIssues = takeNext(argv, i) + .split(",") + .map((s) => Number(s.trim())) + .filter(Number.isFinite); + i++; + break; + default: + throw new Error(`Unknown arg: ${a}`); + } + } + + if (!out.owner) throw new Error("--owner is required"); + if (!out.repo) throw new Error("--repo is required"); + + return out; +} diff --git a/tests/unit/feature-triage/args.test.mjs b/tests/unit/feature-triage/args.test.mjs new file mode 100644 index 0000000000..ddf903cea5 --- /dev/null +++ b/tests/unit/feature-triage/args.test.mjs @@ -0,0 +1,48 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { parseArgs } from "../../../scripts/features/lib/args.mjs"; + +describe("parseArgs", () => { + it("requires --owner and --repo", () => { + assert.throws(() => parseArgs([]), /--owner is required/); + assert.throws(() => parseArgs(["--owner", "x"]), /--repo is required/); + }); + + it("applies defaults when args/env missing", () => { + const args = parseArgs(["--owner", "diegosouzapw", "--repo", "OmniRoute"], {}); + assert.equal(args.quarantineDays, 14); + assert.equal(args.overrideThumbs, 5); + assert.equal(args.overrideCommenters, 3); + assert.equal(args.staleNeedsDays, 30); + assert.equal(args.staleDeferDays, 90); + assert.equal(args.ideiaDir, "_ideia"); + assert.equal(args.changelog, "CHANGELOG.md"); + assert.equal(args.output, null); + assert.equal(args.dryRun, false); + assert.equal(args.verbose, false); + assert.deepEqual(args.onlyIssues, []); + }); + + it("CLI arg takes precedence over env var", () => { + const args = parseArgs(["--owner", "x", "--repo", "y", "--quarantine-days", "7"], { + FEATURE_QUARANTINE_DAYS: "21", + }); + assert.equal(args.quarantineDays, 7); + }); + + it("env var used when CLI arg missing", () => { + const args = parseArgs(["--owner", "x", "--repo", "y"], { FEATURE_QUARANTINE_DAYS: "21" }); + assert.equal(args.quarantineDays, 21); + }); + + it("parses --only-issues as comma-separated numbers", () => { + const args = parseArgs(["--owner", "x", "--repo", "y", "--only-issues", "1046,1041,980"], {}); + assert.deepEqual(args.onlyIssues, [1046, 1041, 980]); + }); + + it("parses --dry-run and --verbose as booleans", () => { + const args = parseArgs(["--owner", "x", "--repo", "y", "--dry-run", "--verbose"], {}); + assert.equal(args.dryRun, true); + assert.equal(args.verbose, true); + }); +});