@masculinecache/wrangler-axi 0.1.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/LICENSE +21 -0
- package/README.md +193 -0
- package/dist/bin/wrangler-axi.d.ts +2 -0
- package/dist/bin/wrangler-axi.js +17 -0
- package/dist/src/args.d.ts +19 -0
- package/dist/src/args.js +105 -0
- package/dist/src/cli.d.ts +27 -0
- package/dist/src/cli.js +101 -0
- package/dist/src/commands/d1.d.ts +2 -0
- package/dist/src/commands/d1.js +149 -0
- package/dist/src/commands/kv.d.ts +2 -0
- package/dist/src/commands/kv.js +423 -0
- package/dist/src/commands/pages.d.ts +2 -0
- package/dist/src/commands/pages.js +337 -0
- package/dist/src/commands/r2.d.ts +2 -0
- package/dist/src/commands/r2.js +289 -0
- package/dist/src/commands/secrets.d.ts +2 -0
- package/dist/src/commands/secrets.js +195 -0
- package/dist/src/commands/whoami.d.ts +2 -0
- package/dist/src/commands/whoami.js +60 -0
- package/dist/src/commands/workers.d.ts +2 -0
- package/dist/src/commands/workers.js +431 -0
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.js +77 -0
- package/dist/src/fields.d.ts +15 -0
- package/dist/src/fields.js +36 -0
- package/dist/src/format.d.ts +13 -0
- package/dist/src/format.js +21 -0
- package/dist/src/list.d.ts +18 -0
- package/dist/src/list.js +23 -0
- package/dist/src/stdin.d.ts +2 -0
- package/dist/src/stdin.js +14 -0
- package/dist/src/toon.d.ts +30 -0
- package/dist/src/toon.js +159 -0
- package/dist/src/util.d.ts +8 -0
- package/dist/src/util.js +26 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +23 -0
- package/dist/src/wrangler.d.ts +64 -0
- package/dist/src/wrangler.js +175 -0
- package/package.json +48 -0
- package/skills/wrangler-axi/SKILL.md +82 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { callWrangler, parseJson } from "../wrangler.js";
|
|
2
|
+
import { takeAllFlags, takeFlag, takeNumber, getPositional, pushRepeated } from "../args.js";
|
|
3
|
+
import { validateFlags, wantsHelp } from "../util.js";
|
|
4
|
+
import { formatCountLine } from "../format.js";
|
|
5
|
+
import { AxiError, mapWranglerError } from "../errors.js";
|
|
6
|
+
import { addExtraDefs, parseFields, } from "../fields.js";
|
|
7
|
+
import { custom, field, relativeTime, pluck, renderDetail, renderHelp, renderList, renderOutput, } from "../toon.js";
|
|
8
|
+
import { renderListBlock } from "../list.js";
|
|
9
|
+
const versionsAvailable = {
|
|
10
|
+
modified: { def: relativeTime("modified_on", "modified") },
|
|
11
|
+
deployment: { def: pluck("resources", "script", "deployment") },
|
|
12
|
+
};
|
|
13
|
+
const deploymentsAvailable = {
|
|
14
|
+
modified: { def: relativeTime("modified_on", "modified") },
|
|
15
|
+
source: { def: pluck("source", "type", "source") },
|
|
16
|
+
};
|
|
17
|
+
const DEPLOY_FLAGS = [
|
|
18
|
+
"--name", "--tag", "--message", "--no-bundle", "--outdir", "--outfile",
|
|
19
|
+
"--compatibility-date", "--compatibility-flags", "--latest", "--assets",
|
|
20
|
+
"--var", "--define", "--alias", "--tsconfig", "--minify", "--dry-run",
|
|
21
|
+
"--secrets-file", "--keep-vars", "--strict", "--triggers", "--routes",
|
|
22
|
+
"--domains", "--dispatch-namespace", "--autoconfig", "--json", "--full",
|
|
23
|
+
];
|
|
24
|
+
/** Pass-through filter flags for `wrangler tail` (single-valued). */
|
|
25
|
+
const TAIL_VALUE_FLAGS = ["--search", "--sampling-rate", "--version-id"];
|
|
26
|
+
/** Pass-through filter flags for `wrangler tail` (repeatable). */
|
|
27
|
+
const TAIL_ARRAY_FLAGS = ["--status", "--method", "--ip", "--header"];
|
|
28
|
+
/** Our own control flags on top of the wrangler filters. */
|
|
29
|
+
const TAIL_VALID_FLAGS = [
|
|
30
|
+
"--max-entries", "--timeout", "--json", "--full",
|
|
31
|
+
...TAIL_VALUE_FLAGS,
|
|
32
|
+
...TAIL_ARRAY_FLAGS,
|
|
33
|
+
];
|
|
34
|
+
export const workersCommand = {
|
|
35
|
+
name: "workers",
|
|
36
|
+
aliases: ["worker"],
|
|
37
|
+
description: "manage Workers (deploy, versions, deployments, tail)",
|
|
38
|
+
help: [
|
|
39
|
+
"Usage: wrangler-axi workers <deploy|versions|deployments|tail> [args]",
|
|
40
|
+
" deploy — deploy a Worker (mutating)",
|
|
41
|
+
" versions — list/upload versioned deployments",
|
|
42
|
+
" deployments — list recent deployments",
|
|
43
|
+
" tail — stream bounded live logs (stops at --max-entries or --timeout)",
|
|
44
|
+
"Example: wrangler-axi workers deployments list --name my-worker",
|
|
45
|
+
],
|
|
46
|
+
async run(ctx, args) {
|
|
47
|
+
const sub = args[0];
|
|
48
|
+
if (sub === undefined) {
|
|
49
|
+
return { stdout: renderHelp(workersCommand.help), exitCode: 0 };
|
|
50
|
+
}
|
|
51
|
+
if (sub.startsWith("-")) {
|
|
52
|
+
return { stdout: renderHelp(workersCommand.help), exitCode: 0 };
|
|
53
|
+
}
|
|
54
|
+
const rest = args.slice(1);
|
|
55
|
+
switch (sub) {
|
|
56
|
+
case "deploy":
|
|
57
|
+
return deploy(ctx, rest);
|
|
58
|
+
case "versions":
|
|
59
|
+
return versions(ctx, rest);
|
|
60
|
+
case "deployments":
|
|
61
|
+
return deployments(ctx, rest);
|
|
62
|
+
case "tail":
|
|
63
|
+
return tail(ctx, rest);
|
|
64
|
+
default:
|
|
65
|
+
const validSubcommands = ["deploy", "versions", "deployments", "tail"];
|
|
66
|
+
throw new AxiError(`Unknown workers subcommand "${sub}". Valid subcommands are: ${validSubcommands.join(", ")}`, "VALIDATION_ERROR");
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
async function deploy(ctx, args) {
|
|
71
|
+
if (wantsHelp(args)) {
|
|
72
|
+
return {
|
|
73
|
+
stdout: renderHelp([
|
|
74
|
+
"Usage: wrangler-axi workers deploy [path] [--name <worker>] [flags]",
|
|
75
|
+
"Deploys a Worker. Mirrors wrangler deploy; pass through flags like --compatibility-date, --var, --minify, --full.",
|
|
76
|
+
"Use --dry-run to preview without deploying.",
|
|
77
|
+
]),
|
|
78
|
+
exitCode: 0,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const userJson = takeAllFlags(args, ["--json"]).length > 0;
|
|
82
|
+
const full = takeAllFlags(args, ["--full"]).length > 0;
|
|
83
|
+
validateFlags(args, DEPLOY_FLAGS, "workers deploy");
|
|
84
|
+
const wArgs = ["deploy"];
|
|
85
|
+
if (ctx.account) {
|
|
86
|
+
wArgs.push("--account", ctx.account);
|
|
87
|
+
}
|
|
88
|
+
// Pass through single-value flags verbatim BEFORE extracting the positional path.
|
|
89
|
+
for (const f of ["--name", "--tag", "--message", "--compatibility-date", "--assets", "--outdir", "--outfile", "--tsconfig", "--secrets-file", "--dispatch-namespace"]) {
|
|
90
|
+
const v = takeFlag(args, f);
|
|
91
|
+
if (v !== undefined) {
|
|
92
|
+
wArgs.push(f, v);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Pass through repeatable flags (space or = form).
|
|
96
|
+
let aliasOut = "";
|
|
97
|
+
for (const f of ["--compatibility-flags", "--var", "--define", "--alias", "--routes", "--domains", "--triggers"]) {
|
|
98
|
+
const values = extractRepeatable(args, f);
|
|
99
|
+
if (values.length > 0) {
|
|
100
|
+
pushRepeated(wArgs, f, values);
|
|
101
|
+
if (f === "--alias") {
|
|
102
|
+
aliasOut = values.join(", ");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Pass through boolean flags.
|
|
107
|
+
for (const f of ["--no-bundle", "--latest", "--minify", "--dry-run", "--keep-vars", "--strict", "--autoconfig"]) {
|
|
108
|
+
if (takeAllFlags(args, [f]).length > 0) {
|
|
109
|
+
wArgs.push(f);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const path = args.filter((a) => !a.startsWith("-"))[0];
|
|
113
|
+
if (path) {
|
|
114
|
+
wArgs.push(path);
|
|
115
|
+
}
|
|
116
|
+
// Add --json to get structured output
|
|
117
|
+
wArgs.push("--json");
|
|
118
|
+
const { stdout } = await callWrangler(ctx.runner, wArgs);
|
|
119
|
+
let version = "";
|
|
120
|
+
let url = "";
|
|
121
|
+
try {
|
|
122
|
+
const json = JSON.parse(stdout);
|
|
123
|
+
// Assuming the structure: { result: { id: "...", deployment: { url: "..." } } }
|
|
124
|
+
if (json.result && json.result.id) {
|
|
125
|
+
version = json.result.id;
|
|
126
|
+
}
|
|
127
|
+
if (json.result && json.result.deployment && json.result.deployment.url) {
|
|
128
|
+
url = json.result.deployment.url;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// If parsing fails, we leave version and url empty
|
|
133
|
+
}
|
|
134
|
+
if (userJson) {
|
|
135
|
+
return { stdout, exitCode: 0 };
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
stdout: renderDetail("deploy", { path: path ?? "(config)", status: "deployed", version, url, alias: aliasOut }, {
|
|
139
|
+
path: custom((d) => d.path),
|
|
140
|
+
status: custom((d) => d.status),
|
|
141
|
+
version: custom((d) => d.version),
|
|
142
|
+
url: custom((d) => d.url),
|
|
143
|
+
alias: custom((d) => d.alias),
|
|
144
|
+
output: custom(() => {
|
|
145
|
+
const truncated = truncateTail(stdout.trim(), 120, full);
|
|
146
|
+
return truncated || "(no output)";
|
|
147
|
+
}),
|
|
148
|
+
}),
|
|
149
|
+
exitCode: 0,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function extractRepeatable(args, flag) {
|
|
153
|
+
const values = [];
|
|
154
|
+
for (let i = 0; i < args.length; i++) {
|
|
155
|
+
const a = args[i];
|
|
156
|
+
if (a === flag || a.startsWith(`${flag}=`)) {
|
|
157
|
+
if (a.startsWith(`${flag}=`)) {
|
|
158
|
+
values.push(a.slice(flag.length + 1));
|
|
159
|
+
args.splice(i, 1);
|
|
160
|
+
i--;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const v = args[i + 1];
|
|
164
|
+
if (v !== undefined && !v.startsWith("-")) {
|
|
165
|
+
values.push(v);
|
|
166
|
+
args.splice(i, 2);
|
|
167
|
+
i--;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
args.splice(i, 1);
|
|
171
|
+
i--;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return values;
|
|
177
|
+
}
|
|
178
|
+
async function versions(ctx, args) {
|
|
179
|
+
if (wantsHelp(args)) {
|
|
180
|
+
return {
|
|
181
|
+
stdout: renderHelp([
|
|
182
|
+
"Usage: wrangler-axi workers versions list [--name <worker>]",
|
|
183
|
+
"Flags: --name <worker>, --fields, --limit <n> (default 20), --full, --json",
|
|
184
|
+
]),
|
|
185
|
+
exitCode: 0,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const sub = args[0];
|
|
189
|
+
if (sub !== undefined && sub !== "list" && !sub.startsWith("-")) {
|
|
190
|
+
throw new AxiError(`Unknown versions subcommand "${sub}"`, "VALIDATION_ERROR", [
|
|
191
|
+
"Run `wrangler-axi workers versions --help` for valid subcommands",
|
|
192
|
+
]);
|
|
193
|
+
}
|
|
194
|
+
const listArgs = sub === "list" ? args.slice(1) : args;
|
|
195
|
+
const json = takeAllFlags(listArgs, ["--json"]).length > 0;
|
|
196
|
+
const full = takeAllFlags(listArgs, ["--full"]).length > 0;
|
|
197
|
+
validateFlags(listArgs, ["--name", "--fields", "--limit", "--full", "--json"], "workers versions list");
|
|
198
|
+
const wArgs = ["versions", "list", "--json"];
|
|
199
|
+
if (ctx.account) {
|
|
200
|
+
wArgs.push("--account", ctx.account);
|
|
201
|
+
}
|
|
202
|
+
const worker = takeFlag(listArgs, "--name");
|
|
203
|
+
if (worker) {
|
|
204
|
+
wArgs.push("--name", worker);
|
|
205
|
+
}
|
|
206
|
+
const limit = full ? undefined : (takeNumber(listArgs, "--limit") ?? 20);
|
|
207
|
+
const fieldsArg = takeFlag(listArgs, "--fields");
|
|
208
|
+
const { stdout } = await callWrangler(ctx.runner, wArgs);
|
|
209
|
+
if (json) {
|
|
210
|
+
return { stdout, exitCode: 0 };
|
|
211
|
+
}
|
|
212
|
+
const items = parseJson(stdout, "versions");
|
|
213
|
+
const { extraDefs } = parseFields(fieldsArg, versionsAvailable);
|
|
214
|
+
const schema = addExtraDefs({
|
|
215
|
+
id: field("id"),
|
|
216
|
+
created: relativeTime("created_on", "created"),
|
|
217
|
+
}, extraDefs);
|
|
218
|
+
return {
|
|
219
|
+
stdout: renderListBlock({
|
|
220
|
+
noun: "versions",
|
|
221
|
+
items,
|
|
222
|
+
schema,
|
|
223
|
+
limit,
|
|
224
|
+
empty: "versions: no versions found for this worker",
|
|
225
|
+
truncatedHint: "Run `wrangler-axi workers versions list --json` for the full set wrangler returns",
|
|
226
|
+
}),
|
|
227
|
+
exitCode: 0,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async function deployments(ctx, args) {
|
|
231
|
+
if (wantsHelp(args)) {
|
|
232
|
+
return {
|
|
233
|
+
stdout: renderHelp([
|
|
234
|
+
"Usage: wrangler-axi workers deployments list [--name <worker>]",
|
|
235
|
+
"Flags: --name <worker>, --fields, --limit <n> (default 20), --full, --json",
|
|
236
|
+
"Note: wrangler caps this list to the 10 most recent deployments.",
|
|
237
|
+
]),
|
|
238
|
+
exitCode: 0,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const sub = args[0];
|
|
242
|
+
if (sub !== undefined && sub !== "list" && !sub.startsWith("-")) {
|
|
243
|
+
throw new AxiError(`Unknown deployments subcommand "${sub}"`, "VALIDATION_ERROR", [
|
|
244
|
+
"Run `wrangler-axi workers deployments --help` for valid subcommands",
|
|
245
|
+
]);
|
|
246
|
+
}
|
|
247
|
+
const listArgs = sub === "list" ? args.slice(1) : args;
|
|
248
|
+
const json = takeAllFlags(listArgs, ["--json"]).length > 0;
|
|
249
|
+
const full = takeAllFlags(listArgs, ["--full"]).length > 0;
|
|
250
|
+
validateFlags(listArgs, ["--name", "--fields", "--limit", "--full", "--json"], "workers deployments list");
|
|
251
|
+
const wArgs = ["deployments", "list", "--json"];
|
|
252
|
+
if (ctx.account) {
|
|
253
|
+
wArgs.push("--account", ctx.account);
|
|
254
|
+
}
|
|
255
|
+
const worker = takeFlag(listArgs, "--name");
|
|
256
|
+
if (worker) {
|
|
257
|
+
wArgs.push("--name", worker);
|
|
258
|
+
}
|
|
259
|
+
const limit = full ? undefined : (takeNumber(listArgs, "--limit") ?? 20);
|
|
260
|
+
const fieldsArg = takeFlag(listArgs, "--fields");
|
|
261
|
+
const { stdout } = await callWrangler(ctx.runner, wArgs);
|
|
262
|
+
if (json) {
|
|
263
|
+
return { stdout, exitCode: 0 };
|
|
264
|
+
}
|
|
265
|
+
const items = parseJson(stdout, "deployments");
|
|
266
|
+
const { extraDefs } = parseFields(fieldsArg, deploymentsAvailable);
|
|
267
|
+
const schema = addExtraDefs({
|
|
268
|
+
id: field("id"),
|
|
269
|
+
url: field("url"),
|
|
270
|
+
created: relativeTime("created_on", "created"),
|
|
271
|
+
}, extraDefs);
|
|
272
|
+
return {
|
|
273
|
+
stdout: renderListBlock({
|
|
274
|
+
noun: "deployments",
|
|
275
|
+
items,
|
|
276
|
+
schema,
|
|
277
|
+
limit,
|
|
278
|
+
empty: "deployments: no deployments found",
|
|
279
|
+
truncatedHint: "Run `wrangler-axi workers deployments list --json` for the full set wrangler returns",
|
|
280
|
+
}),
|
|
281
|
+
exitCode: 0,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/** Truncate a summary value to a readable width, noting how much was cut. */
|
|
285
|
+
function truncateTail(raw, max = 120, full = false) {
|
|
286
|
+
if (full || raw.length <= max) {
|
|
287
|
+
return raw;
|
|
288
|
+
}
|
|
289
|
+
const cut = raw.length - max;
|
|
290
|
+
return `${raw.slice(0, max)}\u2026 (+${cut} more)`;
|
|
291
|
+
}
|
|
292
|
+
/** Parse one raw `wrangler tail --format json` line into a compact row. */
|
|
293
|
+
function parseTailLine(line, full) {
|
|
294
|
+
let parsed;
|
|
295
|
+
try {
|
|
296
|
+
parsed = JSON.parse(line);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return {
|
|
300
|
+
event: "raw",
|
|
301
|
+
outcome: "unknown",
|
|
302
|
+
summary: truncateTail(line, 120, full),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const ev = parsed.event ?? {};
|
|
306
|
+
const type = ev.type ?? parsed.eventType ?? "unknown";
|
|
307
|
+
const outcome = ev.outcome ?? parsed.outcome ?? "unknown";
|
|
308
|
+
if (type === "request" && ev.request) {
|
|
309
|
+
const method = ev.request.method ?? "?";
|
|
310
|
+
const url = ev.request.url ?? "";
|
|
311
|
+
return { event: type, outcome, summary: truncateTail(`${method} ${url}`, 120, full) };
|
|
312
|
+
}
|
|
313
|
+
const exc = ev.exception ?? ev.error;
|
|
314
|
+
if (exc) {
|
|
315
|
+
return {
|
|
316
|
+
event: type,
|
|
317
|
+
outcome,
|
|
318
|
+
summary: truncateTail(`${exc.name ?? "error"}: ${exc.message ?? ""}`, 120, full),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (ev.message) {
|
|
322
|
+
return { event: type, outcome, summary: truncateTail(ev.message, 120, full) };
|
|
323
|
+
}
|
|
324
|
+
return { event: type, outcome, summary: truncateTail(line, 120, full) };
|
|
325
|
+
}
|
|
326
|
+
/** Stopped-by size hint, so agents know the stream was bounded and how to widen it. */
|
|
327
|
+
function tailStopHint(result) {
|
|
328
|
+
if (result.stoppedBy === "limit") {
|
|
329
|
+
return `captured ${result.captured} entries (stopped at --max-entries ${result.maxEntries}); raise --max-entries to keep streaming`;
|
|
330
|
+
}
|
|
331
|
+
if (result.stoppedBy === "timeout") {
|
|
332
|
+
return `stopped after ${result.timeoutSec}s (--timeout) before --max-entries; raise --timeout to sample longer`;
|
|
333
|
+
}
|
|
334
|
+
return `stream ended (exit ${result.exitCode}) before limits — retry, or use --status to filter`;
|
|
335
|
+
}
|
|
336
|
+
async function tail(ctx, args) {
|
|
337
|
+
if (wantsHelp(args)) {
|
|
338
|
+
return {
|
|
339
|
+
stdout: renderHelp([
|
|
340
|
+
"Usage: wrangler-axi workers tail [worker] [flags]",
|
|
341
|
+
"Streams live Worker logs with a hard bound: stops at --max-entries or --timeout.",
|
|
342
|
+
`Flags: --max-entries <n> (default 20), --timeout <sec> (default 15), --json (raw lines), --full (disable truncation)`,
|
|
343
|
+
`Filters (passed through): ${TAIL_VALUE_FLAGS.join(", ")}, ${TAIL_ARRAY_FLAGS.join(", ")}`,
|
|
344
|
+
"Example: wrangler-axi workers tail my-worker --search \"error\" --status error --max-entries 5",
|
|
345
|
+
]),
|
|
346
|
+
exitCode: 0,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
validateFlags(args, TAIL_VALID_FLAGS, "workers tail");
|
|
350
|
+
const json = takeAllFlags(args, ["--json"]).length > 0;
|
|
351
|
+
const full = takeAllFlags(args, ["--full"]).length > 0;
|
|
352
|
+
let maxEntries = takeNumber(args, "--max-entries");
|
|
353
|
+
if (maxEntries === undefined) {
|
|
354
|
+
maxEntries = 20;
|
|
355
|
+
}
|
|
356
|
+
else if (!Number.isInteger(maxEntries)) {
|
|
357
|
+
throw new AxiError("--max-entries must be an integer", "VALIDATION_ERROR");
|
|
358
|
+
}
|
|
359
|
+
let timeoutSec = takeNumber(args, "--timeout");
|
|
360
|
+
if (timeoutSec === undefined) {
|
|
361
|
+
timeoutSec = 15;
|
|
362
|
+
}
|
|
363
|
+
else if (!Number.isInteger(timeoutSec)) {
|
|
364
|
+
throw new AxiError("--timeout must be an integer", "VALIDATION_ERROR");
|
|
365
|
+
}
|
|
366
|
+
if (maxEntries <= 0 && timeoutSec <= 0) {
|
|
367
|
+
throw new AxiError("workers tail needs a bound — set --max-entries > 0 or --timeout > 0", "VALIDATION_ERROR");
|
|
368
|
+
}
|
|
369
|
+
const wArgs = ["tail", "--format", "json"];
|
|
370
|
+
if (ctx.account) {
|
|
371
|
+
wArgs.push("--account", ctx.account);
|
|
372
|
+
}
|
|
373
|
+
for (const f of TAIL_VALUE_FLAGS) {
|
|
374
|
+
const v = takeFlag(args, f);
|
|
375
|
+
if (v !== undefined) {
|
|
376
|
+
wArgs.push(f, v);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
for (const f of TAIL_ARRAY_FLAGS) {
|
|
380
|
+
const values = extractRepeatable(args, f);
|
|
381
|
+
if (values.length > 0) {
|
|
382
|
+
pushRepeated(wArgs, f, values);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const worker = getPositional(args);
|
|
386
|
+
if (worker) {
|
|
387
|
+
wArgs.push(worker);
|
|
388
|
+
}
|
|
389
|
+
const tailFn = ctx.runner.tail;
|
|
390
|
+
if (!tailFn) {
|
|
391
|
+
throw new AxiError("workers tail requires a runner with streaming support", "UNKNOWN");
|
|
392
|
+
}
|
|
393
|
+
const result = await tailFn({
|
|
394
|
+
args: wArgs,
|
|
395
|
+
maxEntries,
|
|
396
|
+
timeoutMs: timeoutSec * 1000,
|
|
397
|
+
});
|
|
398
|
+
if (result.exitCode !== 0 && result.stoppedBy === "exit") {
|
|
399
|
+
throw mapWranglerError(result.stderr, result.exitCode);
|
|
400
|
+
}
|
|
401
|
+
if (json) {
|
|
402
|
+
return {
|
|
403
|
+
stdout: result.entries.length > 0 ? result.entries.join("\n") : "",
|
|
404
|
+
exitCode: 0,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const rows = result.entries.map((line) => parseTailLine(line, full));
|
|
408
|
+
const schema = {
|
|
409
|
+
event: field("event"),
|
|
410
|
+
outcome: field("outcome"),
|
|
411
|
+
summary: field("summary"),
|
|
412
|
+
};
|
|
413
|
+
if (rows.length === 0) {
|
|
414
|
+
const hint = `no log events in ${timeoutSec}s — the worker may have no traffic`;
|
|
415
|
+
return {
|
|
416
|
+
stdout: renderOutput([
|
|
417
|
+
formatCountLine({ count: 0, limit: maxEntries > 0 ? maxEntries : undefined }),
|
|
418
|
+
`tail: ${hint}`,
|
|
419
|
+
]),
|
|
420
|
+
exitCode: 0,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
stdout: renderOutput([
|
|
425
|
+
formatCountLine({ count: rows.length, limit: maxEntries > 0 ? maxEntries : undefined }),
|
|
426
|
+
renderList(`tail[${rows.length}]`, rows, schema),
|
|
427
|
+
renderHelp([tailStopHint({ ...result, maxEntries, timeoutSec, captured: rows.length })]),
|
|
428
|
+
]),
|
|
429
|
+
exitCode: 0,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { AxiError, exitCodeForError } from "axi-sdk-js";
|
|
2
|
+
export { AxiError, exitCodeForError };
|
|
3
|
+
export declare function stripAnsi(text: string): string;
|
|
4
|
+
/** Translate wrangler stderr + exit code into a structured AxiError. */
|
|
5
|
+
export declare function mapWranglerError(stderr: string, exitCode: number): AxiError;
|
|
6
|
+
export declare function wranglerNotInstalledError(): AxiError;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AxiError, exitCodeForError } from "axi-sdk-js";
|
|
2
|
+
export { AxiError, exitCodeForError };
|
|
3
|
+
function firstErrorLine(stderr) {
|
|
4
|
+
return stderr.trim().split("\n")[0] ?? "";
|
|
5
|
+
}
|
|
6
|
+
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
7
|
+
export function stripAnsi(text) {
|
|
8
|
+
return text.replace(ANSI_RE, "");
|
|
9
|
+
}
|
|
10
|
+
const patterns = [
|
|
11
|
+
{
|
|
12
|
+
pattern: /set a CLOUDFLARE_API_TOKEN environment variable/i,
|
|
13
|
+
code: "NOT_AUTHENTICATED",
|
|
14
|
+
message: () => "No Cloudflare credentials available in this non-interactive environment",
|
|
15
|
+
suggestions: () => [
|
|
16
|
+
"Set CLOUDFLARE_API_TOKEN in your environment",
|
|
17
|
+
"Run `npx wrangler login` once to authenticate the wrangler CLI",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pattern: /You are not authenticated\./i,
|
|
22
|
+
code: "NOT_AUTHENTICATED",
|
|
23
|
+
message: () => "You are not authenticated with Cloudflare",
|
|
24
|
+
suggestions: () => [
|
|
25
|
+
"Set CLOUDFLARE_API_TOKEN in your environment",
|
|
26
|
+
"Run `npx wrangler login` once to authenticate the wrangler CLI",
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
pattern: /Authentication needs a valid API token/i,
|
|
31
|
+
code: "AUTH_INVALID",
|
|
32
|
+
message: (m) => m[0] || "Authentication needs a valid API token",
|
|
33
|
+
suggestions: () => ["Set a valid CLOUDFLARE_API_TOKEN in your environment"],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pattern: /Unable to find.*(?:namespace|bucket|database|worker|project)/i,
|
|
37
|
+
code: "NOT_FOUND",
|
|
38
|
+
message: (m) => m[0] || "Requested resource was not found",
|
|
39
|
+
suggestions: () => ["Check the resource name and account"],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /The specified.*does not exist/i,
|
|
43
|
+
code: "NOT_FOUND",
|
|
44
|
+
message: (m) => m[0] || "Resource does not exist",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
pattern: /not found/i,
|
|
48
|
+
code: "NOT_FOUND",
|
|
49
|
+
message: (m) => m[0] || "Resource not found",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
pattern: /Unknown (?:argument|command)/i,
|
|
53
|
+
code: "VALIDATION_ERROR",
|
|
54
|
+
message: (m) => m[0] || "Unknown argument or command",
|
|
55
|
+
suggestions: () => ["Run `wrangler-axi <command> --help` for valid flags"],
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
/** Translate wrangler stderr + exit code into a structured AxiError. */
|
|
59
|
+
export function mapWranglerError(stderr, exitCode) {
|
|
60
|
+
// wrangler colorizes its output when it expects a TTY; strip escapes so
|
|
61
|
+
// patterns match and messages stay clean regardless of the environment.
|
|
62
|
+
const clean = stripAnsi(stderr);
|
|
63
|
+
for (const p of patterns) {
|
|
64
|
+
const match = clean.match(p.pattern);
|
|
65
|
+
if (match) {
|
|
66
|
+
return new AxiError(p.message(match, clean), p.code, p.suggestions ? p.suggestions(match) : []);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const line = firstErrorLine(clean);
|
|
70
|
+
if (/not found/i.test(clean)) {
|
|
71
|
+
return new AxiError(line || "Resource not found", "NOT_FOUND");
|
|
72
|
+
}
|
|
73
|
+
return new AxiError(line || `wrangler exited with code ${exitCode}`, "UNKNOWN");
|
|
74
|
+
}
|
|
75
|
+
export function wranglerNotInstalledError() {
|
|
76
|
+
return new AxiError("wrangler CLI is not installed — install it via `npm install -g wrangler`", "WRANGLER_NOT_INSTALLED");
|
|
77
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FieldDef } from "./toon.js";
|
|
2
|
+
export interface AvailableField {
|
|
3
|
+
def?: FieldDef;
|
|
4
|
+
key?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Validate a comma-separated --fields argument against a map of known fields.
|
|
8
|
+
* Unknown fields fail loudly (VALIDATION_ERROR, exit 2).
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseFields(fieldsArg: string | undefined, available: Record<string, AvailableField>): {
|
|
11
|
+
extraDefs: [string, FieldDef][];
|
|
12
|
+
extraKeys: string[];
|
|
13
|
+
};
|
|
14
|
+
/** Merge extra defs into a base schema (returning a new object). */
|
|
15
|
+
export declare function addExtraDefs(base: Record<string, FieldDef>, extra: [string, FieldDef][]): Record<string, FieldDef>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
/**
|
|
3
|
+
* Validate a comma-separated --fields argument against a map of known fields.
|
|
4
|
+
* Unknown fields fail loudly (VALIDATION_ERROR, exit 2).
|
|
5
|
+
*/
|
|
6
|
+
export function parseFields(fieldsArg, available) {
|
|
7
|
+
if (fieldsArg === undefined) {
|
|
8
|
+
return { extraDefs: [], extraKeys: [] };
|
|
9
|
+
}
|
|
10
|
+
const names = [...new Set(fieldsArg.split(",").map((s) => s.trim()).filter(Boolean))];
|
|
11
|
+
const unknown = names.filter((n) => !(n in available));
|
|
12
|
+
if (unknown.length > 0) {
|
|
13
|
+
throw new AxiError(`Unknown field(s): ${unknown.join(", ")}. Available: ${Object.keys(available)
|
|
14
|
+
.sort()
|
|
15
|
+
.join(", ")}`, "VALIDATION_ERROR");
|
|
16
|
+
}
|
|
17
|
+
const extraDefs = [];
|
|
18
|
+
const extraKeys = [];
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
if (available[name].def) {
|
|
21
|
+
extraDefs.push([name, available[name].def]);
|
|
22
|
+
}
|
|
23
|
+
if (available[name].key) {
|
|
24
|
+
extraKeys.push(available[name].key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { extraDefs, extraKeys };
|
|
28
|
+
}
|
|
29
|
+
/** Merge extra defs into a base schema (returning a new object). */
|
|
30
|
+
export function addExtraDefs(base, extra) {
|
|
31
|
+
const merged = { ...base };
|
|
32
|
+
for (const [name, def] of extra) {
|
|
33
|
+
merged[name] = def;
|
|
34
|
+
}
|
|
35
|
+
return merged;
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CountLineOptions {
|
|
2
|
+
count: number;
|
|
3
|
+
limit?: number;
|
|
4
|
+
totalCount?: number;
|
|
5
|
+
apiLimitHit?: boolean;
|
|
6
|
+
displayLimit?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Render a definitive `count:` line satisfying AXI's pre-computed aggregate +
|
|
10
|
+
* definitive empty state principles. All diagnostics (totals, truncation, api
|
|
11
|
+
* limits) are folded into the single line so agents don't paginate blindly.
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatCountLine(opts: CountLineOptions): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a definitive `count:` line satisfying AXI's pre-computed aggregate +
|
|
3
|
+
* definitive empty state principles. All diagnostics (totals, truncation, api
|
|
4
|
+
* limits) are folded into the single line so agents don't paginate blindly.
|
|
5
|
+
*/
|
|
6
|
+
export function formatCountLine(opts) {
|
|
7
|
+
const { count, limit, totalCount, apiLimitHit, displayLimit } = opts;
|
|
8
|
+
if (apiLimitHit) {
|
|
9
|
+
return `count: ${count}+ (api limit reached)`;
|
|
10
|
+
}
|
|
11
|
+
if (totalCount !== undefined && totalCount >= count) {
|
|
12
|
+
return `count: ${count} of ${totalCount} total`;
|
|
13
|
+
}
|
|
14
|
+
if (displayLimit !== undefined && count > displayLimit) {
|
|
15
|
+
return `count: ${count} (showing first ${displayLimit})`;
|
|
16
|
+
}
|
|
17
|
+
if (limit !== undefined && count === limit && count > 0) {
|
|
18
|
+
return `count: ${count} (showing first ${count})`;
|
|
19
|
+
}
|
|
20
|
+
return `count: ${count}`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Schema } from "./toon.js";
|
|
2
|
+
export interface ListBlockOptions {
|
|
3
|
+
noun: string;
|
|
4
|
+
items: unknown[];
|
|
5
|
+
schema: Schema;
|
|
6
|
+
limit?: number;
|
|
7
|
+
/** totalCount shown in the count line when it differs from items.length. */
|
|
8
|
+
totalCount?: number;
|
|
9
|
+
empty: string;
|
|
10
|
+
truncatedHint?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Render a definitive list block: aggregate count line, item list (respecting a
|
|
14
|
+
* display limit), a definitive empty state, and a truncated-list hint. This is
|
|
15
|
+
* the single place every `--limit` list renders, keeping empty/truncated output
|
|
16
|
+
* consistent across commands.
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderListBlock(opts: ListBlockOptions): string;
|
package/dist/src/list.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { formatCountLine } from "./format.js";
|
|
2
|
+
import { renderHelp, renderList } from "./toon.js";
|
|
3
|
+
/**
|
|
4
|
+
* Render a definitive list block: aggregate count line, item list (respecting a
|
|
5
|
+
* display limit), a definitive empty state, and a truncated-list hint. This is
|
|
6
|
+
* the single place every `--limit` list renders, keeping empty/truncated output
|
|
7
|
+
* consistent across commands.
|
|
8
|
+
*/
|
|
9
|
+
export function renderListBlock(opts) {
|
|
10
|
+
const { noun, items, schema, limit, totalCount, empty, truncatedHint } = opts;
|
|
11
|
+
const display = limit !== undefined ? items.slice(0, limit) : items;
|
|
12
|
+
const total = totalCount ?? items.length;
|
|
13
|
+
const countLine = formatCountLine({ count: total, limit, displayLimit: display.length });
|
|
14
|
+
if (items.length === 0) {
|
|
15
|
+
return [countLine, empty].filter(Boolean).join("\n");
|
|
16
|
+
}
|
|
17
|
+
const blocks = [countLine];
|
|
18
|
+
blocks.push(renderList(`${noun}[${display.length}]`, display, schema));
|
|
19
|
+
if (truncatedHint && limit !== undefined && items.length > limit) {
|
|
20
|
+
blocks.push(renderHelp([truncatedHint]));
|
|
21
|
+
}
|
|
22
|
+
return blocks.filter(Boolean).join("\n");
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function readStdin() {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
let data = "";
|
|
4
|
+
process.stdin.setEncoding("utf8");
|
|
5
|
+
process.stdin.on("data", (chunk) => {
|
|
6
|
+
data += chunk;
|
|
7
|
+
});
|
|
8
|
+
process.stdin.on("end", () => resolve(data));
|
|
9
|
+
process.stdin.on("error", (err) => reject(err));
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function isStdinTTY() {
|
|
13
|
+
return Boolean(process.stdin.isTTY);
|
|
14
|
+
}
|