@nickmeriano/task 0.10.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.
- package/README.md +232 -1
- package/dist/asks.test.js +6 -0
- package/dist/asks.test.js.map +1 -1
- package/dist/board/github.d.ts +68 -0
- package/dist/board/github.d.ts.map +1 -0
- package/dist/board/github.js +112 -0
- package/dist/board/github.js.map +1 -0
- package/dist/board/handler.d.ts +50 -0
- package/dist/board/handler.d.ts.map +1 -0
- package/dist/board/handler.js +183 -0
- package/dist/board/handler.js.map +1 -0
- package/dist/board/handler.test.d.ts +11 -0
- package/dist/board/handler.test.d.ts.map +1 -0
- package/dist/board/handler.test.js +229 -0
- package/dist/board/handler.test.js.map +1 -0
- package/dist/board/pages-function.d.ts +23 -0
- package/dist/board/pages-function.d.ts.map +1 -0
- package/dist/board/pages-function.js +34 -0
- package/dist/board/pages-function.js.map +1 -0
- package/dist/board/source.d.ts +118 -0
- package/dist/board/source.d.ts.map +1 -0
- package/dist/board/source.js +333 -0
- package/dist/board/source.js.map +1 -0
- package/dist/board/source.test.d.ts +12 -0
- package/dist/board/source.test.d.ts.map +1 -0
- package/dist/board/source.test.js +165 -0
- package/dist/board/source.test.js.map +1 -0
- package/dist/board/tar.d.ts +28 -0
- package/dist/board/tar.d.ts.map +1 -0
- package/dist/board/tar.js +188 -0
- package/dist/board/tar.js.map +1 -0
- package/dist/board/tar.test.d.ts +9 -0
- package/dist/board/tar.test.d.ts.map +1 -0
- package/dist/board/tar.test.js +110 -0
- package/dist/board/tar.test.js.map +1 -0
- package/dist/claim-io.d.ts.map +1 -1
- package/dist/claim-io.js +4 -3
- package/dist/claim-io.js.map +1 -1
- package/dist/claim.d.ts +14 -2
- package/dist/claim.d.ts.map +1 -1
- package/dist/claim.js +9 -3
- package/dist/claim.js.map +1 -1
- package/dist/claim.test.js +41 -1
- package/dist/claim.test.js.map +1 -1
- package/dist/cli.js +127 -7
- package/dist/cli.js.map +1 -1
- package/dist/export.d.ts +163 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +259 -0
- package/dist/export.js.map +1 -0
- package/dist/export.test.d.ts +12 -0
- package/dist/export.test.d.ts.map +1 -0
- package/dist/export.test.js +264 -0
- package/dist/export.test.js.map +1 -0
- package/dist/functions/board.js +709 -0
- package/dist/git-serve.d.ts +14 -1
- package/dist/git-serve.d.ts.map +1 -1
- package/dist/git-serve.js +30 -2
- package/dist/git-serve.js.map +1 -1
- package/dist/git-serve.test.d.ts +1 -0
- package/dist/git-serve.test.d.ts.map +1 -1
- package/dist/git-serve.test.js +36 -1
- package/dist/git-serve.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +12 -3
- package/dist/server.js.map +1 -1
- package/dist/trailers.d.ts +51 -0
- package/dist/trailers.d.ts.map +1 -0
- package/dist/trailers.js +32 -0
- package/dist/trailers.js.map +1 -0
- package/package.json +3 -3
- package/skill/SKILL.md +16 -1
- package/src/asks.test.ts +15 -0
- package/src/board/github.ts +151 -0
- package/src/board/handler.test.ts +276 -0
- package/src/board/handler.ts +228 -0
- package/src/board/pages-function.ts +42 -0
- package/src/board/source.test.ts +203 -0
- package/src/board/source.ts +422 -0
- package/src/board/tar.test.ts +128 -0
- package/src/board/tar.ts +199 -0
- package/src/claim-io.ts +12 -3
- package/src/claim.test.ts +56 -1
- package/src/claim.ts +27 -4
- package/src/cli.ts +127 -7
- package/src/export.test.ts +361 -0
- package/src/export.ts +354 -0
- package/src/git-serve.test.ts +41 -1
- package/src/git-serve.ts +30 -2
- package/src/index.ts +10 -0
- package/src/server.ts +12 -3
- package/src/trailers.ts +65 -0
- package/ui/dist/assets/index-B8M_DaOt.js +229 -0
- package/ui/dist/assets/{index-CoKCUYic.css → index-eHsqltgs.css} +1 -1
- package/ui/dist/index.html +3 -3
- package/ui/dist/assets/index-BjsorZOU.js +0 -229
|
@@ -0,0 +1,361 @@
|
|
|
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
|
+
|
|
12
|
+
import assert from "node:assert/strict"
|
|
13
|
+
import { test } from "node:test"
|
|
14
|
+
import { execFileSync } from "node:child_process"
|
|
15
|
+
import {
|
|
16
|
+
existsSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
mkdtempSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
writeFileSync,
|
|
22
|
+
} from "node:fs"
|
|
23
|
+
import { tmpdir } from "node:os"
|
|
24
|
+
import { join } from "node:path"
|
|
25
|
+
import { initProject, openBoard } from "./file-store.ts"
|
|
26
|
+
import {
|
|
27
|
+
buildSnapshot,
|
|
28
|
+
functionPath,
|
|
29
|
+
renderFunction,
|
|
30
|
+
writeExport,
|
|
31
|
+
SNAPSHOT_FILE,
|
|
32
|
+
SNAPSHOT_VERSION,
|
|
33
|
+
type Snapshot,
|
|
34
|
+
} from "./export.ts"
|
|
35
|
+
import { STATUSES } from "./types.ts"
|
|
36
|
+
|
|
37
|
+
function tempRoot(): string {
|
|
38
|
+
const dir = mkdtempSync(join(tmpdir(), "task-export-"))
|
|
39
|
+
process.on("exit", () => rmSync(dir, { recursive: true, force: true }))
|
|
40
|
+
return dir
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A fake `ui/dist` — the shape of one is all the copy step cares about. */
|
|
44
|
+
function fakeUi(): string {
|
|
45
|
+
const dir = tempRoot()
|
|
46
|
+
writeFileSync(join(dir, "index.html"), "<!doctype html><html><head><title>board</title></head></html>")
|
|
47
|
+
mkdirSync(join(dir, "assets"))
|
|
48
|
+
writeFileSync(join(dir, "assets", "index-abc123.js"), "// bundle")
|
|
49
|
+
return dir
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A single-board repo with enough in it to be worth exporting. */
|
|
53
|
+
function board(): string {
|
|
54
|
+
const root = tempRoot()
|
|
55
|
+
const store = initProject(root, { name: "acme", prefix: "ACM" })
|
|
56
|
+
const talked = store.create({ title: "Ship the export", status: "todo" })
|
|
57
|
+
store.addComment(talked.key, "on it", "claude")
|
|
58
|
+
store.addComment(talked.key, "still on it", "claude")
|
|
59
|
+
store.create({ title: "Quiet ticket", status: "backlog" })
|
|
60
|
+
const finished = store.create({ title: "Already shipped", status: "done" })
|
|
61
|
+
store.archive(finished.key)
|
|
62
|
+
store.createGoal({ title: "Launch", slug: "launch" })
|
|
63
|
+
store.createGoal({ title: "Old plan", slug: "old-plan" })
|
|
64
|
+
store.archiveGoal("old-plan")
|
|
65
|
+
store.close()
|
|
66
|
+
return root
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
test("the snapshot answers what the serve API answers", () => {
|
|
70
|
+
const root = board()
|
|
71
|
+
const store = openBoard(root)
|
|
72
|
+
|
|
73
|
+
const snapshot = buildSnapshot(root)
|
|
74
|
+
const data = snapshot.data["."]
|
|
75
|
+
|
|
76
|
+
// /api/project — field for field, minus readOnly (the reader forces it).
|
|
77
|
+
assert.equal(data.project.name, store.config.name)
|
|
78
|
+
assert.equal(data.project.prefix, store.config.prefix)
|
|
79
|
+
assert.deepEqual(data.project.statuses, STATUSES)
|
|
80
|
+
assert.equal(typeof data.project.author, "string")
|
|
81
|
+
assert.ok(!("readOnly" in data.project))
|
|
82
|
+
|
|
83
|
+
// /api/tasks — the same list, in the same order, with the same counts.
|
|
84
|
+
const tasks = store.list()
|
|
85
|
+
const counts = store.commentCounts()
|
|
86
|
+
assert.deepEqual(
|
|
87
|
+
data.tasks,
|
|
88
|
+
tasks.map((t) => ({ ...t, commentCount: counts.get(t.key) ?? 0 })),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// /api/goals — live first, archived riding along flagged.
|
|
92
|
+
assert.deepEqual(data.goals, [...store.goals(), ...store.goals(true)])
|
|
93
|
+
assert.ok(data.goals.some((g) => g.archived))
|
|
94
|
+
|
|
95
|
+
// /api/tasks/:n — comments per task, oldest first, empty ones omitted.
|
|
96
|
+
const talkative = tasks.find((t) => t.title === "Ship the export")!
|
|
97
|
+
const quiet = tasks.find((t) => t.title === "Quiet ticket")!
|
|
98
|
+
assert.deepEqual(data.comments[talkative.key], store.comments(talkative.key))
|
|
99
|
+
assert.equal(data.comments[quiet.key], undefined)
|
|
100
|
+
|
|
101
|
+
store.close()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("an archived ticket is off the board, in the export too", () => {
|
|
105
|
+
const root = board()
|
|
106
|
+
|
|
107
|
+
const snapshot = buildSnapshot(root)
|
|
108
|
+
|
|
109
|
+
assert.ok(!snapshot.data["."].tasks.some((t) => t.title === "Already shipped"))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("a monorepo root exports every board, keyed by what /boards says", () => {
|
|
113
|
+
const root = tempRoot()
|
|
114
|
+
initProject(root, { name: "acme", prefix: "ACM" }).close()
|
|
115
|
+
const nested = join(root, "projects", "widget")
|
|
116
|
+
mkdirSync(nested, { recursive: true })
|
|
117
|
+
initProject(nested, { name: "widget", prefix: "WID" }).close()
|
|
118
|
+
|
|
119
|
+
const snapshot = buildSnapshot(root)
|
|
120
|
+
|
|
121
|
+
assert.deepEqual(
|
|
122
|
+
snapshot.boards.map((b) => b.id),
|
|
123
|
+
[".", "projects/widget"],
|
|
124
|
+
)
|
|
125
|
+
assert.deepEqual(snapshot.boards[1], { id: "projects/widget", name: "widget", prefix: "WID" })
|
|
126
|
+
// Every board in the list has an entry, and nothing else does.
|
|
127
|
+
assert.deepEqual(Object.keys(snapshot.data).sort(), [".", "projects/widget"])
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test("a board with nothing above or below it is not an export", () => {
|
|
131
|
+
assert.throws(() => buildSnapshot(tempRoot()), /run `task init`/)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("the commit is the HEAD it was exported from, or null outside a repo", () => {
|
|
135
|
+
const loose = board()
|
|
136
|
+
|
|
137
|
+
assert.equal(buildSnapshot(loose).meta.commit, null)
|
|
138
|
+
|
|
139
|
+
const repo = board()
|
|
140
|
+
const git = (...args: string[]) =>
|
|
141
|
+
execFileSync("git", args, { cwd: repo, stdio: ["ignore", "pipe", "ignore"] })
|
|
142
|
+
git("init", "-q")
|
|
143
|
+
git("config", "user.email", "t@example.com")
|
|
144
|
+
git("config", "user.name", "Test")
|
|
145
|
+
git("add", "-A")
|
|
146
|
+
git("commit", "-qm", "board")
|
|
147
|
+
const head = git("rev-parse", "HEAD").toString().trim()
|
|
148
|
+
|
|
149
|
+
assert.equal(buildSnapshot(repo).meta.commit, head)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test("the export is the assets plus the snapshot", () => {
|
|
153
|
+
const root = board()
|
|
154
|
+
const out = join(tempRoot(), "board")
|
|
155
|
+
|
|
156
|
+
const summary = writeExport({ serveRoot: root, outDir: out, uiDir: fakeUi() })
|
|
157
|
+
|
|
158
|
+
assert.ok(existsSync(join(out, "index.html")))
|
|
159
|
+
assert.ok(existsSync(join(out, "assets", "index-abc123.js")))
|
|
160
|
+
const snapshot = JSON.parse(readFileSync(join(out, SNAPSHOT_FILE), "utf8")) as Snapshot
|
|
161
|
+
assert.equal(snapshot.version, SNAPSHOT_VERSION)
|
|
162
|
+
assert.equal(snapshot.boards.length, 1)
|
|
163
|
+
assert.deepEqual(summary, {
|
|
164
|
+
outDir: out,
|
|
165
|
+
base: "/",
|
|
166
|
+
boards: 1,
|
|
167
|
+
tasks: snapshot.data["."].tasks.length,
|
|
168
|
+
commit: null,
|
|
169
|
+
builtAt: snapshot.meta.builtAt,
|
|
170
|
+
functions: null,
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test("exporting without a built UI says which build to run", () => {
|
|
175
|
+
assert.throws(
|
|
176
|
+
() =>
|
|
177
|
+
writeExport({
|
|
178
|
+
serveRoot: board(),
|
|
179
|
+
outDir: join(tempRoot(), "board"),
|
|
180
|
+
uiDir: join(tempRoot(), "never-built"),
|
|
181
|
+
}),
|
|
182
|
+
/run the package build/,
|
|
183
|
+
)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test("exporting onto the UI build is refused, not a way to delete it", () => {
|
|
187
|
+
const ui = fakeUi()
|
|
188
|
+
|
|
189
|
+
assert.throws(
|
|
190
|
+
() => writeExport({ serveRoot: board(), outDir: ui, uiDir: ui, force: true }),
|
|
191
|
+
/the UI build itself/,
|
|
192
|
+
)
|
|
193
|
+
assert.ok(existsSync(join(ui, "index.html")))
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test("re-exporting refreshes in place and leaves a deployed function alone", () => {
|
|
197
|
+
const root = board()
|
|
198
|
+
const out = join(tempRoot(), "board")
|
|
199
|
+
const ui = fakeUi()
|
|
200
|
+
writeExport({ serveRoot: root, outDir: out, uiDir: ui })
|
|
201
|
+
// TAS-33's room: something the export didn't put there and must not remove.
|
|
202
|
+
mkdirSync(join(out, "functions"))
|
|
203
|
+
writeFileSync(join(out, "functions", "handler.js"), "// live proxy")
|
|
204
|
+
// A stale bundle from the previous build, which it *should* remove.
|
|
205
|
+
writeFileSync(join(out, "assets", "index-old.js"), "// last time")
|
|
206
|
+
|
|
207
|
+
const store = openBoard(root)
|
|
208
|
+
store.create({ title: "Added since the last export", status: "todo" })
|
|
209
|
+
store.close()
|
|
210
|
+
writeExport({ serveRoot: root, outDir: out, uiDir: ui })
|
|
211
|
+
|
|
212
|
+
const snapshot = JSON.parse(readFileSync(join(out, SNAPSHOT_FILE), "utf8")) as Snapshot
|
|
213
|
+
assert.ok(snapshot.data["."].tasks.some((t) => t.title === "Added since the last export"))
|
|
214
|
+
assert.equal(readFileSync(join(out, "functions", "handler.js"), "utf8"), "// live proxy")
|
|
215
|
+
assert.ok(!existsSync(join(out, "assets", "index-old.js")))
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test("a directory that isn't a previous export is refused, not overwritten", () => {
|
|
219
|
+
const root = board()
|
|
220
|
+
const out = join(tempRoot(), "not-an-export")
|
|
221
|
+
mkdirSync(out)
|
|
222
|
+
writeFileSync(join(out, "thesis.md"), "years of work")
|
|
223
|
+
|
|
224
|
+
assert.throws(
|
|
225
|
+
() => writeExport({ serveRoot: root, outDir: out, uiDir: fakeUi() }),
|
|
226
|
+
/--force/,
|
|
227
|
+
)
|
|
228
|
+
assert.equal(readFileSync(join(out, "thesis.md"), "utf8"), "years of work")
|
|
229
|
+
|
|
230
|
+
// --force writes in; it still only ever overwrites what it writes.
|
|
231
|
+
writeExport({ serveRoot: root, outDir: out, uiDir: fakeUi(), force: true })
|
|
232
|
+
assert.ok(existsSync(join(out, SNAPSHOT_FILE)))
|
|
233
|
+
assert.equal(readFileSync(join(out, "thesis.md"), "utf8"), "years of work")
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// ── The mount point, stamped into the page ──────────────────────────────────
|
|
237
|
+
|
|
238
|
+
test("the exported page carries its mount point as a <base> tag", () => {
|
|
239
|
+
const root = board()
|
|
240
|
+
|
|
241
|
+
const atRoot = join(tempRoot(), "root")
|
|
242
|
+
writeExport({ serveRoot: root, outDir: atRoot, uiDir: fakeUi() })
|
|
243
|
+
assert.match(readFileSync(join(atRoot, "index.html"), "utf8"), /<head><base href="\/" \/>/)
|
|
244
|
+
|
|
245
|
+
const atSubpath = join(tempRoot(), "sub")
|
|
246
|
+
const summary = writeExport({ serveRoot: root, outDir: atSubpath, uiDir: fakeUi(), base: "board" })
|
|
247
|
+
assert.equal(summary.base, "/board/")
|
|
248
|
+
assert.match(
|
|
249
|
+
readFileSync(join(atSubpath, "index.html"), "utf8"),
|
|
250
|
+
/<head><base href="\/board\/" \/>/,
|
|
251
|
+
)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test("a base that isn't a plain URL path is refused", () => {
|
|
255
|
+
const root = board()
|
|
256
|
+
for (const bad of ["//cdn.example.com", "/a b/", '/"/']) {
|
|
257
|
+
assert.throws(
|
|
258
|
+
() => writeExport({ serveRoot: root, outDir: join(tempRoot(), "out"), uiDir: fakeUi(), base: bad }),
|
|
259
|
+
/--base/,
|
|
260
|
+
bad,
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// ── The live proxy, written beside the snapshot ─────────────────────────────
|
|
266
|
+
|
|
267
|
+
/** A stand-in for `dist/functions/board.js`: the placeholders are the contract. */
|
|
268
|
+
function fakeBundle(): string {
|
|
269
|
+
const dir = tempRoot()
|
|
270
|
+
const path = join(dir, "board.js")
|
|
271
|
+
writeFileSync(
|
|
272
|
+
path,
|
|
273
|
+
'const REPO = "__TASK_EXPORT_REPO__";\nconst BASE_PATH = "__TASK_EXPORT_BASE_PATH__";\nexport const onRequest = () => [REPO, BASE_PATH];\n',
|
|
274
|
+
)
|
|
275
|
+
return path
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
test("--functions writes the Pages Function where the SPA will look for its API", () => {
|
|
279
|
+
const root = board()
|
|
280
|
+
const out = join(tempRoot(), "board")
|
|
281
|
+
const summary = writeExport({
|
|
282
|
+
serveRoot: root,
|
|
283
|
+
outDir: out,
|
|
284
|
+
uiDir: fakeUi(),
|
|
285
|
+
functions: { repo: "acme/widgets", bundle: fakeBundle() },
|
|
286
|
+
})
|
|
287
|
+
assert.equal(summary.functions, join("functions", "api", "[[path]].js"))
|
|
288
|
+
const written = readFileSync(join(out, summary.functions!), "utf8")
|
|
289
|
+
assert.match(written, /const REPO = "acme\/widgets";/)
|
|
290
|
+
assert.match(written, /const BASE_PATH = "\/api";/)
|
|
291
|
+
assert.ok(!written.includes("__TASK_EXPORT_"))
|
|
292
|
+
// The snapshot is still there: the function is an upgrade, not a replacement.
|
|
293
|
+
assert.ok(existsSync(join(out, SNAPSHOT_FILE)))
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test("under --base the function moves with the mount point", () => {
|
|
297
|
+
assert.equal(functionPath("/"), join("functions", "api", "[[path]].js"))
|
|
298
|
+
assert.equal(functionPath("board"), join("functions", "board", "api", "[[path]].js"))
|
|
299
|
+
assert.equal(functionPath("/a/b/"), join("functions", "a", "b", "api", "[[path]].js"))
|
|
300
|
+
|
|
301
|
+
const out = join(tempRoot(), "sub")
|
|
302
|
+
const summary = writeExport({
|
|
303
|
+
serveRoot: board(),
|
|
304
|
+
outDir: out,
|
|
305
|
+
uiDir: fakeUi(),
|
|
306
|
+
base: "/board/",
|
|
307
|
+
functions: { repo: "acme/widgets", bundle: fakeBundle() },
|
|
308
|
+
})
|
|
309
|
+
assert.equal(summary.functions, join("functions", "board", "api", "[[path]].js"))
|
|
310
|
+
assert.match(readFileSync(join(out, summary.functions!), "utf8"), /const BASE_PATH = "\/board\/api";/)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
test("a re-export without --functions leaves the function as deployed; with it, refreshes it", () => {
|
|
314
|
+
const root = board()
|
|
315
|
+
const out = join(tempRoot(), "board")
|
|
316
|
+
const ui = fakeUi()
|
|
317
|
+
const first = writeExport({
|
|
318
|
+
serveRoot: root,
|
|
319
|
+
outDir: out,
|
|
320
|
+
uiDir: ui,
|
|
321
|
+
functions: { repo: "acme/widgets", bundle: fakeBundle() },
|
|
322
|
+
})
|
|
323
|
+
const file = join(out, first.functions!)
|
|
324
|
+
writeFileSync(file, "// hand-edited on the host")
|
|
325
|
+
|
|
326
|
+
const plain = writeExport({ serveRoot: root, outDir: out, uiDir: ui })
|
|
327
|
+
assert.equal(plain.functions, null)
|
|
328
|
+
assert.equal(readFileSync(file, "utf8"), "// hand-edited on the host")
|
|
329
|
+
|
|
330
|
+
writeExport({ serveRoot: root, outDir: out, uiDir: ui, functions: { repo: "acme/gadgets", bundle: fakeBundle() } })
|
|
331
|
+
assert.match(readFileSync(file, "utf8"), /acme\/gadgets/)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
test("a repo that isn't owner/name, or a missing bundle, fails before anything is written", () => {
|
|
335
|
+
const out = join(tempRoot(), "board")
|
|
336
|
+
assert.throws(
|
|
337
|
+
() =>
|
|
338
|
+
writeExport({
|
|
339
|
+
serveRoot: board(),
|
|
340
|
+
outDir: out,
|
|
341
|
+
uiDir: fakeUi(),
|
|
342
|
+
functions: { repo: 'acme/"; fetch("evil', bundle: fakeBundle() },
|
|
343
|
+
}),
|
|
344
|
+
/owner\/name/,
|
|
345
|
+
)
|
|
346
|
+
assert.ok(!existsSync(out))
|
|
347
|
+
assert.throws(
|
|
348
|
+
() =>
|
|
349
|
+
writeExport({
|
|
350
|
+
serveRoot: board(),
|
|
351
|
+
outDir: out,
|
|
352
|
+
uiDir: fakeUi(),
|
|
353
|
+
functions: { repo: "acme/widgets", bundle: join(tempRoot(), "missing.js") },
|
|
354
|
+
}),
|
|
355
|
+
/function bundle not found/,
|
|
356
|
+
)
|
|
357
|
+
assert.ok(!existsSync(out))
|
|
358
|
+
// A bundle without the placeholders is not the function — refuse rather
|
|
359
|
+
// than ship a file that reads the wrong repository.
|
|
360
|
+
assert.throws(() => renderFunction("export const onRequest = 1", "acme/widgets", "/"), /placeholder|__TASK_EXPORT_/)
|
|
361
|
+
})
|