Fix duplicate ids, validate CLI input, harden load/save
- add() uses max id + 1 so ids are never reused after rm - done/rm reject missing, non-numeric and unknown ids; add rejects empty text - load() gives a readable error on bad JSON or non-list data - save() writes a temp file and renames it - CLI checks the command before loading and no longer relies on process.exit for ls - Add regression tests for all of the above Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f0ba80a7bd
commit
ba17376132
4 files changed
+166
-26
No files matched your search
+38
-21
@@ -1,28 +1,45 @@
|
||||
#!/usr/bin/env node
|
||||
import { load, save, add, complete, remove, format } from "./todo.js";
|
||||
import { load, save, add, has, complete, remove, format } from "./todo.js";
|
||||
|
||||
const FILE = process.env.TODO_FILE ?? "todos.json";
|
||||
const [cmd, ...args] = process.argv.slice(2);
|
||||
let todos = load(FILE);
|
||||
const USAGE = "Usage: todo [ls | add <text> | done <id> | rm <id>]";
|
||||
const [cmd = "ls", ...args] = process.argv.slice(2);
|
||||
|
||||
switch (cmd) {
|
||||
case "add":
|
||||
todos = add(todos, args.join(" "));
|
||||
break;
|
||||
case "done":
|
||||
todos = complete(todos, Number(args[0]));
|
||||
break;
|
||||
case "rm":
|
||||
todos = remove(todos, Number(args[0]));
|
||||
break;
|
||||
case "ls":
|
||||
case undefined:
|
||||
console.log(format(todos));
|
||||
process.exit(0);
|
||||
default:
|
||||
console.error(`Unknown command: ${cmd}\nUsage: todo [ls | add <text> | done <id> | rm <id>]`);
|
||||
process.exit(1);
|
||||
function fail(message) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
save(FILE, todos);
|
||||
function parseId(todos, arg) {
|
||||
const id = Number(arg);
|
||||
if (arg === undefined || !Number.isInteger(id)) fail(`Invalid id: ${arg ?? "(missing)"}\n${USAGE}`);
|
||||
if (!has(todos, id)) fail(`No todo with id ${id}`);
|
||||
return id;
|
||||
}
|
||||
|
||||
if (!["ls", "add", "done", "rm"].includes(cmd)) fail(`Unknown command: ${cmd}\n${USAGE}`);
|
||||
|
||||
let todos;
|
||||
try {
|
||||
todos = load(FILE);
|
||||
} catch (err) {
|
||||
fail(err.message);
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case "add": {
|
||||
const text = args.join(" ").trim();
|
||||
if (!text) fail(`Missing todo text\n${USAGE}`);
|
||||
todos = add(todos, text);
|
||||
break;
|
||||
}
|
||||
case "done":
|
||||
todos = complete(todos, parseId(todos, args[0]));
|
||||
break;
|
||||
case "rm":
|
||||
todos = remove(todos, parseId(todos, args[0]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmd !== "ls") save(FILE, todos);
|
||||
console.log(format(todos));
|
||||
Reference in new issue
Block a user