@nickmeriano/task 0.11.0 → 0.12.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 (73) hide show
  1. package/README.md +175 -1
  2. package/dist/board/github.d.ts +68 -0
  3. package/dist/board/github.d.ts.map +1 -0
  4. package/dist/board/github.js +112 -0
  5. package/dist/board/github.js.map +1 -0
  6. package/dist/board/handler.d.ts +50 -0
  7. package/dist/board/handler.d.ts.map +1 -0
  8. package/dist/board/handler.js +183 -0
  9. package/dist/board/handler.js.map +1 -0
  10. package/dist/board/handler.test.d.ts +11 -0
  11. package/dist/board/handler.test.d.ts.map +1 -0
  12. package/dist/board/handler.test.js +229 -0
  13. package/dist/board/handler.test.js.map +1 -0
  14. package/dist/board/pages-function.d.ts +23 -0
  15. package/dist/board/pages-function.d.ts.map +1 -0
  16. package/dist/board/pages-function.js +34 -0
  17. package/dist/board/pages-function.js.map +1 -0
  18. package/dist/board/source.d.ts +118 -0
  19. package/dist/board/source.d.ts.map +1 -0
  20. package/dist/board/source.js +333 -0
  21. package/dist/board/source.js.map +1 -0
  22. package/dist/board/source.test.d.ts +12 -0
  23. package/dist/board/source.test.d.ts.map +1 -0
  24. package/dist/board/source.test.js +165 -0
  25. package/dist/board/source.test.js.map +1 -0
  26. package/dist/board/tar.d.ts +28 -0
  27. package/dist/board/tar.d.ts.map +1 -0
  28. package/dist/board/tar.js +188 -0
  29. package/dist/board/tar.js.map +1 -0
  30. package/dist/board/tar.test.d.ts +9 -0
  31. package/dist/board/tar.test.d.ts.map +1 -0
  32. package/dist/board/tar.test.js +110 -0
  33. package/dist/board/tar.test.js.map +1 -0
  34. package/dist/cli.js +37 -2
  35. package/dist/cli.js.map +1 -1
  36. package/dist/export.d.ts +31 -0
  37. package/dist/export.d.ts.map +1 -1
  38. package/dist/export.js +61 -1
  39. package/dist/export.js.map +1 -1
  40. package/dist/export.test.js +80 -1
  41. package/dist/export.test.js.map +1 -1
  42. package/dist/functions/board.js +709 -0
  43. package/dist/git-serve.d.ts +14 -1
  44. package/dist/git-serve.d.ts.map +1 -1
  45. package/dist/git-serve.js +30 -2
  46. package/dist/git-serve.js.map +1 -1
  47. package/dist/git-serve.test.d.ts +1 -0
  48. package/dist/git-serve.test.d.ts.map +1 -1
  49. package/dist/git-serve.test.js +36 -1
  50. package/dist/git-serve.test.js.map +1 -1
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +6 -0
  54. package/dist/index.js.map +1 -1
  55. package/package.json +3 -3
  56. package/skill/SKILL.md +10 -3
  57. package/src/board/github.ts +151 -0
  58. package/src/board/handler.test.ts +276 -0
  59. package/src/board/handler.ts +228 -0
  60. package/src/board/pages-function.ts +42 -0
  61. package/src/board/source.test.ts +203 -0
  62. package/src/board/source.ts +422 -0
  63. package/src/board/tar.test.ts +128 -0
  64. package/src/board/tar.ts +199 -0
  65. package/src/cli.ts +36 -2
  66. package/src/export.test.ts +108 -1
  67. package/src/export.ts +79 -1
  68. package/src/git-serve.test.ts +41 -1
  69. package/src/git-serve.ts +30 -2
  70. package/src/index.ts +10 -0
  71. package/ui/dist/assets/index-B8M_DaOt.js +229 -0
  72. package/ui/dist/index.html +1 -1
  73. package/ui/dist/assets/index-mJmm4sWq.js +0 -229
@@ -0,0 +1,188 @@
1
+ /**
2
+ * A minimal streaming tar reader — just enough to walk a GitHub repository
3
+ * tarball. One pass, bounded memory: every entry's *path* is recorded, but
4
+ * bytes are kept only for the paths the caller's `keep` predicate selects;
5
+ * everything else is discarded chunk by chunk as it streams through, so a big
6
+ * repository costs no more memory than its boards.
7
+ *
8
+ * Understands what GitHub's tarballs actually contain: ustar headers with the
9
+ * split `prefix` field, a leading `pax_global_header`, and pax extended
10
+ * headers (`x`) or GNU longname entries (`L`) for paths longer than the
11
+ * header's 100 bytes. Checksums are not verified — the bytes come off a TLS
12
+ * connection from GitHub, not a tape from 1988.
13
+ */
14
+ export class TarError extends Error {
15
+ }
16
+ const BLOCK = 512;
17
+ /** Reads exact byte counts off a stream, buffering only what's unconsumed. */
18
+ class ByteReader {
19
+ reader;
20
+ chunks = [];
21
+ have = 0;
22
+ done = false;
23
+ constructor(stream) {
24
+ this.reader = stream.getReader();
25
+ }
26
+ async pull() {
27
+ if (this.done)
28
+ return false;
29
+ const { done, value } = await this.reader.read();
30
+ if (done) {
31
+ this.done = true;
32
+ return false;
33
+ }
34
+ if (value && value.length > 0) {
35
+ this.chunks.push(value);
36
+ this.have += value.length;
37
+ }
38
+ return true;
39
+ }
40
+ /** Exactly `n` bytes; null at a clean end-of-stream, throws mid-entry. */
41
+ async read(n) {
42
+ while (this.have < n) {
43
+ if (!(await this.pull())) {
44
+ if (this.have === 0)
45
+ return null;
46
+ throw new TarError("truncated archive");
47
+ }
48
+ }
49
+ const out = new Uint8Array(n);
50
+ let filled = 0;
51
+ while (filled < n) {
52
+ const head = this.chunks[0];
53
+ const take = Math.min(head.length, n - filled);
54
+ out.set(head.subarray(0, take), filled);
55
+ filled += take;
56
+ if (take === head.length)
57
+ this.chunks.shift();
58
+ else
59
+ this.chunks[0] = head.subarray(take);
60
+ }
61
+ this.have -= n;
62
+ return out;
63
+ }
64
+ /** Discard `n` bytes without accumulating them. */
65
+ async skip(n) {
66
+ while (n > 0) {
67
+ if (this.chunks.length === 0) {
68
+ if (!(await this.pull()))
69
+ throw new TarError("truncated archive");
70
+ continue;
71
+ }
72
+ const head = this.chunks[0];
73
+ const take = Math.min(head.length, n);
74
+ if (take === head.length)
75
+ this.chunks.shift();
76
+ else
77
+ this.chunks[0] = head.subarray(take);
78
+ this.have -= take;
79
+ n -= take;
80
+ }
81
+ }
82
+ }
83
+ const decoder = new TextDecoder();
84
+ function field(header, start, length) {
85
+ const slice = header.subarray(start, start + length);
86
+ const nul = slice.indexOf(0);
87
+ return decoder.decode(nul < 0 ? slice : slice.subarray(0, nul));
88
+ }
89
+ function octal(header, start, length) {
90
+ const text = field(header, start, length).trim();
91
+ if (text === "")
92
+ return 0;
93
+ const value = parseInt(text, 8);
94
+ if (Number.isNaN(value) || value < 0)
95
+ throw new TarError("bad size field");
96
+ return value;
97
+ }
98
+ /** The `path=` record of a pax extended header, if it has one. */
99
+ function paxPath(data) {
100
+ // Records are "<len> <key>=<value>\n", len covering the whole record.
101
+ const text = decoder.decode(data);
102
+ for (let at = 0; at < text.length;) {
103
+ const space = text.indexOf(" ", at);
104
+ if (space < 0)
105
+ break;
106
+ const length = Number(text.slice(at, space));
107
+ if (!Number.isInteger(length) || length <= 0)
108
+ break;
109
+ const record = text.slice(space + 1, at + length);
110
+ if (record.startsWith("path="))
111
+ return record.slice(5).replace(/\n$/, "");
112
+ at += length;
113
+ }
114
+ return null;
115
+ }
116
+ /**
117
+ * Walk a (decompressed) tar stream. Paths are reported with the archive's
118
+ * root directory stripped — GitHub tarballs wrap everything in
119
+ * `<owner>-<repo>-<sha>/` — matching what a git tree listing would say.
120
+ */
121
+ export async function readTar(stream, keep) {
122
+ const bytes = new ByteReader(stream);
123
+ const paths = [];
124
+ const files = new Map();
125
+ let overridePath = null;
126
+ for (;;) {
127
+ const header = await bytes.read(BLOCK);
128
+ if (header === null)
129
+ break;
130
+ if (header.every((b) => b === 0)) {
131
+ // End marker: two zero blocks, then maybe padding. Nothing left to parse.
132
+ break;
133
+ }
134
+ const size = octal(header, 124, 12);
135
+ const padded = Math.ceil(size / BLOCK) * BLOCK;
136
+ const type = header[156];
137
+ // Extended headers name the *next* entry; the global one ('g') is skipped.
138
+ if (type === 0x78 /* x */ || type === 0x4c /* L */) {
139
+ const data = await bytes.read(size);
140
+ if (data === null)
141
+ throw new TarError("truncated archive");
142
+ overridePath =
143
+ type === 0x78 ? (paxPath(data) ?? overridePath) : decoder.decode(data).replace(/\0+$/, "");
144
+ await bytes.skip(padded - size);
145
+ continue;
146
+ }
147
+ let name = field(header, 0, 100);
148
+ // ustar splits long paths into prefix + name.
149
+ const prefix = field(header, 257, 6).startsWith("ustar") ? field(header, 345, 155) : "";
150
+ if (prefix)
151
+ name = `${prefix}/${name}`;
152
+ if (overridePath !== null) {
153
+ name = overridePath;
154
+ overridePath = null;
155
+ }
156
+ // Regular file entries only — directories, links and the global header
157
+ // carry no board bytes. Type '0' or the ancient NUL both mean "file".
158
+ if (type !== 0x30 && type !== 0) {
159
+ await bytes.skip(padded);
160
+ continue;
161
+ }
162
+ // Strip the tarball's root directory. A path without one (no slash) is
163
+ // the metadata GitHub puts at the top level — not repository content.
164
+ const slash = name.indexOf("/");
165
+ if (slash < 0) {
166
+ await bytes.skip(padded);
167
+ continue;
168
+ }
169
+ const path = name.slice(slash + 1);
170
+ if (path === "") {
171
+ await bytes.skip(padded);
172
+ continue;
173
+ }
174
+ paths.push(path);
175
+ if (keep(path)) {
176
+ const data = await bytes.read(size);
177
+ if (data === null)
178
+ throw new TarError("truncated archive");
179
+ files.set(path, data);
180
+ await bytes.skip(padded - size);
181
+ }
182
+ else {
183
+ await bytes.skip(padded);
184
+ }
185
+ }
186
+ return { paths, files };
187
+ }
188
+ //# sourceMappingURL=tar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar.js","sourceRoot":"","sources":["../../src/board/tar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The tar walk is hand-written against a binary format, so it's tested the
3
+ * way the SQLite reader used to be: against archives built independently —
4
+ * here by a minimal tar *writer* that produces the same shapes GitHub's
5
+ * tarballs contain (ustar headers, a pax_global_header, pax path overrides,
6
+ * a wrapping root directory), gzipped through node:zlib.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=tar.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar.test.d.ts","sourceRoot":"","sources":["../../src/board/tar.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The tar walk is hand-written against a binary format, so it's tested the
3
+ * way the SQLite reader used to be: against archives built independently —
4
+ * here by a minimal tar *writer* that produces the same shapes GitHub's
5
+ * tarballs contain (ustar headers, a pax_global_header, pax path overrides,
6
+ * a wrapping root directory), gzipped through node:zlib.
7
+ */
8
+ import assert from "node:assert/strict";
9
+ import { test } from "node:test";
10
+ import { gzipSync } from "node:zlib";
11
+ import { readTar, TarError } from "./tar.js";
12
+ const encoder = new TextEncoder();
13
+ function header(name, type, size) {
14
+ const block = new Uint8Array(512);
15
+ block.set(encoder.encode(name).subarray(0, 100), 0);
16
+ block.set(encoder.encode("0000644\0"), 100);
17
+ block.set(encoder.encode(size.toString(8).padStart(11, "0") + "\0"), 124);
18
+ block.set(encoder.encode("00000000000\0"), 136);
19
+ block[156] = type.charCodeAt(0);
20
+ block.set(encoder.encode("ustar\x0000"), 257);
21
+ // Checksum: field treated as spaces while summing.
22
+ block.set(encoder.encode(" "), 148);
23
+ const sum = block.reduce((a, b) => a + b, 0);
24
+ block.set(encoder.encode(sum.toString(8).padStart(6, "0") + "\0 "), 148);
25
+ return block;
26
+ }
27
+ function tarball(entries) {
28
+ const blocks = [];
29
+ for (const entry of entries) {
30
+ if (entry.paxPath) {
31
+ const record = ` path=${entry.paxPath}\n`;
32
+ const body = `${record.length + String(record.length + 2).length}${record}`;
33
+ const bytes = encoder.encode(body);
34
+ blocks.push(header("./PaxHeaders/x", "x", bytes.length), pad(bytes));
35
+ }
36
+ const data = encoder.encode(entry.data ?? "");
37
+ blocks.push(header(entry.name, entry.type ?? "0", data.length));
38
+ if (data.length > 0)
39
+ blocks.push(pad(data));
40
+ }
41
+ blocks.push(new Uint8Array(1024));
42
+ const total = blocks.reduce((n, b) => n + b.length, 0);
43
+ const out = new Uint8Array(total);
44
+ let at = 0;
45
+ for (const block of blocks) {
46
+ out.set(block, at);
47
+ at += block.length;
48
+ }
49
+ return out;
50
+ }
51
+ function pad(data) {
52
+ const padded = new Uint8Array(Math.ceil(data.length / 512) * 512);
53
+ padded.set(data);
54
+ return padded;
55
+ }
56
+ function stream(bytes) {
57
+ return new Response(gzipSync(bytes)).body.pipeThrough(new DecompressionStream("gzip"));
58
+ }
59
+ test("walks a GitHub-shaped tarball: root stripped, kept bytes kept, rest skipped", async () => {
60
+ const bytes = tarball([
61
+ { name: "pax_global_header", type: "g", data: "52 comment=1234\n" },
62
+ { name: "owner-repo-abc123/", type: "5" },
63
+ { name: "owner-repo-abc123/README.md", data: "# hi" },
64
+ { name: "owner-repo-abc123/.task/config.json", data: '{"name":"x","prefix":"X","version":2}' },
65
+ { name: "owner-repo-abc123/.task/tickets/1/ticket.md", data: "---\nstatus: todo\n---\n\n# T\n" },
66
+ { name: "owner-repo-abc123/big/blob.bin", data: "z".repeat(5000) },
67
+ { name: "owner-repo-abc123/pkg/.task/config.json", data: "{}" },
68
+ ]);
69
+ const { paths, files } = await readTar(stream(bytes), (path) => path.includes(".task/"));
70
+ assert.deepEqual(paths, [
71
+ "README.md",
72
+ ".task/config.json",
73
+ ".task/tickets/1/ticket.md",
74
+ "big/blob.bin",
75
+ "pkg/.task/config.json",
76
+ ]);
77
+ assert.deepEqual([...files.keys()], [
78
+ ".task/config.json",
79
+ ".task/tickets/1/ticket.md",
80
+ "pkg/.task/config.json",
81
+ ]);
82
+ assert.equal(new TextDecoder().decode(files.get(".task/tickets/1/ticket.md")), "---\nstatus: todo\n---\n\n# T\n");
83
+ });
84
+ test("pax path records override the header name, as GitHub does for long paths", async () => {
85
+ const longPath = `owner-repo-abc123/${"deeply/".repeat(20)}.task/config.json`;
86
+ const bytes = tarball([
87
+ { name: "owner-repo-abc123/x", paxPath: longPath, data: "{}" },
88
+ ]);
89
+ const { paths, files } = await readTar(stream(bytes), () => true);
90
+ assert.deepEqual(paths, [longPath.slice("owner-repo-abc123/".length)]);
91
+ assert.equal(files.size, 1);
92
+ });
93
+ test("ustar prefix fields are joined back onto the name", async () => {
94
+ const bytes = tarball([{ name: "owner-repo-abc123/plain.txt", data: "ok" }]);
95
+ // Craft one entry using the prefix field by hand.
96
+ const block = header("tail.txt", "0", 2);
97
+ block.set(encoder.encode("owner-repo-abc123/some/dir"), 345);
98
+ const data = pad(encoder.encode("ok"));
99
+ const joined = new Uint8Array(bytes.length + block.length + data.length);
100
+ joined.set(block, 0);
101
+ joined.set(data, block.length);
102
+ joined.set(bytes, block.length + data.length);
103
+ const { paths } = await readTar(stream(joined), () => false);
104
+ assert.deepEqual(paths, ["some/dir/tail.txt", "plain.txt"]);
105
+ });
106
+ test("a truncated archive throws instead of returning half a repository", async () => {
107
+ const bytes = tarball([{ name: "owner-repo-abc123/a.txt", data: "hello" }]);
108
+ await assert.rejects(readTar(stream(bytes.subarray(0, 600)), () => true), TarError);
109
+ });
110
+ //# sourceMappingURL=tar.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar.test.js","sourceRoot":"","sources":["../../src/board/tar.test.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -60,6 +60,7 @@ const BOOLEAN_FLAGS = new Set([
60
60
  "fix",
61
61
  "next",
62
62
  "force",
63
+ "no-functions",
63
64
  "reopen",
64
65
  "lock-only",
65
66
  "dry-run",
@@ -1287,6 +1288,24 @@ function cmdExport(args) {
1287
1288
  // every asset copied.
1288
1289
  const out = str(args.flags, "out") ?? join(serveRoot, DEFAULT_OUT_DIR);
1289
1290
  const outDir = isAbsolute(out) ? out : resolve(process.cwd(), out);
1291
+ // The live proxy is written unless told not to. It is inert until the host
1292
+ // has a token — it answers 503 and the board reads the snapshot — so the
1293
+ // default costs nothing, and turning a deploy live becomes one secret
1294
+ // rather than a re-export. The repository it reads is baked in: the export
1295
+ // already knows (the same remote detection `task publish` uses), and one
1296
+ // fewer setting on the host is one fewer thing to get wrong.
1297
+ let functions;
1298
+ let noRemote = false;
1299
+ if (!args.flags["no-functions"]) {
1300
+ const slug = str(args.flags, "repo");
1301
+ const ref = slug ? parseSlug(slug) : detectRepo(serveRoot);
1302
+ if (slug && !ref)
1303
+ fail(`--repo must be owner/name — got ${JSON.stringify(slug)}`);
1304
+ if (ref)
1305
+ functions = { repo: `${ref.owner}/${ref.repo}` };
1306
+ else
1307
+ noRemote = true;
1308
+ }
1290
1309
  let summary;
1291
1310
  try {
1292
1311
  summary = writeExport({
@@ -1295,6 +1314,7 @@ function cmdExport(args) {
1295
1314
  uiDir: UI_DIR,
1296
1315
  force: Boolean(args.flags.force),
1297
1316
  base: str(args.flags, "base"),
1317
+ functions,
1298
1318
  });
1299
1319
  }
1300
1320
  catch (error) {
@@ -1308,6 +1328,14 @@ function cmdExport(args) {
1308
1328
  console.log(`Exported ${what}${summary.commit ? ` at ${summary.commit.slice(0, 7)}` : ""}`);
1309
1329
  console.log(` ${summary.outDir}`);
1310
1330
  console.log("Serve that directory from any static host — the board reads snapshot.json.");
1331
+ if (summary.functions && functions) {
1332
+ // One line, every run: this tier has no viewer auth, and the moment the
1333
+ // function is written is the moment to say so. The README says the rest.
1334
+ console.log(` ${summary.functions} reads ${functions.repo} live once the host has a TASK_GITHUB_TOKEN secret — to anyone who can reach it (README: "Live data for an exported board")`);
1335
+ }
1336
+ else if (noRemote) {
1337
+ console.log(" no live function written — no GitHub remote to read from; pass --repo owner/name to add one");
1338
+ }
1311
1339
  }
1312
1340
  /**
1313
1341
  * `task publish` — the board, at a URL.
@@ -1584,7 +1612,7 @@ Usage
1584
1612
  fails instead). Serves every board at or below
1585
1613
  here — in a monorepo the header becomes a
1586
1614
  board switcher
1587
- task export [--out <dir>] [--base <path>] [--force]
1615
+ task export [--out <dir>] [--base <path>] [--force] [--no-functions | --repo <owner/name>]
1588
1616
  write the board out as static files — the same
1589
1617
  UI plus a snapshot.json it reads instead of an
1590
1618
  API, so any static host serves it read-only
@@ -1596,7 +1624,14 @@ Usage
1596
1624
  domain root. Re-exporting refreshes an earlier
1597
1625
  export in place and leaves anything else in
1598
1626
  there alone; a non-empty directory that isn't
1599
- one needs --force
1627
+ one needs --force. Also writes a Cloudflare
1628
+ Pages Function that reads the repo live from
1629
+ GitHub: inert until the host has a
1630
+ TASK_GITHUB_TOKEN secret (a fine-grained PAT,
1631
+ Contents: read-only), at which point the same
1632
+ deploy upgrades itself — no viewer auth, mind.
1633
+ The repo comes from your remotes; --repo sets
1634
+ it, --no-functions skips the function
1600
1635
  task publish [--public | --private] [--repo <owner/name>]
1601
1636
  give this repo's board a URL at
1602
1637
  task.nickmeriano.com, read-only, updated from