← 中文资源

简体中文版本

任务跟踪 CLI — 可运行 Deno 项目

把待办保存在本地 JSON 文件的 Deno 任务跟踪 CLI,包含完整源码、构建说明和验证核心操作的测试。

阅读英文原文

项目说明

这是一个可直接运行的 Deno CLI,演示命令解析、文件持久化和基本任务状态管理。

运行方法

安装 Deno 后执行 deno task test;使用 deno run -A main.ts add "Write docs" 添加任务,list 查看,done <id> 完成。数据保存在当前目录的 JSON 文件中。

验证方式

测试覆盖添加、列出、完成和无效 id,断言输出与文件内容。注意并发写入可能覆盖数据,生产使用应加锁或改用数据库。CLI 应返回明确退出码,并在文件损坏时给出可恢复的错误而不是静默重置。

验收清单

完成「任务跟踪 CLI — 可运行 Deno 项目」后,按节点或操作步骤逐项自查:准备可复现的本地环境,执行文档中的命令,记录输出与失败信息,并确认权限、空数据和失败恢复路径都有对应证据。

代码示例

# No install needed for Deno — just allow read/write permissions
deno run --allow-read=tasks.json --allow-write=tasks.json cli.ts add "Buy groceries"
deno run --allow-read=tasks.json --allow-write=tasks.json cli.ts list
deno run --allow-read=tasks.json --allow-write=tasks.json cli.ts complete 1
deno run --allow-read=tasks.json --allow-write=tasks.json cli.ts delete 1
task-tracker/
├── cli.ts           # Entry point: parse args and dispatch commands
├── store.ts         # JSON file persistence layer
├── types.ts         # Task type definition
├── tasks.json       # Auto-created data file
└── store.test.ts    # Unit tests for store operations
// cli.ts
import { loadTasks, saveTasks, addTask, completeTask, deleteTask, listTasks } from './store.ts';

const [command, ...args] = Deno.args;

try {
  switch (command) {
    case 'add': {
      const title = args.join(' ');
      if (!title) throw new Error('Usage: add <task title>');
      const tasks = loadTasks();
      const newTask = addTask(tasks, title);
      saveTasks(tasks);
      console.log(`Task #${newTask.id} added: "${newTask.title}"`);
      break;
    }
    case 'list': {
      const tasks = loadTasks();
      listTasks(tasks).forEach(t => {
        const status = t.done ? '✓' : '○';
        console.log(`${status} #${t.id} ${t.title}`);
      });
      break;
    }
    case 'complete': {
      const id = Number(args[0]);
      const tasks = loadTasks();
      completeTask(tasks, id);
      saveTasks(tasks);
      console.log(`Task #${id} marked done`);
      break;
    }
    case 'delete': {
      const id = Number(args[0]);
      const tasks = loadTasks();
      deleteTask(tasks, id);
      saveTasks(tasks);
      console.log(`Task #${id} deleted`);
      break;
    }
    default:
      console.log('Usage: cli.ts <add|list|complete|delete> [args]');
  }
} catch (err) {
  console.error(`Error: ${err.message}`);
  Deno.exit(1);
}
// store.ts
export interface Task {
  id: number;
  title: string;
  done: boolean;
  createdAt: string;
}

export function loadTasks(): Task[] {
  try {
    const raw = Deno.readTextFileSync('tasks.json');
    return JSON.parse(raw);
  } catch {
    return []; // File doesn't exist yet → start fresh
  }
}

export function saveTasks(tasks: Task[]): void {
  Deno.writeTextFileSync('tasks.json', JSON.stringify(tasks, null, 2));
}

export function addTask(tasks: Task[], title: string): Task {
  const task: Task = {
    id: tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1,
    title,
    done: false,
    createdAt: new Date().toISOString(),
  };
  tasks.push(task);
  return task;
}

export function completeTask(tasks: Task[], id: number): void {
  const task = tasks.find(t => t.id === id);
  if (!task) throw new Error(`Task #${id} not found`);
  task.done = true;
}

export function deleteTask(tasks: Task[], id: number): void {
  const idx = tasks.findIndex(t => t.id === id);
  if (idx === -1) throw new Error(`Task #${id} not found`);
  tasks.splice(idx, 1);
}

export function listTasks(tasks: Task[]): Task[] {
  return tasks;
}
deno test
Check file:///.../store.test.ts
ok | 4 passed (4) | 0 failed (0)
     Duration 1.23s