@stringsync/splates 0.0.53 → 0.0.55
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 +7 -24
- package/artifact.d.ts +3 -0
- package/budget.d.ts +3 -0
- package/creds.d.ts +3 -0
- package/flags.d.ts +37 -0
- package/handoff.d.ts +13 -0
- package/hook.d.ts +3 -0
- package/index.d.ts +2 -0
- package/index.js +1844 -0
- package/lane.d.ts +3 -0
- package/notes.d.ts +4 -0
- package/package.json +12 -14
- package/projects.d.ts +3 -0
- package/queue.d.ts +3 -0
- package/replica.d.ts +3 -0
- package/runners.d.ts +3 -0
- package/server.d.ts +6 -0
- package/setup.d.ts +22 -0
- package/slots.d.ts +3 -0
- package/splates.d.ts +10 -0
- package/task.d.ts +3 -0
- package/tunnel.d.ts +3 -0
- package/waiting.d.ts +3 -0
- package/worker.d.ts +3 -0
- package/artifact.ts +0 -120
- package/budget.ts +0 -117
- package/checkout.ts +0 -8
- package/creds.test.ts +0 -336
- package/creds.ts +0 -144
- package/dev/dev.ts +0 -86
- package/dev/eval.ts +0 -21
- package/dev/fix.ts +0 -63
- package/dev/index.ts +0 -1
- package/dev/prove.ts +0 -38
- package/dev/root.ts +0 -7
- package/dev/ship.ts +0 -46
- package/dev/test.ts +0 -154
- package/e2e.test.ts +0 -472
- package/flags.ts +0 -157
- package/hook.ts +0 -328
- package/index.ts +0 -18
- package/infra.ts +0 -107
- package/lane.test.ts +0 -29
- package/lane.ts +0 -172
- package/notes.ts +0 -19
- package/projects.test.ts +0 -191
- package/projects.ts +0 -37
- package/queue.ts +0 -82
- package/screenshots/annotate.png +0 -0
- package/screenshots/demo.png +0 -0
- package/screenshots/parallel.png +0 -0
- package/screenshots/questions.png +0 -0
- package/server.ts +0 -287
- package/setup.ts +0 -233
- package/slots.ts +0 -65
- package/splates.test.ts +0 -338
- package/splates.ts +0 -567
- package/task.ts +0 -354
- package/tunnel.ts +0 -106
- package/wait-journal.test.ts +0 -96
- package/waiting.ts +0 -59
- package/worker-relay.test.ts +0 -233
- package/worker.ts +0 -300
package/creds.test.ts
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { Testing } from "@stringsync/splates-server/testing";
|
|
3
|
-
import { Duration, sleep } from "webappwiz/time";
|
|
4
|
-
import { splates } from "./splates";
|
|
5
|
-
|
|
6
|
-
const WHY = "To read the build logs through the dashboard's API.";
|
|
7
|
-
const HOW = "Open the dashboard, create a token, and paste it here.";
|
|
8
|
-
|
|
9
|
-
/** Long enough that a test only reaches it when waiting is genuinely stuck. */
|
|
10
|
-
const PATIENT = { timeout: Duration.secs(5), poll: Duration.ms(5) };
|
|
11
|
-
|
|
12
|
-
describe("creds", () => {
|
|
13
|
-
let deps: Testing;
|
|
14
|
-
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
deps = await Testing.open();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => deps.disposeAsync());
|
|
20
|
-
|
|
21
|
-
it("records a request, with what for and how to get it, for the page to show", async () => {
|
|
22
|
-
await deps.creds.request("API_TOKEN", {
|
|
23
|
-
scope: "device",
|
|
24
|
-
purpose: WHY,
|
|
25
|
-
instructions: HOW,
|
|
26
|
-
task: "alpha",
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
expect(await deps.creds.requests()).toMatchObject([
|
|
30
|
-
{
|
|
31
|
-
name: "API_TOKEN",
|
|
32
|
-
scope: "device",
|
|
33
|
-
purpose: WHY,
|
|
34
|
-
instructions: HOW,
|
|
35
|
-
task: "alpha",
|
|
36
|
-
status: "open",
|
|
37
|
-
},
|
|
38
|
-
]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("asks nothing for a cred already kept, unless told the kept one fails", async () => {
|
|
42
|
-
await deps.projectStore.set("API_TOKEN", "old");
|
|
43
|
-
const ask = {
|
|
44
|
-
scope: "project" as const,
|
|
45
|
-
purpose: WHY,
|
|
46
|
-
instructions: HOW,
|
|
47
|
-
task: null,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
expect(await deps.creds.request("API_TOKEN", ask)).toBeNull();
|
|
51
|
-
expect(
|
|
52
|
-
await deps.creds.request("API_TOKEN", { ...ask, replace: true }),
|
|
53
|
-
).toMatchObject({ status: "open" });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("replaces with the last request's purpose and instructions when left off", async () => {
|
|
57
|
-
const ask = {
|
|
58
|
-
scope: "project" as const,
|
|
59
|
-
purpose: WHY,
|
|
60
|
-
instructions: HOW,
|
|
61
|
-
task: null,
|
|
62
|
-
};
|
|
63
|
-
await deps.creds.request("API_TOKEN", ask);
|
|
64
|
-
await deps.projectStore.set("API_TOKEN", "old");
|
|
65
|
-
|
|
66
|
-
expect(
|
|
67
|
-
await deps.creds.request("API_TOKEN", {
|
|
68
|
-
scope: "project",
|
|
69
|
-
purpose: "",
|
|
70
|
-
instructions: "",
|
|
71
|
-
task: null,
|
|
72
|
-
replace: true,
|
|
73
|
-
}),
|
|
74
|
-
).toMatchObject({ purpose: WHY, instructions: HOW, status: "open" });
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("refuses a request that says nothing of what it is for", async () => {
|
|
78
|
-
await expect(
|
|
79
|
-
deps.creds.request("API_TOKEN", {
|
|
80
|
-
scope: "project",
|
|
81
|
-
purpose: " ",
|
|
82
|
-
instructions: HOW,
|
|
83
|
-
task: null,
|
|
84
|
-
}),
|
|
85
|
-
).toBail("usage");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("refuses a request that says nothing of how to get it", async () => {
|
|
89
|
-
await expect(
|
|
90
|
-
deps.creds.request("API_TOKEN", {
|
|
91
|
-
scope: "project",
|
|
92
|
-
purpose: WHY,
|
|
93
|
-
instructions: " ",
|
|
94
|
-
task: null,
|
|
95
|
-
}),
|
|
96
|
-
).toBail("usage");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("refuses a name no environment variable could have", async () => {
|
|
100
|
-
await expect(
|
|
101
|
-
deps.creds.request("api-token", {
|
|
102
|
-
scope: "project",
|
|
103
|
-
purpose: WHY,
|
|
104
|
-
instructions: HOW,
|
|
105
|
-
task: null,
|
|
106
|
-
}),
|
|
107
|
-
).toBail("usage");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("keeps a value in the store its request asked for, and fills it", async () => {
|
|
111
|
-
await deps.creds.request("API_TOKEN", {
|
|
112
|
-
scope: "device",
|
|
113
|
-
purpose: WHY,
|
|
114
|
-
instructions: HOW,
|
|
115
|
-
task: null,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
expect(await deps.creds.save("API_TOKEN", " secret \n")).toBe("device");
|
|
119
|
-
|
|
120
|
-
expect(await deps.deviceStore.get("API_TOKEN")).toBe("secret");
|
|
121
|
-
expect(await deps.projectStore.get("API_TOKEN")).toBeUndefined();
|
|
122
|
-
expect((await deps.creds.requests())[0]?.status).toBe("saved");
|
|
123
|
-
expect(await deps.creds.credentials.require("API_TOKEN")).toBe("secret");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("never writes a value into its record", async () => {
|
|
127
|
-
await deps.creds.request("API_TOKEN", {
|
|
128
|
-
scope: "project",
|
|
129
|
-
purpose: WHY,
|
|
130
|
-
instructions: HOW,
|
|
131
|
-
task: null,
|
|
132
|
-
});
|
|
133
|
-
await deps.creds.save("API_TOKEN", "hunter2", { by: "a tool" });
|
|
134
|
-
|
|
135
|
-
expect(await deps.fs.read(`${deps.splatesDir}/creds.json`)).not.toContain(
|
|
136
|
-
"hunter2",
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("ends a wait once the value is saved", async () => {
|
|
141
|
-
await deps.creds.request("API_TOKEN", {
|
|
142
|
-
scope: "project",
|
|
143
|
-
purpose: WHY,
|
|
144
|
-
instructions: HOW,
|
|
145
|
-
task: null,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const waiting = deps.creds.wait("API_TOKEN", PATIENT);
|
|
149
|
-
await sleep(Duration.ms(20));
|
|
150
|
-
await deps.creds.save("API_TOKEN", "secret");
|
|
151
|
-
|
|
152
|
-
expect(await waiting).toBe("saved");
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("ends a wait once the request is dismissed", async () => {
|
|
156
|
-
await deps.creds.request("API_TOKEN", {
|
|
157
|
-
scope: "project",
|
|
158
|
-
purpose: WHY,
|
|
159
|
-
instructions: HOW,
|
|
160
|
-
task: null,
|
|
161
|
-
});
|
|
162
|
-
await deps.creds.dismiss("API_TOKEN");
|
|
163
|
-
|
|
164
|
-
expect(await deps.creds.wait("API_TOKEN", PATIENT)).toBe("dismissed");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("gives up waiting on a request nobody fills", async () => {
|
|
168
|
-
await deps.creds.request("API_TOKEN", {
|
|
169
|
-
scope: "project",
|
|
170
|
-
purpose: WHY,
|
|
171
|
-
instructions: HOW,
|
|
172
|
-
task: null,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
await expect(
|
|
176
|
-
deps.creds.wait("API_TOKEN", {
|
|
177
|
-
timeout: Duration.ms(20),
|
|
178
|
-
poll: Duration.ms(5),
|
|
179
|
-
}),
|
|
180
|
-
).toBail("timeout");
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("refuses to wait on a cred nothing requested", async () => {
|
|
184
|
-
await expect(deps.creds.wait("API_TOKEN", PATIENT)).toBail("not_found");
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("lists what each store keeps, and what a tool saved itself", async () => {
|
|
188
|
-
await deps.projectStore.set("DATABASE_URL", "x");
|
|
189
|
-
await deps.creds.save("TUNNEL_TOKEN", "y", {
|
|
190
|
-
scope: "device",
|
|
191
|
-
by: "splates tunnel setup",
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
expect(await deps.creds.kept()).toEqual([
|
|
195
|
-
{
|
|
196
|
-
name: "DATABASE_URL",
|
|
197
|
-
scope: "project",
|
|
198
|
-
provider: "system",
|
|
199
|
-
savedBy: null,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: "TUNNEL_TOKEN",
|
|
203
|
-
scope: "device",
|
|
204
|
-
provider: "system",
|
|
205
|
-
savedBy: "splates tunnel setup",
|
|
206
|
-
},
|
|
207
|
-
]);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it("removes a kept cred from its store", async () => {
|
|
211
|
-
await deps.creds.save("TUNNEL_TOKEN", "y", { scope: "device", by: "t" });
|
|
212
|
-
|
|
213
|
-
expect(await deps.creds.remove("TUNNEL_TOKEN", "device")).toBe(true);
|
|
214
|
-
expect(await deps.creds.kept()).toEqual([]);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it("says the system keeps no secrets when its store cannot be reached", async () => {
|
|
218
|
-
deps.projectStore.names = () => Promise.reject(new Error("no daemon"));
|
|
219
|
-
|
|
220
|
-
await expect(deps.creds.kept()).toBail("no_secret_store", {
|
|
221
|
-
message: "secret service",
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// scry-ignore simple-test-setup: one describe per command, as the file groups the CLI's cases
|
|
227
|
-
describe("splates creds", () => {
|
|
228
|
-
let env: Testing;
|
|
229
|
-
let deps: Parameters<typeof splates.run>[0];
|
|
230
|
-
|
|
231
|
-
beforeEach(async () => {
|
|
232
|
-
env = await Testing.open();
|
|
233
|
-
env.ps.cd(env.root);
|
|
234
|
-
deps = {
|
|
235
|
-
log: env.log,
|
|
236
|
-
fs: env.fs,
|
|
237
|
-
ps: env.ps,
|
|
238
|
-
assets: env.assets,
|
|
239
|
-
secrets: env.secrets,
|
|
240
|
-
};
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
afterEach(() => env.disposeAsync());
|
|
244
|
-
|
|
245
|
-
it("requests a cred, then waits until a person saves it", async () => {
|
|
246
|
-
await splates.run(deps, [
|
|
247
|
-
"creds",
|
|
248
|
-
"request",
|
|
249
|
-
"API_TOKEN",
|
|
250
|
-
"--purpose",
|
|
251
|
-
WHY,
|
|
252
|
-
"--instructions",
|
|
253
|
-
HOW,
|
|
254
|
-
"--device",
|
|
255
|
-
]);
|
|
256
|
-
expect(env.out()).toContain(`requested API_TOKEN ${WHY}`);
|
|
257
|
-
|
|
258
|
-
const waiting = splates.run(deps, ["creds", "wait", "API_TOKEN"]);
|
|
259
|
-
await sleep(Duration.ms(20));
|
|
260
|
-
await env.creds.save("API_TOKEN", "secret");
|
|
261
|
-
await waiting;
|
|
262
|
-
|
|
263
|
-
expect(env.out()).toContain("saved API_TOKEN");
|
|
264
|
-
expect(await env.deviceStore.get("API_TOKEN")).toBe("secret");
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("exits dismissed when the person turns the request down", async () => {
|
|
268
|
-
await splates.run(deps, [
|
|
269
|
-
"creds",
|
|
270
|
-
"request",
|
|
271
|
-
"API_TOKEN",
|
|
272
|
-
"--purpose",
|
|
273
|
-
WHY,
|
|
274
|
-
"--instructions",
|
|
275
|
-
HOW,
|
|
276
|
-
]);
|
|
277
|
-
await env.creds.dismiss("API_TOKEN");
|
|
278
|
-
|
|
279
|
-
await splates.run(deps, ["creds", "wait", "API_TOKEN"]);
|
|
280
|
-
|
|
281
|
-
expect(env.proc.exits).toEqual([22]);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it("lists requests and kept creds by name, never a value", async () => {
|
|
285
|
-
await env.projectStore.set("DATABASE_URL", "postgres://secret");
|
|
286
|
-
await splates.run(deps, [
|
|
287
|
-
"creds",
|
|
288
|
-
"request",
|
|
289
|
-
"API_TOKEN",
|
|
290
|
-
"--purpose",
|
|
291
|
-
WHY,
|
|
292
|
-
"--instructions",
|
|
293
|
-
HOW,
|
|
294
|
-
]);
|
|
295
|
-
|
|
296
|
-
await splates.run(deps, ["creds", "list", "--json"]);
|
|
297
|
-
|
|
298
|
-
const listed = String(env.log.entries.at(-1)?.message);
|
|
299
|
-
expect(JSON.parse(listed)).toMatchObject({
|
|
300
|
-
requested: [{ name: "API_TOKEN", status: "open" }],
|
|
301
|
-
kept: [{ name: "DATABASE_URL", scope: "project" }],
|
|
302
|
-
});
|
|
303
|
-
expect(listed).not.toContain("postgres://secret");
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// scry-ignore simple-test-setup: copyLegacy reads a store the other commands never touch
|
|
308
|
-
describe("copyLegacy", () => {
|
|
309
|
-
let deps: Testing;
|
|
310
|
-
|
|
311
|
-
beforeEach(async () => {
|
|
312
|
-
deps = await Testing.open();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
afterEach(() => deps.disposeAsync());
|
|
316
|
-
|
|
317
|
-
it("keeps each of arbor's creds under splates's name too, in the same store", async () => {
|
|
318
|
-
await deps.deviceStore.set("ARBOR_CLOUDFLARE_API_TOKEN", "secret");
|
|
319
|
-
|
|
320
|
-
await deps.creds.copyLegacy();
|
|
321
|
-
|
|
322
|
-
expect(await deps.deviceStore.names()).toEqual([
|
|
323
|
-
"ARBOR_CLOUDFLARE_API_TOKEN",
|
|
324
|
-
"SPLATES_CLOUDFLARE_API_TOKEN",
|
|
325
|
-
]);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it("leaves a cred splates already keeps", async () => {
|
|
329
|
-
await deps.deviceStore.set("ARBOR_TUNNEL_TOKEN", "old");
|
|
330
|
-
await deps.deviceStore.set("SPLATES_TUNNEL_TOKEN", "new");
|
|
331
|
-
|
|
332
|
-
await deps.creds.copyLegacy();
|
|
333
|
-
|
|
334
|
-
expect(await deps.deviceStore.get("SPLATES_TUNNEL_TOKEN")).toBe("new");
|
|
335
|
-
});
|
|
336
|
-
});
|
package/creds.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_TIMEOUT, PROVIDERS } from "@stringsync/splates-server/creds";
|
|
2
|
-
import { fail } from "@stringsync/splates-server/exit";
|
|
3
|
-
import { repository } from "@stringsync/splates-server/records";
|
|
4
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
5
|
-
import { untilAfter } from "@stringsync/splates-server/tasks";
|
|
6
|
-
import { cli } from "webappwiz/cmd";
|
|
7
|
-
import { color } from "webappwiz/log";
|
|
8
|
-
import { Duration } from "webappwiz/time";
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
import { here } from "./flags";
|
|
11
|
-
|
|
12
|
-
/** `splates creds`, which the root cli mounts after `splates lane`. */
|
|
13
|
-
export const splatesCreds = cli<SplatesDeps>("creds").description(
|
|
14
|
-
"creds an agent needs a person for, such as an API token: request one saying what it is for, with instructions for getting it, which the Creds page of `splates server` shows for the person to paste, then wait for it; the value goes into the system's store (the macOS Keychain, Windows Credential Manager, a Linux secret service), never to the agent, and code reads it through webappwiz/creds",
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
splatesCreds
|
|
18
|
-
.command("request")
|
|
19
|
-
.use(repository({ needs: "own" }))
|
|
20
|
-
.description(
|
|
21
|
-
"ask a person for a cred, saying why it is needed and what it will be used for, with markdown instructions for getting it; returns at once, and does nothing when the store already keeps it unless --replace; run from a worktree, it records the task asking",
|
|
22
|
-
)
|
|
23
|
-
.arg("name", z.string(), {
|
|
24
|
-
description: "what it is kept as, an environment variable's name",
|
|
25
|
-
})
|
|
26
|
-
.option("purpose", z.string(), {
|
|
27
|
-
default: "",
|
|
28
|
-
description:
|
|
29
|
-
"why it is needed and what it will be used for, in a sentence the person reads before getting it",
|
|
30
|
-
})
|
|
31
|
-
.option("instructions", z.string(), {
|
|
32
|
-
default: "",
|
|
33
|
-
description:
|
|
34
|
-
"how to get it, in markdown: where to go, what to pick, what to paste",
|
|
35
|
-
})
|
|
36
|
-
.option("device", z.boolean(), {
|
|
37
|
-
default: false,
|
|
38
|
-
description:
|
|
39
|
-
"keep it for every project on this device, as for a person's own account, rather than this project alone",
|
|
40
|
-
})
|
|
41
|
-
.option("provider", z.optional(z.enum(PROVIDERS)), {
|
|
42
|
-
description:
|
|
43
|
-
"who keeps it: `system`, the machine's own store, or `ssm`, AWS Parameter Store, where that is offered",
|
|
44
|
-
})
|
|
45
|
-
.option("replace", z.boolean(), {
|
|
46
|
-
default: false,
|
|
47
|
-
description:
|
|
48
|
-
"ask again although one is kept, since the kept one does not work; --purpose and --instructions left off are those of the last request",
|
|
49
|
-
})
|
|
50
|
-
.action(async (opts, ctx) => {
|
|
51
|
-
const scope = opts.device ? "device" : "project";
|
|
52
|
-
const provider = opts.provider ?? "system";
|
|
53
|
-
const request = await ctx.journal.record(
|
|
54
|
-
"creds request",
|
|
55
|
-
null,
|
|
56
|
-
async () =>
|
|
57
|
-
ctx.creds.request(opts.name, {
|
|
58
|
-
scope,
|
|
59
|
-
provider,
|
|
60
|
-
purpose: opts.purpose,
|
|
61
|
-
instructions: opts.instructions,
|
|
62
|
-
task: await here(ctx),
|
|
63
|
-
replace: opts.replace,
|
|
64
|
-
}),
|
|
65
|
-
(asked) => ({ name: opts.name, asked: asked !== null }),
|
|
66
|
-
);
|
|
67
|
-
if (request === null) {
|
|
68
|
-
ctx.log.info(
|
|
69
|
-
`${color.green("kept")} ${opts.name} in ${ctx.creds.label(scope, provider)}: nothing to ask`,
|
|
70
|
-
);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
ctx.log.info(
|
|
74
|
-
`${color.yellow("requested")} ${opts.name} ${color.dim(request.purpose)}`,
|
|
75
|
-
);
|
|
76
|
-
ctx.log.error(
|
|
77
|
-
`It shows on the Creds page of \`splates server\`. \`splates creds wait ${opts.name}\` until it is saved.`,
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
splatesCreds
|
|
82
|
-
.command("wait")
|
|
83
|
-
.use(repository({ needs: "own" }))
|
|
84
|
-
.description(
|
|
85
|
-
"block until a person saves the cred requested, or dismisses the request (`dismissed`); gives up with `timeout` rather than waiting forever",
|
|
86
|
-
)
|
|
87
|
-
.arg("name", z.string(), { description: "the cred requested" })
|
|
88
|
-
.option("timeout-secs", z.coerce.number(), {
|
|
89
|
-
default: DEFAULT_TIMEOUT.secs,
|
|
90
|
-
description: "how long to wait before giving up",
|
|
91
|
-
})
|
|
92
|
-
.action(async (opts, ctx) => {
|
|
93
|
-
const timeout = Duration.secs(opts["timeout-secs"]);
|
|
94
|
-
await ctx.journal.waiting(
|
|
95
|
-
"creds wait",
|
|
96
|
-
null,
|
|
97
|
-
{ name: opts.name, until: untilAfter(timeout) },
|
|
98
|
-
async () => {
|
|
99
|
-
const ended = await ctx.creds.wait(opts.name, { timeout });
|
|
100
|
-
if (ended === "dismissed") {
|
|
101
|
-
fail(
|
|
102
|
-
"dismissed",
|
|
103
|
-
`${opts.name} was dismissed: carry on without it, or ask the person why`,
|
|
104
|
-
{ name: opts.name },
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
ctx.log.info(`${color.green("saved")} ${opts.name}`);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
splatesCreds
|
|
113
|
-
.command("list")
|
|
114
|
-
.use(repository({ needs: "read" }))
|
|
115
|
-
.description(
|
|
116
|
-
"the creds requested and still open, with what each is for, and every cred the project's store and the device's keep, by name; never a value",
|
|
117
|
-
)
|
|
118
|
-
.option("json", z.boolean(), { default: false, description: "emit JSON" })
|
|
119
|
-
.action(async (opts, ctx) => {
|
|
120
|
-
const open = (await ctx.creds.requests()).filter(
|
|
121
|
-
(each) => each.status === "open",
|
|
122
|
-
);
|
|
123
|
-
const kept = await ctx.creds.kept();
|
|
124
|
-
if (opts.json) {
|
|
125
|
-
ctx.log.info(JSON.stringify({ requested: open, kept }, null, 2));
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
for (const each of open) {
|
|
129
|
-
ctx.log.info(
|
|
130
|
-
`${color.yellow("requested")} ${each.name} ${color.dim(each.scope)}${each.task === null ? "" : color.dim(` by ${each.task}`)}`,
|
|
131
|
-
);
|
|
132
|
-
if (each.purpose !== "") {
|
|
133
|
-
ctx.log.info(` ${each.purpose}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
for (const each of kept) {
|
|
137
|
-
ctx.log.info(
|
|
138
|
-
`${color.green("kept")} ${each.name} ${color.dim(`${each.provider} ${each.scope}`)}${each.savedBy === null ? "" : color.dim(` saved by ${each.savedBy}`)}`,
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
if (open.length === 0 && kept.length === 0) {
|
|
142
|
-
ctx.log.info(color.dim("no creds requested or kept"));
|
|
143
|
-
}
|
|
144
|
-
});
|
package/dev/dev.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { SplatesDeps } from "@stringsync/splates-server/server";
|
|
2
|
-
import type { Cli } from "webappwiz/cmd";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { evalSkill } from "./eval";
|
|
5
|
-
import { fix } from "./fix";
|
|
6
|
-
import { PROOFS, prove } from "./prove";
|
|
7
|
-
import { ship } from "./ship";
|
|
8
|
-
import { test } from "./test";
|
|
9
|
-
|
|
10
|
-
/** Adds `splates dev`, the commands for working on this repository, to `program`. */
|
|
11
|
-
export function mountDev(program: Cli<SplatesDeps>): void {
|
|
12
|
-
// Only `checkout.ts`, the entry `bin/splates` runs, calls this, so the
|
|
13
|
-
// published cli never carries these: a release bundles what its `bin` and
|
|
14
|
-
// `exports` entries import, and `index.ts` imports none of this.
|
|
15
|
-
const dev = program
|
|
16
|
-
.group("dev")
|
|
17
|
-
.description("work on the splates repository");
|
|
18
|
-
|
|
19
|
-
dev
|
|
20
|
-
.command("eval")
|
|
21
|
-
.description(
|
|
22
|
-
"compare versions of the splates skill by running agents on it; uses tokens billed to an API key or counted against a Claude plan, so run it only when asked",
|
|
23
|
-
)
|
|
24
|
-
.rest("args", z.string(), {
|
|
25
|
-
description:
|
|
26
|
-
"[fast|list|report] [--probe id] [--target agent:model] [--parallel n], or explicit e2e [run|report]",
|
|
27
|
-
})
|
|
28
|
-
.allowUnknownOption()
|
|
29
|
-
.action((opts, { ps }) => evalSkill({ ...opts, ps }));
|
|
30
|
-
|
|
31
|
-
dev
|
|
32
|
-
.command("fix")
|
|
33
|
-
.description("format, check, and typecheck the repository")
|
|
34
|
-
.option("check", z.boolean(), {
|
|
35
|
-
default: false,
|
|
36
|
-
description: "report problems without writing fixes (for CI)",
|
|
37
|
-
})
|
|
38
|
-
.option("code-only", z.boolean(), {
|
|
39
|
-
default: false,
|
|
40
|
-
description: "run Scry code rules without credentials or model calls",
|
|
41
|
-
})
|
|
42
|
-
.action(({ "code-only": codeOnly, ...opts }, { log, ps }) =>
|
|
43
|
-
fix({ ...opts, codeOnly, log, ps }),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
dev
|
|
47
|
-
.command("prove")
|
|
48
|
-
.description(
|
|
49
|
-
"prove splates on Linux end to end with Docker, nothing of yours used: `coordinator` runs the server in a Debian container as the user splates, as the AWS box will, with this machine as its worker, and drives a task, a restart, an archive and a cred through it; `worker` runs a container as a worker of a server here. Read docker/prove-<which>.sh for what each checks",
|
|
50
|
-
)
|
|
51
|
-
.arg("proof", z.enum(PROOFS), {
|
|
52
|
-
description: "coordinator or worker",
|
|
53
|
-
})
|
|
54
|
-
.rest("args", z.string(), {
|
|
55
|
-
description:
|
|
56
|
-
"handed to the script: a port, and for `worker` --agent <id> --model <model> to hand the task to a roster agent",
|
|
57
|
-
})
|
|
58
|
-
.allowUnknownOption()
|
|
59
|
-
.action(({ proof, args }, { log, ps }) => prove({ proof, args, log, ps }));
|
|
60
|
-
|
|
61
|
-
dev
|
|
62
|
-
.command("ship")
|
|
63
|
-
.description("release the package, with its skill stamped at its version")
|
|
64
|
-
.action((_opts, { log, fs, ps }) => ship({ log, fs, ps }));
|
|
65
|
-
|
|
66
|
-
dev
|
|
67
|
-
.command("test")
|
|
68
|
-
.description("run the package's tests; eval harness tests are separate")
|
|
69
|
-
.option("evals", z.boolean(), {
|
|
70
|
-
default: false,
|
|
71
|
-
description: "run only free eval harness tests (no model calls)",
|
|
72
|
-
})
|
|
73
|
-
.option("all", z.boolean(), {
|
|
74
|
-
default: false,
|
|
75
|
-
description: "run the package's and the eval harness's tests together",
|
|
76
|
-
})
|
|
77
|
-
.option("linux", z.boolean(), {
|
|
78
|
-
default: false,
|
|
79
|
-
description:
|
|
80
|
-
"run them on Linux, in a Docker container built from the checkout, as the box and the workers run; needs Docker",
|
|
81
|
-
})
|
|
82
|
-
.rest("files", z.string(), {
|
|
83
|
-
description: "run only the test files whose path has one of these",
|
|
84
|
-
})
|
|
85
|
-
.action((opts, { log, ps }) => test({ ...opts, log, ps }));
|
|
86
|
-
}
|
package/dev/eval.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { NodePs, type Ps } from "webappwiz/system";
|
|
2
|
-
import { ROOT } from "./root";
|
|
3
|
-
|
|
4
|
-
export interface EvalOptions {
|
|
5
|
-
/** What splates's evals entry is given: fast selection, report, or explicit e2e, then its arguments. */
|
|
6
|
-
args: string[];
|
|
7
|
-
ps?: Ps;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** Run the focused suite by default; legacy end-to-end evaluation requires `e2e`. */
|
|
11
|
-
export async function evalSkill(opts: EvalOptions): Promise<void> {
|
|
12
|
-
const ps = opts.ps ?? new NodePs();
|
|
13
|
-
const args = opts.args.length ? opts.args : ["fast"];
|
|
14
|
-
const { exitCode } = await ps.spawn(
|
|
15
|
-
["bun", "evals/splates-skill/index.ts", ...args],
|
|
16
|
-
{ cwd: ROOT },
|
|
17
|
-
);
|
|
18
|
-
if (exitCode !== 0) {
|
|
19
|
-
throw new Error("The evals failed");
|
|
20
|
-
}
|
|
21
|
-
}
|
package/dev/fix.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { ConsoleLogger, color, type Logger } from "webappwiz/log";
|
|
2
|
-
import { NodePs, type Ps } from "webappwiz/system";
|
|
3
|
-
import { ROOT } from "./root";
|
|
4
|
-
|
|
5
|
-
export interface FixOptions {
|
|
6
|
-
/** Report problems without writing fixes, as CI wants it. */
|
|
7
|
-
check: boolean;
|
|
8
|
-
/** Check only code rules, without credentials or model calls. */
|
|
9
|
-
codeOnly?: boolean;
|
|
10
|
-
/** Skip Scry when checking a release that was already reviewed. */
|
|
11
|
-
skipScry?: boolean;
|
|
12
|
-
log?: Logger;
|
|
13
|
-
ps?: Ps;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Runs biome, the type checker, then scry on what changed since main, in
|
|
18
|
-
* that order. Scry leaves off each model its budget no longer covers, so a
|
|
19
|
-
* spent budget narrows the check rather than fail it.
|
|
20
|
-
*/
|
|
21
|
-
export async function fix(opts: FixOptions): Promise<void> {
|
|
22
|
-
const log = opts.log ?? new ConsoleLogger();
|
|
23
|
-
const ps = opts.ps ?? new NodePs();
|
|
24
|
-
|
|
25
|
-
// Every step says how it went and the first failure ends the run, so the
|
|
26
|
-
// verdict a person reads last is the one that stopped them.
|
|
27
|
-
const step = (name: string, ok: boolean, failure: string): void => {
|
|
28
|
-
log.info(`${name}: ${ok ? color.green("success") : color.red("failed")}`);
|
|
29
|
-
if (!ok) {
|
|
30
|
-
throw new Error(failure);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const flags = opts.check ? [] : ["--write", "--unsafe"];
|
|
35
|
-
const biome = await ps.spawn(["bunx", "biome", "check", ...flags, "."], {
|
|
36
|
-
cwd: ROOT,
|
|
37
|
-
});
|
|
38
|
-
step("biome", biome.exitCode === 0, "Biome check failed");
|
|
39
|
-
|
|
40
|
-
const typecheck = await ps.spawn(["bunx", "tsc", "--noEmit"], {
|
|
41
|
-
cwd: ROOT,
|
|
42
|
-
});
|
|
43
|
-
step("typecheck", typecheck.exitCode === 0, "Typechecking failed");
|
|
44
|
-
|
|
45
|
-
if (opts.skipScry) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const scry = await ps.spawn(
|
|
50
|
-
[
|
|
51
|
-
"bunx",
|
|
52
|
-
"@webappwiz/cli",
|
|
53
|
-
"scry",
|
|
54
|
-
"check",
|
|
55
|
-
"--since",
|
|
56
|
-
"main",
|
|
57
|
-
"--disable-over-budget",
|
|
58
|
-
...(opts.codeOnly ? ["--disable", "som", "--disable", "llm"] : []),
|
|
59
|
-
],
|
|
60
|
-
{ cwd: ROOT },
|
|
61
|
-
);
|
|
62
|
-
step("scry", scry.exitCode === 0, "Scry check failed");
|
|
63
|
-
}
|
package/dev/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { mountDev } from "./dev";
|
package/dev/prove.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { ConsoleLogger, type Logger } from "webappwiz/log";
|
|
3
|
-
import { NodePs, type Ps } from "webappwiz/system";
|
|
4
|
-
import { ROOT } from "./root";
|
|
5
|
-
|
|
6
|
-
/** The proofs, each a script in `docker/`. */
|
|
7
|
-
export const PROOFS = ["coordinator", "worker"] as const;
|
|
8
|
-
export type Proof = (typeof PROOFS)[number];
|
|
9
|
-
|
|
10
|
-
export interface ProveOptions {
|
|
11
|
-
proof: Proof;
|
|
12
|
-
/** What the script takes after its name, such as a port or `--agent`. */
|
|
13
|
-
args?: string[];
|
|
14
|
-
ps?: Ps;
|
|
15
|
-
log?: Logger;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Runs a proof of splates on Linux end to end: `coordinator` runs the server
|
|
20
|
-
* in a Docker container with this machine as its worker, and `worker` the
|
|
21
|
-
* inverse. Each prints what it checked and fails on the first thing wrong.
|
|
22
|
-
*/
|
|
23
|
-
export async function prove(opts: ProveOptions): Promise<void> {
|
|
24
|
-
const ps = opts.ps ?? new NodePs();
|
|
25
|
-
const log = opts.log ?? new ConsoleLogger();
|
|
26
|
-
log.info(`proving the ${opts.proof} on Linux; needs Docker`);
|
|
27
|
-
const { exitCode } = await ps.spawn(
|
|
28
|
-
[
|
|
29
|
-
"sh",
|
|
30
|
-
join(ROOT, "docker", `prove-${opts.proof}.sh`),
|
|
31
|
-
...(opts.args ?? []),
|
|
32
|
-
],
|
|
33
|
-
{ cwd: ROOT },
|
|
34
|
-
);
|
|
35
|
-
if (exitCode !== 0) {
|
|
36
|
-
throw new Error(`The ${opts.proof} proof failed`);
|
|
37
|
-
}
|
|
38
|
-
}
|
package/dev/root.ts
DELETED