/** * Sync TickTick into the private `tasks` schema. Idempotent; rerun after a new export. * * bun scripts/ticktick-import.ts [--dry-run] [--csv backup.csv] ~/.cache/ticktick-export/all.json * * all.json comes from scripts/ticktick-export.ts (Open API). --csv adds the tasks from a * TickTick backup (Settings → Backup & Import) that the API no longer returns (closed lists, * old won't-do tasks); rows the API also has are matched and skipped (see mergeCsv). * Links listed in data/tasks/ticktick-merges.json (plus exact title matches, see * syncTickTick) attach a TickTick task to an existing task instead of duplicating it. * Uses DB_URL (the app role on prod, personal_dev on dev). */ import { readFile } from 'node:fs/promises'; import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import { mergeCsv, parseTickTickCsv, syncTickTick, type TtExport, type TtMerge } from '../src/lib/server/ticktick'; const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); const csvAt = args.indexOf('--csv'); const csvPath = csvAt >= 0 ? args[csvAt + 1] : undefined; const files = args.filter((a, i) => !a.startsWith('--') && (csvAt < 0 || i !== csvAt + 1)); if (files.length !== 1 || (csvAt >= 0 && !csvPath)) { console.error( 'usage: bun scripts/ticktick-import.ts [--dry-run] [--csv backup.csv] ' ); process.exit(2); } let data = JSON.parse(await readFile(files[0], 'utf8')) as TtExport; if (csvPath) { const merged = mergeCsv(data, parseTickTickCsv(await readFile(csvPath, 'utf8'))); data = merged.data; console.log(JSON.stringify({ csv: merged.report })); } const merges = JSON.parse( await readFile(new URL('../data/tasks/ticktick-merges.json', import.meta.url), 'utf8') ) as TtMerge[]; if (!process.env.DB_URL) throw new Error('DB_URL is required'); const client = postgres(process.env.DB_URL, { max: 1, onnotice: () => {} }); class DryRun extends Error {} try { const db = drizzle({ client }); try { const report = await db.transaction(async (tx) => { const r = await syncTickTick(tx, data, { merges }); if (dryRun) { console.log(JSON.stringify(r, null, 2)); throw new DryRun(); } return r; }); console.log(JSON.stringify(report, null, 2)); } catch (e) { if (!(e instanceof DryRun)) throw e; console.log('(dry run, rolled back)'); } } finally { await client.end(); }