@nickmeriano/task 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +129 -0
  2. package/dist/asks.test.js +6 -0
  3. package/dist/asks.test.js.map +1 -1
  4. package/dist/claim-io.d.ts.map +1 -1
  5. package/dist/claim-io.js +4 -3
  6. package/dist/claim-io.js.map +1 -1
  7. package/dist/claim.d.ts +99 -1
  8. package/dist/claim.d.ts.map +1 -1
  9. package/dist/claim.js +271 -6
  10. package/dist/claim.js.map +1 -1
  11. package/dist/claim.test.d.ts +8 -0
  12. package/dist/claim.test.d.ts.map +1 -1
  13. package/dist/claim.test.js +211 -2
  14. package/dist/claim.test.js.map +1 -1
  15. package/dist/cli.js +193 -7
  16. package/dist/cli.js.map +1 -1
  17. package/dist/export.d.ts +132 -0
  18. package/dist/export.d.ts.map +1 -0
  19. package/dist/export.js +199 -0
  20. package/dist/export.js.map +1 -0
  21. package/dist/export.test.d.ts +12 -0
  22. package/dist/export.test.d.ts.map +1 -0
  23. package/dist/export.test.js +185 -0
  24. package/dist/export.test.js.map +1 -0
  25. package/dist/server.d.ts +6 -0
  26. package/dist/server.d.ts.map +1 -1
  27. package/dist/server.js +12 -3
  28. package/dist/server.js.map +1 -1
  29. package/dist/trailers.d.ts +51 -0
  30. package/dist/trailers.d.ts.map +1 -0
  31. package/dist/trailers.js +32 -0
  32. package/dist/trailers.js.map +1 -0
  33. package/package.json +1 -1
  34. package/skill/SKILL.md +34 -1
  35. package/src/asks.test.ts +15 -0
  36. package/src/claim-io.ts +12 -3
  37. package/src/claim.test.ts +286 -2
  38. package/src/claim.ts +375 -6
  39. package/src/cli.ts +208 -7
  40. package/src/export.test.ts +254 -0
  41. package/src/export.ts +276 -0
  42. package/src/server.ts +12 -3
  43. package/src/trailers.ts +65 -0
  44. package/ui/dist/assets/{index-CoKCUYic.css → index-eHsqltgs.css} +1 -1
  45. package/ui/dist/assets/{index-BjsorZOU.js → index-mJmm4sWq.js} +53 -53
  46. package/ui/dist/index.html +3 -3
@@ -0,0 +1,132 @@
1
+ /**
2
+ * `task export` — the board as a directory of files.
3
+ *
4
+ * The third deployment of this app (after `task serve` and the hosted board)
5
+ * is a static host: the same prebuilt UI the package already ships for serve,
6
+ * plus a `snapshot.json` this module writes out of the local `.task/`. There
7
+ * is no server behind it, so the app reads its answers out of that file —
8
+ * `ui/src/snapshot.ts` is the reader, and this is the writer.
9
+ *
10
+ * The whole correctness question is one sentence: **the snapshot must answer
11
+ * what `routeApi` answers**. Every field below is sourced from the same store
12
+ * call the matching `/api/…` route makes, so a board rendered from a file and
13
+ * a board rendered from the server differ only in how stale they are.
14
+ *
15
+ * Split in two on purpose: `buildSnapshot` is the shape (no writes, so it is
16
+ * testable without a built UI, and the UI's own test can round-trip it through
17
+ * `parseSnapshot`), `writeExport` is the directory.
18
+ */
19
+ import { type Comment, type Goal, type Status, type Task } from "./types.ts";
20
+ /**
21
+ * The version the reader accepts, restated from `ui/src/snapshot.ts`. The UI
22
+ * is built separately from this NodeNext package, so the contract is mirrored
23
+ * rather than cross-imported — the same precedent `ui/src/api.ts` sets for
24
+ * `types.ts`. `ui/src/deployment.test.ts` imports `buildSnapshot` and feeds
25
+ * the result to the real `parseSnapshot`, which is what keeps the two honest.
26
+ */
27
+ export declare const SNAPSHOT_VERSION = 2;
28
+ /** The file's name in the output directory, matching `SNAPSHOT_PATH`. */
29
+ export declare const SNAPSHOT_FILE = "snapshot.json";
30
+ /** Where an export goes when `--out` doesn't say. */
31
+ export declare const DEFAULT_OUT_DIR = "task-export";
32
+ /**
33
+ * The assets are built with relative refs (`base: "./"` in ui/vite.config.ts)
34
+ * because one build ships three ways and can't know where it will live. The
35
+ * `<base href>` each deployment stamps into index.html is what completes them:
36
+ * the browser resolves `./assets/…` — and the app resolves its routes, API and
37
+ * snapshot fetch — against it instead of the page's own URL, which is what
38
+ * keeps deep links working under any mount point.
39
+ */
40
+ /** `board` / `/board` / `board/` → `/board/`; `""` and `/` → `/`. */
41
+ export declare function normalizeBase(base: string): string;
42
+ /**
43
+ * Stamp the deployment's mount point into the built page. The built
44
+ * index.html always has exactly one `<head>` for this to anchor on; refusing
45
+ * a page without one beats silently shipping a board that 404s its assets.
46
+ */
47
+ export declare function injectBase(html: string, base: string): string;
48
+ export interface SnapshotMeta {
49
+ /** The commit exported from (`git rev-parse HEAD`), or null outside a repo. */
50
+ commit: string | null;
51
+ /** When the export ran, ISO-8601 — the other half of the staleness banner. */
52
+ builtAt: string;
53
+ }
54
+ /** What `/api/boards` answers, per board. */
55
+ export interface SnapshotBoardInfo {
56
+ id: string;
57
+ name: string;
58
+ prefix: string;
59
+ }
60
+ /** What `/api/project` answers. `readOnly` is the reader's call, not ours. */
61
+ export interface SnapshotProject {
62
+ name: string;
63
+ prefix: string;
64
+ statuses: readonly Status[];
65
+ author: string;
66
+ }
67
+ /** One board's baked answers, in the shapes the serve API returns. */
68
+ export interface SnapshotBoard {
69
+ project: SnapshotProject;
70
+ tasks: Task[];
71
+ goals: Goal[];
72
+ /** Task key to its comments, oldest first; absent means none. */
73
+ comments: Record<string, Comment[]>;
74
+ }
75
+ export interface Snapshot {
76
+ version: number;
77
+ meta: SnapshotMeta;
78
+ boards: SnapshotBoardInfo[];
79
+ data: Record<string, SnapshotBoard>;
80
+ }
81
+ export interface BuildSnapshotOptions {
82
+ /** Overridable so tests get a fixed build time. */
83
+ builtAt?: string;
84
+ }
85
+ /**
86
+ * The commit this export is of.
87
+ *
88
+ * Every way this can fail — git not installed, not a repository, a repo with
89
+ * no commits yet — is the same non-answer, so they all collapse to null. The
90
+ * reader's type already says `string | null` and its banner degrades to
91
+ * "snapshot, built …", which is the honest thing to show.
92
+ */
93
+ export declare function exportedCommit(cwd: string): string | null;
94
+ /**
95
+ * Bake every board at or below `serveRoot` into one snapshot.
96
+ *
97
+ * Anchoring matches `cmdServe` exactly (`findRoot` ?? cwd, then `findBoards`),
98
+ * so "export what serve would serve" is true by construction — one board in a
99
+ * package, every nested board at a monorepo root, `.` first either way.
100
+ */
101
+ export declare function buildSnapshot(serveRoot: string, options?: BuildSnapshotOptions): Snapshot;
102
+ export interface WriteExportOptions {
103
+ serveRoot: string;
104
+ outDir: string;
105
+ /** The built SPA to copy. Injectable so tests never need a vite build. */
106
+ uiDir: string;
107
+ /** Write into a directory that isn't a previous export. Never deletes. */
108
+ force?: boolean;
109
+ /** URL path the export will be served under (default `/`) — see `injectBase`. */
110
+ base?: string;
111
+ builtAt?: string;
112
+ }
113
+ export interface ExportSummary {
114
+ outDir: string;
115
+ base: string;
116
+ boards: number;
117
+ tasks: number;
118
+ commit: string | null;
119
+ builtAt: string;
120
+ }
121
+ /**
122
+ * Write the assets and the snapshot into `outDir`.
123
+ *
124
+ * Refresh is the interesting case. Re-exporting over a previous export
125
+ * replaces the UI's own entries and `snapshot.json` and leaves everything else
126
+ * alone — notably a `functions/` directory, so a deployed live-proxy function
127
+ * survives a re-export. Anything else in a non-empty directory is refused
128
+ * rather than overwritten: `--out .` against a repo is the one genuinely
129
+ * destructive mistake available here.
130
+ */
131
+ export declare function writeExport(options: WriteExportOptions): ExportSummary;
132
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAA;AAEjC,yEAAyE;AACzE,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,qDAAqD;AACrD,eAAO,MAAM,eAAe,gBAAgB,CAAA;AAK5C;;;;;;;GAOG;AAEH,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;CACf;AAED,sEAAsE;AACtE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,eAAe,CAAA;IACxB,KAAK,EAAE,IAAI,EAAE,CAAA;IACb,KAAK,EAAE,IAAI,EAAE,CAAA;IACb,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,QAAQ,CAmD7F;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,aAAa,CAyCtE"}
package/dist/export.js ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * `task export` — the board as a directory of files.
3
+ *
4
+ * The third deployment of this app (after `task serve` and the hosted board)
5
+ * is a static host: the same prebuilt UI the package already ships for serve,
6
+ * plus a `snapshot.json` this module writes out of the local `.task/`. There
7
+ * is no server behind it, so the app reads its answers out of that file —
8
+ * `ui/src/snapshot.ts` is the reader, and this is the writer.
9
+ *
10
+ * The whole correctness question is one sentence: **the snapshot must answer
11
+ * what `routeApi` answers**. Every field below is sourced from the same store
12
+ * call the matching `/api/…` route makes, so a board rendered from a file and
13
+ * a board rendered from the server differ only in how stale they are.
14
+ *
15
+ * Split in two on purpose: `buildSnapshot` is the shape (no writes, so it is
16
+ * testable without a built UI, and the UI's own test can round-trip it through
17
+ * `parseSnapshot`), `writeExport` is the directory.
18
+ */
19
+ import { execFileSync } from "node:child_process";
20
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
21
+ import { join, resolve } from "node:path";
22
+ import { resolveAuthor } from "./author.js";
23
+ import { openBoard } from "./file-store.js";
24
+ import { findBoards } from "./store.js";
25
+ import { STATUSES } from "./types.js";
26
+ /**
27
+ * The version the reader accepts, restated from `ui/src/snapshot.ts`. The UI
28
+ * is built separately from this NodeNext package, so the contract is mirrored
29
+ * rather than cross-imported — the same precedent `ui/src/api.ts` sets for
30
+ * `types.ts`. `ui/src/deployment.test.ts` imports `buildSnapshot` and feeds
31
+ * the result to the real `parseSnapshot`, which is what keeps the two honest.
32
+ */
33
+ export const SNAPSHOT_VERSION = 2;
34
+ /** The file's name in the output directory, matching `SNAPSHOT_PATH`. */
35
+ export const SNAPSHOT_FILE = "snapshot.json";
36
+ /** Where an export goes when `--out` doesn't say. */
37
+ export const DEFAULT_OUT_DIR = "task-export";
38
+ /** The sentence `serveStatic` prints for the same missing directory. */
39
+ const NO_UI_BUILD = "UI build not found — run the package build (vite build ui).";
40
+ /**
41
+ * The assets are built with relative refs (`base: "./"` in ui/vite.config.ts)
42
+ * because one build ships three ways and can't know where it will live. The
43
+ * `<base href>` each deployment stamps into index.html is what completes them:
44
+ * the browser resolves `./assets/…` — and the app resolves its routes, API and
45
+ * snapshot fetch — against it instead of the page's own URL, which is what
46
+ * keeps deep links working under any mount point.
47
+ */
48
+ /** `board` / `/board` / `board/` → `/board/`; `""` and `/` → `/`. */
49
+ export function normalizeBase(base) {
50
+ let path = base.trim();
51
+ if (path === "" || path === "/")
52
+ return "/";
53
+ if (!path.startsWith("/"))
54
+ path = `/${path}`;
55
+ if (!path.endsWith("/"))
56
+ path = `${path}/`;
57
+ if (path.includes("//") || /[\s<>"']/.test(path)) {
58
+ throw new Error(`--base must be a plain URL path like /board/ — got ${JSON.stringify(base)}`);
59
+ }
60
+ return path;
61
+ }
62
+ /**
63
+ * Stamp the deployment's mount point into the built page. The built
64
+ * index.html always has exactly one `<head>` for this to anchor on; refusing
65
+ * a page without one beats silently shipping a board that 404s its assets.
66
+ */
67
+ export function injectBase(html, base) {
68
+ if (!html.includes("<head>")) {
69
+ throw new Error("index.html has no <head> to carry the <base> tag — is this the built UI?");
70
+ }
71
+ return html.replace("<head>", `<head><base href="${normalizeBase(base)}" />`);
72
+ }
73
+ /**
74
+ * The commit this export is of.
75
+ *
76
+ * Every way this can fail — git not installed, not a repository, a repo with
77
+ * no commits yet — is the same non-answer, so they all collapse to null. The
78
+ * reader's type already says `string | null` and its banner degrades to
79
+ * "snapshot, built …", which is the honest thing to show.
80
+ */
81
+ export function exportedCommit(cwd) {
82
+ try {
83
+ const out = execFileSync("git", ["rev-parse", "HEAD"], {
84
+ cwd,
85
+ encoding: "utf8",
86
+ timeout: 2000,
87
+ stdio: ["ignore", "pipe", "ignore"],
88
+ });
89
+ return out.trim() || null;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ /**
96
+ * Bake every board at or below `serveRoot` into one snapshot.
97
+ *
98
+ * Anchoring matches `cmdServe` exactly (`findRoot` ?? cwd, then `findBoards`),
99
+ * so "export what serve would serve" is true by construction — one board in a
100
+ * package, every nested board at a monorepo root, `.` first either way.
101
+ */
102
+ export function buildSnapshot(serveRoot, options = {}) {
103
+ const refs = findBoards(serveRoot);
104
+ if (refs.length === 0) {
105
+ throw new Error(`no .task directory found at or below ${serveRoot} — run \`task init\` first`);
106
+ }
107
+ // Resolved once, server-side, exactly as `createTaskServer` does it: all the
108
+ // boards live in one checkout, so one resolution covers them.
109
+ const author = resolveAuthor(undefined, serveRoot).name;
110
+ const boards = [];
111
+ const data = {};
112
+ for (const ref of refs) {
113
+ const store = openBoard(ref.root);
114
+ try {
115
+ boards.push({ id: ref.id, name: store.config.name, prefix: store.config.prefix });
116
+ const counts = store.commentCounts();
117
+ const tasks = store.list().map((task) => ({
118
+ ...task,
119
+ commentCount: counts.get(task.key) ?? 0,
120
+ }));
121
+ const comments = {};
122
+ for (const task of tasks) {
123
+ const list = store.comments(task.key);
124
+ // Omitted rather than empty: the reader defaults a missing key to [],
125
+ // and a snapshot is a file people open.
126
+ if (list.length > 0)
127
+ comments[task.key] = list;
128
+ }
129
+ data[ref.id] = {
130
+ project: { name: store.config.name, prefix: store.config.prefix, statuses: STATUSES, author },
131
+ tasks,
132
+ // Archived goals ride along flagged, as `/api/goals` sends them, so an
133
+ // old chip on a task can still resolve a title.
134
+ goals: [...store.goals(), ...store.goals(true)],
135
+ comments,
136
+ };
137
+ }
138
+ finally {
139
+ store.close();
140
+ }
141
+ }
142
+ return {
143
+ version: SNAPSHOT_VERSION,
144
+ meta: {
145
+ commit: exportedCommit(serveRoot),
146
+ builtAt: options.builtAt ?? new Date().toISOString(),
147
+ },
148
+ boards,
149
+ data,
150
+ };
151
+ }
152
+ /**
153
+ * Write the assets and the snapshot into `outDir`.
154
+ *
155
+ * Refresh is the interesting case. Re-exporting over a previous export
156
+ * replaces the UI's own entries and `snapshot.json` and leaves everything else
157
+ * alone — notably a `functions/` directory, so a deployed live-proxy function
158
+ * survives a re-export. Anything else in a non-empty directory is refused
159
+ * rather than overwritten: `--out .` against a repo is the one genuinely
160
+ * destructive mistake available here.
161
+ */
162
+ export function writeExport(options) {
163
+ const { serveRoot, outDir, uiDir, force = false } = options;
164
+ const base = normalizeBase(options.base ?? "/");
165
+ if (!existsSync(uiDir))
166
+ throw new Error(NO_UI_BUILD);
167
+ // Exporting onto the assets it copies from would clear them first and then
168
+ // copy an empty directory — the one way this can destroy the package itself.
169
+ if (resolve(outDir) === resolve(uiDir)) {
170
+ throw new Error(`${outDir} is the UI build itself — export somewhere else`);
171
+ }
172
+ const snapshot = buildSnapshot(serveRoot, { builtAt: options.builtAt });
173
+ const existing = existsSync(outDir) ? readdirSync(outDir) : null;
174
+ if (existing && existing.length > 0 && !existing.includes(SNAPSHOT_FILE) && !force) {
175
+ throw new Error(`${outDir} is not empty and isn't a previous export — pass --force to write into it anyway`);
176
+ }
177
+ mkdirSync(outDir, { recursive: true });
178
+ // Clear only what this export is about to write: a stale `assets/` full of
179
+ // last build's hashed bundles is dead weight, and everything else in there
180
+ // belongs to whoever put it there.
181
+ for (const entry of readdirSync(uiDir)) {
182
+ rmSync(join(outDir, entry), { recursive: true, force: true });
183
+ }
184
+ cpSync(uiDir, outDir, { recursive: true });
185
+ const indexPath = join(outDir, "index.html");
186
+ writeFileSync(indexPath, injectBase(readFileSync(indexPath, "utf8"), base));
187
+ // Pretty-printed: this lands in CI artifacts and gets diffed, and at this
188
+ // size the whitespace costs nothing.
189
+ writeFileSync(join(outDir, SNAPSHOT_FILE), `${JSON.stringify(snapshot, null, 2)}\n`);
190
+ return {
191
+ outDir,
192
+ base,
193
+ boards: snapshot.boards.length,
194
+ tasks: Object.values(snapshot.data).reduce((n, board) => n + board.tasks.length, 0),
195
+ commit: snapshot.meta.commit,
196
+ builtAt: snapshot.meta.builtAt,
197
+ };
198
+ }
199
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `task export` writes a file another program reads, so what's checked here is
3
+ * the contract rather than the code: every field comes from the same store
4
+ * call the matching `/api/…` route makes, and the directory it lands in
5
+ * refreshes without eating what someone else put there.
6
+ *
7
+ * The round trip through the actual reader (`parseSnapshot` + `snapshotSource`)
8
+ * lives in `ui/src/deployment.test.ts` — it's the UI's tsconfig that can see
9
+ * both sides.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=export.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.test.d.ts","sourceRoot":"","sources":["../src/export.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * `task export` writes a file another program reads, so what's checked here is
3
+ * the contract rather than the code: every field comes from the same store
4
+ * call the matching `/api/…` route makes, and the directory it lands in
5
+ * refreshes without eating what someone else put there.
6
+ *
7
+ * The round trip through the actual reader (`parseSnapshot` + `snapshotSource`)
8
+ * lives in `ui/src/deployment.test.ts` — it's the UI's tsconfig that can see
9
+ * both sides.
10
+ */
11
+ import assert from "node:assert/strict";
12
+ import { test } from "node:test";
13
+ import { execFileSync } from "node:child_process";
14
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { join } from "node:path";
17
+ import { initProject, openBoard } from "./file-store.js";
18
+ import { buildSnapshot, writeExport, SNAPSHOT_FILE, SNAPSHOT_VERSION } from "./export.js";
19
+ import { STATUSES } from "./types.js";
20
+ function tempRoot() {
21
+ const dir = mkdtempSync(join(tmpdir(), "task-export-"));
22
+ process.on("exit", () => rmSync(dir, { recursive: true, force: true }));
23
+ return dir;
24
+ }
25
+ /** A fake `ui/dist` — the shape of one is all the copy step cares about. */
26
+ function fakeUi() {
27
+ const dir = tempRoot();
28
+ writeFileSync(join(dir, "index.html"), "<!doctype html><html><head><title>board</title></head></html>");
29
+ mkdirSync(join(dir, "assets"));
30
+ writeFileSync(join(dir, "assets", "index-abc123.js"), "// bundle");
31
+ return dir;
32
+ }
33
+ /** A single-board repo with enough in it to be worth exporting. */
34
+ function board() {
35
+ const root = tempRoot();
36
+ const store = initProject(root, { name: "acme", prefix: "ACM" });
37
+ const talked = store.create({ title: "Ship the export", status: "todo" });
38
+ store.addComment(talked.key, "on it", "claude");
39
+ store.addComment(talked.key, "still on it", "claude");
40
+ store.create({ title: "Quiet ticket", status: "backlog" });
41
+ const finished = store.create({ title: "Already shipped", status: "done" });
42
+ store.archive(finished.key);
43
+ store.createGoal({ title: "Launch", slug: "launch" });
44
+ store.createGoal({ title: "Old plan", slug: "old-plan" });
45
+ store.archiveGoal("old-plan");
46
+ store.close();
47
+ return root;
48
+ }
49
+ test("the snapshot answers what the serve API answers", () => {
50
+ const root = board();
51
+ const store = openBoard(root);
52
+ const snapshot = buildSnapshot(root);
53
+ const data = snapshot.data["."];
54
+ // /api/project — field for field, minus readOnly (the reader forces it).
55
+ assert.equal(data.project.name, store.config.name);
56
+ assert.equal(data.project.prefix, store.config.prefix);
57
+ assert.deepEqual(data.project.statuses, STATUSES);
58
+ assert.equal(typeof data.project.author, "string");
59
+ assert.ok(!("readOnly" in data.project));
60
+ // /api/tasks — the same list, in the same order, with the same counts.
61
+ const tasks = store.list();
62
+ const counts = store.commentCounts();
63
+ assert.deepEqual(data.tasks, tasks.map((t) => ({ ...t, commentCount: counts.get(t.key) ?? 0 })));
64
+ // /api/goals — live first, archived riding along flagged.
65
+ assert.deepEqual(data.goals, [...store.goals(), ...store.goals(true)]);
66
+ assert.ok(data.goals.some((g) => g.archived));
67
+ // /api/tasks/:n — comments per task, oldest first, empty ones omitted.
68
+ const talkative = tasks.find((t) => t.title === "Ship the export");
69
+ const quiet = tasks.find((t) => t.title === "Quiet ticket");
70
+ assert.deepEqual(data.comments[talkative.key], store.comments(talkative.key));
71
+ assert.equal(data.comments[quiet.key], undefined);
72
+ store.close();
73
+ });
74
+ test("an archived ticket is off the board, in the export too", () => {
75
+ const root = board();
76
+ const snapshot = buildSnapshot(root);
77
+ assert.ok(!snapshot.data["."].tasks.some((t) => t.title === "Already shipped"));
78
+ });
79
+ test("a monorepo root exports every board, keyed by what /boards says", () => {
80
+ const root = tempRoot();
81
+ initProject(root, { name: "acme", prefix: "ACM" }).close();
82
+ const nested = join(root, "projects", "widget");
83
+ mkdirSync(nested, { recursive: true });
84
+ initProject(nested, { name: "widget", prefix: "WID" }).close();
85
+ const snapshot = buildSnapshot(root);
86
+ assert.deepEqual(snapshot.boards.map((b) => b.id), [".", "projects/widget"]);
87
+ assert.deepEqual(snapshot.boards[1], { id: "projects/widget", name: "widget", prefix: "WID" });
88
+ // Every board in the list has an entry, and nothing else does.
89
+ assert.deepEqual(Object.keys(snapshot.data).sort(), [".", "projects/widget"]);
90
+ });
91
+ test("a board with nothing above or below it is not an export", () => {
92
+ assert.throws(() => buildSnapshot(tempRoot()), /run `task init`/);
93
+ });
94
+ test("the commit is the HEAD it was exported from, or null outside a repo", () => {
95
+ const loose = board();
96
+ assert.equal(buildSnapshot(loose).meta.commit, null);
97
+ const repo = board();
98
+ const git = (...args) => execFileSync("git", args, { cwd: repo, stdio: ["ignore", "pipe", "ignore"] });
99
+ git("init", "-q");
100
+ git("config", "user.email", "t@example.com");
101
+ git("config", "user.name", "Test");
102
+ git("add", "-A");
103
+ git("commit", "-qm", "board");
104
+ const head = git("rev-parse", "HEAD").toString().trim();
105
+ assert.equal(buildSnapshot(repo).meta.commit, head);
106
+ });
107
+ test("the export is the assets plus the snapshot", () => {
108
+ const root = board();
109
+ const out = join(tempRoot(), "board");
110
+ const summary = writeExport({ serveRoot: root, outDir: out, uiDir: fakeUi() });
111
+ assert.ok(existsSync(join(out, "index.html")));
112
+ assert.ok(existsSync(join(out, "assets", "index-abc123.js")));
113
+ const snapshot = JSON.parse(readFileSync(join(out, SNAPSHOT_FILE), "utf8"));
114
+ assert.equal(snapshot.version, SNAPSHOT_VERSION);
115
+ assert.equal(snapshot.boards.length, 1);
116
+ assert.deepEqual(summary, {
117
+ outDir: out,
118
+ base: "/",
119
+ boards: 1,
120
+ tasks: snapshot.data["."].tasks.length,
121
+ commit: null,
122
+ builtAt: snapshot.meta.builtAt,
123
+ });
124
+ });
125
+ test("exporting without a built UI says which build to run", () => {
126
+ assert.throws(() => writeExport({
127
+ serveRoot: board(),
128
+ outDir: join(tempRoot(), "board"),
129
+ uiDir: join(tempRoot(), "never-built"),
130
+ }), /run the package build/);
131
+ });
132
+ test("exporting onto the UI build is refused, not a way to delete it", () => {
133
+ const ui = fakeUi();
134
+ assert.throws(() => writeExport({ serveRoot: board(), outDir: ui, uiDir: ui, force: true }), /the UI build itself/);
135
+ assert.ok(existsSync(join(ui, "index.html")));
136
+ });
137
+ test("re-exporting refreshes in place and leaves a deployed function alone", () => {
138
+ const root = board();
139
+ const out = join(tempRoot(), "board");
140
+ const ui = fakeUi();
141
+ writeExport({ serveRoot: root, outDir: out, uiDir: ui });
142
+ // TAS-33's room: something the export didn't put there and must not remove.
143
+ mkdirSync(join(out, "functions"));
144
+ writeFileSync(join(out, "functions", "handler.js"), "// live proxy");
145
+ // A stale bundle from the previous build, which it *should* remove.
146
+ writeFileSync(join(out, "assets", "index-old.js"), "// last time");
147
+ const store = openBoard(root);
148
+ store.create({ title: "Added since the last export", status: "todo" });
149
+ store.close();
150
+ writeExport({ serveRoot: root, outDir: out, uiDir: ui });
151
+ const snapshot = JSON.parse(readFileSync(join(out, SNAPSHOT_FILE), "utf8"));
152
+ assert.ok(snapshot.data["."].tasks.some((t) => t.title === "Added since the last export"));
153
+ assert.equal(readFileSync(join(out, "functions", "handler.js"), "utf8"), "// live proxy");
154
+ assert.ok(!existsSync(join(out, "assets", "index-old.js")));
155
+ });
156
+ test("a directory that isn't a previous export is refused, not overwritten", () => {
157
+ const root = board();
158
+ const out = join(tempRoot(), "not-an-export");
159
+ mkdirSync(out);
160
+ writeFileSync(join(out, "thesis.md"), "years of work");
161
+ assert.throws(() => writeExport({ serveRoot: root, outDir: out, uiDir: fakeUi() }), /--force/);
162
+ assert.equal(readFileSync(join(out, "thesis.md"), "utf8"), "years of work");
163
+ // --force writes in; it still only ever overwrites what it writes.
164
+ writeExport({ serveRoot: root, outDir: out, uiDir: fakeUi(), force: true });
165
+ assert.ok(existsSync(join(out, SNAPSHOT_FILE)));
166
+ assert.equal(readFileSync(join(out, "thesis.md"), "utf8"), "years of work");
167
+ });
168
+ // ── The mount point, stamped into the page ──────────────────────────────────
169
+ test("the exported page carries its mount point as a <base> tag", () => {
170
+ const root = board();
171
+ const atRoot = join(tempRoot(), "root");
172
+ writeExport({ serveRoot: root, outDir: atRoot, uiDir: fakeUi() });
173
+ assert.match(readFileSync(join(atRoot, "index.html"), "utf8"), /<head><base href="\/" \/>/);
174
+ const atSubpath = join(tempRoot(), "sub");
175
+ const summary = writeExport({ serveRoot: root, outDir: atSubpath, uiDir: fakeUi(), base: "board" });
176
+ assert.equal(summary.base, "/board/");
177
+ assert.match(readFileSync(join(atSubpath, "index.html"), "utf8"), /<head><base href="\/board\/" \/>/);
178
+ });
179
+ test("a base that isn't a plain URL path is refused", () => {
180
+ const root = board();
181
+ for (const bad of ["//cdn.example.com", "/a b/", '/"/']) {
182
+ assert.throws(() => writeExport({ serveRoot: root, outDir: join(tempRoot(), "out"), uiDir: fakeUi(), base: bad }), /--base/, bad);
183
+ }
184
+ });
185
+ //# sourceMappingURL=export.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.test.js","sourceRoot":"","sources":["../src/export.test.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  import { type Server } from "node:http";
2
+ /**
3
+ * The prebuilt SPA, shipped inside the package next to dist/. Exported because
4
+ * `task export` copies the same assets it serves — one definition of where the
5
+ * UI lives, or the two deployments drift.
6
+ */
7
+ export declare const UI_DIR: string;
2
8
  /**
3
9
  * `task serve` — one plain node:http server for the static UI, the JSON API,
4
10
  * and an SSE stream that pings whenever anything writes to a `.task/` (the UI
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAA;AA+FhG;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAmb1D"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAA;AAahG;;;;GAIG;AACH,eAAO,MAAM,MAAM,QAAwD,CAAA;AAkF3E;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAub1D"}
package/dist/server.js CHANGED
@@ -4,13 +4,18 @@ import { extname, join, normalize } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { resolveAuthor } from "./author.js";
6
6
  import { addAskRouted, deleteAskRouted, setAskResolvedRouted } from "./claim-io.js";
7
+ import { injectBase } from "./export.js";
7
8
  import { openBoard } from "./file-store.js";
8
9
  import { GitServe, GitServeError } from "./git-serve.js";
9
10
  import { buildInbox } from "./inbox.js";
10
11
  import { boardConfig, findBoards } from "./store.js";
11
12
  import { STATUSES, isStatus } from "./types.js";
12
- /** The prebuilt SPA, shipped inside the package next to dist/. */
13
- const UI_DIR = fileURLToPath(new URL("../ui/dist", import.meta.url));
13
+ /**
14
+ * The prebuilt SPA, shipped inside the package next to dist/. Exported because
15
+ * `task export` copies the same assets it serves — one definition of where the
16
+ * UI lives, or the two deployments drift.
17
+ */
18
+ export const UI_DIR = fileURLToPath(new URL("../ui/dist", import.meta.url));
14
19
  const MIME = {
15
20
  ".html": "text/html; charset=utf-8",
16
21
  ".js": "text/javascript",
@@ -513,7 +518,11 @@ export function createTaskServer(serveRoot) {
513
518
  "Content-Type": MIME[extname(file)] ?? "application/octet-stream",
514
519
  "Cache-Control": "no-cache",
515
520
  });
516
- res.end(readFileSync(file));
521
+ // The build's asset refs are relative (ui/vite.config.ts) and the page —
522
+ // this one included, when it's the SPA fallback for a deep link — needs a
523
+ // `<base>` to resolve them against. serve always mounts the app at the
524
+ // origin root; `task export --base` is where any other answer lives.
525
+ res.end(file.endsWith("index.html") ? injectBase(readFileSync(file, "utf8"), "/") : readFileSync(file));
517
526
  }
518
527
  return server;
519
528
  }