mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
fix(cli): validate positive numeric options (#49)
This commit is contained in:
parent
aa3fdba277
commit
fbf4d76372
3 changed files with 37 additions and 8 deletions
|
|
@ -10,6 +10,7 @@ import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators"
|
||||||
import { getConfigValue, readConfig, setConfigValue, writeConfig } from "./config.js";
|
import { getConfigValue, readConfig, setConfigValue, writeConfig } from "./config.js";
|
||||||
import { boards, tasks } from "./fixtures.js";
|
import { boards, tasks } from "./fixtures.js";
|
||||||
import { print, type OutputFormat } from "./format.js";
|
import { print, type OutputFormat } from "./format.js";
|
||||||
|
import { parsePositiveInteger } from "./numeric-options.js";
|
||||||
import { exportOpenSpecSummary, importOpenSpec, writeOpenSpecChange } from "./openspec.js";
|
import { exportOpenSpecSummary, importOpenSpec, writeOpenSpecChange } from "./openspec.js";
|
||||||
import { defaultPluginRegistry } from "./registry.js";
|
import { defaultPluginRegistry } from "./registry.js";
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ program
|
||||||
program
|
program
|
||||||
.command("read")
|
.command("read")
|
||||||
.argument("<board>", "Board path")
|
.argument("<board>", "Board path")
|
||||||
.option("--limit <limit>", "Number of posts", "20")
|
.option("--limit <limit>", "Number of posts", parsePositiveInteger, 20)
|
||||||
.option("--format <format>", "table, json, or markdown", "table")
|
.option("--format <format>", "table, json, or markdown", "table")
|
||||||
.description("Read a board feed.")
|
.description("Read a board feed.")
|
||||||
.action((board, options) => {
|
.action((board, options) => {
|
||||||
|
|
@ -97,7 +98,7 @@ program
|
||||||
{ type: "TASK", board, title: "QA checkout flow", meta: "25 USDC" },
|
{ type: "TASK", board, title: "QA checkout flow", meta: "25 USDC" },
|
||||||
{ type: "POST", board, title: "New agent plugin idea", meta: "4 replies" },
|
{ type: "POST", board, title: "New agent plugin idea", meta: "4 replies" },
|
||||||
{ type: "RUN", board, title: "qa-agent completed task_123", meta: "completed" }
|
{ type: "RUN", board, title: "qa-agent completed task_123", meta: "completed" }
|
||||||
].slice(0, Number(options.limit)),
|
].slice(0, options.limit),
|
||||||
options.format as OutputFormat
|
options.format as OutputFormat
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -306,8 +307,8 @@ feeds
|
||||||
.argument("<keyword>", "Keyword, phrase, or homepage URL")
|
.argument("<keyword>", "Keyword, phrase, or homepage URL")
|
||||||
.option("--type <type>", "Feed kind or all", "all")
|
.option("--type <type>", "Feed kind or all", "all")
|
||||||
.option("--format <format>", "json, opml, rss, atom, or json-feed", "json")
|
.option("--format <format>", "json, opml, rss, atom, or json-feed", "json")
|
||||||
.option("--limit <limit>", "Maximum results", "25")
|
.option("--limit <limit>", "Maximum results", parsePositiveInteger, 25)
|
||||||
.option("--freshness-days <days>", "Freshness window for callers that need it")
|
.option("--freshness-days <days>", "Freshness window for callers that need it", parsePositiveInteger)
|
||||||
.option("--include-dead-feeds", "Include feeds that fail validation")
|
.option("--include-dead-feeds", "Include feeds that fail validation")
|
||||||
.option("--include-unvalidated", "Return provider candidates without validation")
|
.option("--include-unvalidated", "Return provider candidates without validation")
|
||||||
.option("--providers <providers>", "Comma-separated provider ids")
|
.option("--providers <providers>", "Comma-separated provider ids")
|
||||||
|
|
@ -316,8 +317,8 @@ feeds
|
||||||
const response = await discoverFeeds({
|
const response = await discoverFeeds({
|
||||||
q: keyword,
|
q: keyword,
|
||||||
type: options.type as FeedKind | "all",
|
type: options.type as FeedKind | "all",
|
||||||
limit: Number(options.limit),
|
limit: options.limit,
|
||||||
freshnessDays: options.freshnessDays ? Number(options.freshnessDays) : undefined,
|
freshnessDays: options.freshnessDays,
|
||||||
includeDeadFeeds: Boolean(options.includeDeadFeeds),
|
includeDeadFeeds: Boolean(options.includeDeadFeeds),
|
||||||
includeUnvalidated: Boolean(options.includeUnvalidated),
|
includeUnvalidated: Boolean(options.includeUnvalidated),
|
||||||
providers: splitOption(options.providers)
|
providers: splitOption(options.providers)
|
||||||
|
|
@ -362,10 +363,10 @@ feeds
|
||||||
feeds
|
feeds
|
||||||
.command("export-opml")
|
.command("export-opml")
|
||||||
.argument("<keyword>", "Keyword or phrase")
|
.argument("<keyword>", "Keyword or phrase")
|
||||||
.option("--limit <limit>", "Maximum results", "100")
|
.option("--limit <limit>", "Maximum results", parsePositiveInteger, 100)
|
||||||
.description("Discover feeds and print OPML.")
|
.description("Discover feeds and print OPML.")
|
||||||
.action(async (keyword, options) => {
|
.action(async (keyword, options) => {
|
||||||
const response = await discoverFeeds({ q: keyword, limit: Number(options.limit) });
|
const response = await discoverFeeds({ q: keyword, limit: options.limit });
|
||||||
console.log(renderDiscoveryOutput(response, "opml"));
|
console.log(renderDiscoveryOutput(response, "opml"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
19
packages/cli/src/numeric-options.test.ts
Normal file
19
packages/cli/src/numeric-options.test.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { parsePositiveInteger } from "./numeric-options.js";
|
||||||
|
|
||||||
|
describe("parsePositiveInteger", () => {
|
||||||
|
it.each([
|
||||||
|
["1", 1],
|
||||||
|
["25", 25],
|
||||||
|
["100", 100]
|
||||||
|
])("parses %s", (value, expected) => {
|
||||||
|
expect(parsePositiveInteger(value)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(["", " ", "nope", "0", "-1", "1.5", "Infinity", "NaN"])(
|
||||||
|
"rejects invalid input: %s",
|
||||||
|
(value) => {
|
||||||
|
expect(() => parsePositiveInteger(value)).toThrow("must be a positive integer");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
9
packages/cli/src/numeric-options.ts
Normal file
9
packages/cli/src/numeric-options.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { InvalidArgumentError } from "commander";
|
||||||
|
|
||||||
|
export function parsePositiveInteger(value: string): number {
|
||||||
|
const parsed = Number(value);
|
||||||
|
if (value.trim() === "" || !Number.isSafeInteger(parsed) || parsed < 1) {
|
||||||
|
throw new InvalidArgumentError("must be a positive integer");
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue