trickle-cli 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/dist/api-client.d.ts +208 -0
- package/dist/api-client.js +237 -0
- package/dist/commands/annotate.d.ts +6 -0
- package/dist/commands/annotate.js +433 -0
- package/dist/commands/audit.d.ts +7 -0
- package/dist/commands/audit.js +82 -0
- package/dist/commands/auto.d.ts +8 -0
- package/dist/commands/auto.js +268 -0
- package/dist/commands/capture.d.ts +14 -0
- package/dist/commands/capture.js +271 -0
- package/dist/commands/check.d.ts +6 -0
- package/dist/commands/check.js +408 -0
- package/dist/commands/codegen.d.ts +21 -0
- package/dist/commands/codegen.js +129 -0
- package/dist/commands/coverage.d.ts +13 -0
- package/dist/commands/coverage.js +126 -0
- package/dist/commands/dashboard.d.ts +1 -0
- package/dist/commands/dashboard.js +83 -0
- package/dist/commands/dev.d.ts +14 -0
- package/dist/commands/dev.js +319 -0
- package/dist/commands/diff.d.ts +7 -0
- package/dist/commands/diff.js +79 -0
- package/dist/commands/docs.d.ts +13 -0
- package/dist/commands/docs.js +383 -0
- package/dist/commands/errors.d.ts +7 -0
- package/dist/commands/errors.js +180 -0
- package/dist/commands/export.d.ts +18 -0
- package/dist/commands/export.js +238 -0
- package/dist/commands/functions.d.ts +6 -0
- package/dist/commands/functions.js +71 -0
- package/dist/commands/infer.d.ts +14 -0
- package/dist/commands/infer.js +275 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +395 -0
- package/dist/commands/mock.d.ts +5 -0
- package/dist/commands/mock.js +232 -0
- package/dist/commands/openapi.d.ts +8 -0
- package/dist/commands/openapi.js +82 -0
- package/dist/commands/overview.d.ts +11 -0
- package/dist/commands/overview.js +266 -0
- package/dist/commands/pack.d.ts +11 -0
- package/dist/commands/pack.js +133 -0
- package/dist/commands/proxy.d.ts +13 -0
- package/dist/commands/proxy.js +312 -0
- package/dist/commands/replay.d.ts +14 -0
- package/dist/commands/replay.js +289 -0
- package/dist/commands/run.d.ts +17 -0
- package/dist/commands/run.js +997 -0
- package/dist/commands/sample.d.ts +13 -0
- package/dist/commands/sample.js +260 -0
- package/dist/commands/search.d.ts +5 -0
- package/dist/commands/search.js +80 -0
- package/dist/commands/stubs.d.ts +6 -0
- package/dist/commands/stubs.js +187 -0
- package/dist/commands/tail.d.ts +4 -0
- package/dist/commands/tail.js +76 -0
- package/dist/commands/test-gen.d.ts +13 -0
- package/dist/commands/test-gen.js +237 -0
- package/dist/commands/trace.d.ts +14 -0
- package/dist/commands/trace.js +417 -0
- package/dist/commands/types.d.ts +7 -0
- package/dist/commands/types.js +128 -0
- package/dist/commands/unpack.d.ts +11 -0
- package/dist/commands/unpack.js +166 -0
- package/dist/commands/validate.d.ts +13 -0
- package/dist/commands/validate.js +310 -0
- package/dist/commands/watch.d.ts +9 -0
- package/dist/commands/watch.js +267 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +66 -0
- package/dist/formatters/diff-formatter.d.ts +5 -0
- package/dist/formatters/diff-formatter.js +43 -0
- package/dist/formatters/type-formatter.d.ts +22 -0
- package/dist/formatters/type-formatter.js +135 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +419 -0
- package/dist/local-codegen.d.ts +22 -0
- package/dist/local-codegen.js +762 -0
- package/dist/ui/badges.d.ts +16 -0
- package/dist/ui/badges.js +71 -0
- package/dist/ui/helpers.d.ts +13 -0
- package/dist/ui/helpers.js +85 -0
- package/package.json +23 -0
- package/src/api-client.ts +407 -0
- package/src/commands/annotate.ts +450 -0
- package/src/commands/audit.ts +103 -0
- package/src/commands/auto.ts +268 -0
- package/src/commands/capture.ts +257 -0
- package/src/commands/check.ts +437 -0
- package/src/commands/codegen.ts +128 -0
- package/src/commands/coverage.ts +170 -0
- package/src/commands/dashboard.ts +46 -0
- package/src/commands/dev.ts +323 -0
- package/src/commands/diff.ts +99 -0
- package/src/commands/docs.ts +392 -0
- package/src/commands/errors.ts +205 -0
- package/src/commands/export.ts +287 -0
- package/src/commands/functions.ts +81 -0
- package/src/commands/infer.ts +260 -0
- package/src/commands/init.ts +419 -0
- package/src/commands/mock.ts +220 -0
- package/src/commands/openapi.ts +53 -0
- package/src/commands/overview.ts +310 -0
- package/src/commands/pack.ts +139 -0
- package/src/commands/proxy.ts +314 -0
- package/src/commands/replay.ts +356 -0
- package/src/commands/run.ts +1190 -0
- package/src/commands/sample.ts +259 -0
- package/src/commands/search.ts +107 -0
- package/src/commands/stubs.ts +211 -0
- package/src/commands/tail.ts +94 -0
- package/src/commands/test-gen.ts +236 -0
- package/src/commands/trace.ts +440 -0
- package/src/commands/types.ts +161 -0
- package/src/commands/unpack.ts +179 -0
- package/src/commands/validate.ts +368 -0
- package/src/commands/watch.ts +277 -0
- package/src/config.ts +38 -0
- package/src/formatters/diff-formatter.ts +51 -0
- package/src/formatters/type-formatter.ts +161 -0
- package/src/index.ts +454 -0
- package/src/local-codegen.ts +859 -0
- package/src/ui/badges.ts +66 -0
- package/src/ui/helpers.ts +80 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { listFunctions, listTypes, FunctionRow, TypeSnapshot } from "../api-client";
|
|
3
|
+
import { getBackendUrl } from "../config";
|
|
4
|
+
import { relativeTime } from "../ui/helpers";
|
|
5
|
+
|
|
6
|
+
export interface OverviewOptions {
|
|
7
|
+
env?: string;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface TypeNode {
|
|
12
|
+
kind: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RouteInfo {
|
|
17
|
+
name: string;
|
|
18
|
+
method: string;
|
|
19
|
+
path: string;
|
|
20
|
+
module: string;
|
|
21
|
+
environment: string;
|
|
22
|
+
lastSeen: string;
|
|
23
|
+
argsSignature: string;
|
|
24
|
+
returnSignature: string;
|
|
25
|
+
fieldCount: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `trickle overview` — Compact API overview with inline type signatures.
|
|
30
|
+
*
|
|
31
|
+
* Shows all observed routes with their return type shapes, making it easy to
|
|
32
|
+
* understand your entire API surface at a glance. Like `git log --oneline` for APIs.
|
|
33
|
+
*/
|
|
34
|
+
export async function overviewCommand(opts: OverviewOptions): Promise<void> {
|
|
35
|
+
const backendUrl = getBackendUrl();
|
|
36
|
+
|
|
37
|
+
// Check backend
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(`${backendUrl}/api/health`, { signal: AbortSignal.timeout(3000) });
|
|
40
|
+
if (!res.ok) throw new Error("not ok");
|
|
41
|
+
} catch {
|
|
42
|
+
console.error(chalk.red(`\n Cannot reach trickle backend at ${chalk.bold(backendUrl)}\n`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fetch all functions
|
|
47
|
+
const result = await listFunctions({ env: opts.env, limit: 500 });
|
|
48
|
+
const { functions } = result;
|
|
49
|
+
|
|
50
|
+
if (functions.length === 0) {
|
|
51
|
+
console.log(chalk.yellow("\n No observed routes yet."));
|
|
52
|
+
console.log(chalk.gray(" Run ") + chalk.white("trickle capture") + chalk.gray(" or ") + chalk.white("trickle dev") + chalk.gray(" to start observing.\n"));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fetch latest type snapshot for each function
|
|
57
|
+
const routes: RouteInfo[] = [];
|
|
58
|
+
for (const fn of functions) {
|
|
59
|
+
try {
|
|
60
|
+
const typesResult = await listTypes(fn.id, { env: opts.env, limit: 1 });
|
|
61
|
+
const snapshot = typesResult.snapshots[0];
|
|
62
|
+
|
|
63
|
+
const returnType = snapshot
|
|
64
|
+
? (typeof snapshot.return_type === "string"
|
|
65
|
+
? JSON.parse(snapshot.return_type)
|
|
66
|
+
: snapshot.return_type) as TypeNode
|
|
67
|
+
: null;
|
|
68
|
+
|
|
69
|
+
const argsType = snapshot
|
|
70
|
+
? (typeof snapshot.args_type === "string"
|
|
71
|
+
? JSON.parse(snapshot.args_type)
|
|
72
|
+
: snapshot.args_type) as TypeNode
|
|
73
|
+
: null;
|
|
74
|
+
|
|
75
|
+
const { method, path: routePath } = parseRoute(fn.function_name);
|
|
76
|
+
|
|
77
|
+
routes.push({
|
|
78
|
+
name: fn.function_name,
|
|
79
|
+
method,
|
|
80
|
+
path: routePath,
|
|
81
|
+
module: fn.module,
|
|
82
|
+
environment: fn.environment,
|
|
83
|
+
lastSeen: fn.last_seen_at,
|
|
84
|
+
argsSignature: argsType ? compactSignature(argsType, 60) : "",
|
|
85
|
+
returnSignature: returnType ? compactSignature(returnType, 60) : "unknown",
|
|
86
|
+
fieldCount: returnType ? countFields(returnType) : 0,
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
// Skip functions with errors
|
|
90
|
+
const { method, path: routePath } = parseRoute(fn.function_name);
|
|
91
|
+
routes.push({
|
|
92
|
+
name: fn.function_name,
|
|
93
|
+
method,
|
|
94
|
+
path: routePath,
|
|
95
|
+
module: fn.module,
|
|
96
|
+
environment: fn.environment,
|
|
97
|
+
lastSeen: fn.last_seen_at,
|
|
98
|
+
argsSignature: "",
|
|
99
|
+
returnSignature: "?",
|
|
100
|
+
fieldCount: 0,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// JSON output
|
|
106
|
+
if (opts.json) {
|
|
107
|
+
console.log(JSON.stringify({ routes, total: routes.length }, null, 2));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Group by module
|
|
112
|
+
const byModule = new Map<string, RouteInfo[]>();
|
|
113
|
+
for (const r of routes) {
|
|
114
|
+
const mod = r.module || "default";
|
|
115
|
+
if (!byModule.has(mod)) byModule.set(mod, []);
|
|
116
|
+
byModule.get(mod)!.push(r);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log(chalk.bold(" trickle overview"));
|
|
121
|
+
console.log(chalk.gray(" " + "─".repeat(60)));
|
|
122
|
+
console.log(chalk.gray(` ${routes.length} route${routes.length === 1 ? "" : "s"} observed`));
|
|
123
|
+
if (opts.env) {
|
|
124
|
+
console.log(chalk.gray(` Environment: ${opts.env}`));
|
|
125
|
+
}
|
|
126
|
+
console.log(chalk.gray(" " + "─".repeat(60)));
|
|
127
|
+
|
|
128
|
+
// Find the longest method for alignment
|
|
129
|
+
const maxMethodLen = Math.max(...routes.map((r) => r.method.length));
|
|
130
|
+
const maxPathLen = Math.min(30, Math.max(...routes.map((r) => r.path.length)));
|
|
131
|
+
|
|
132
|
+
for (const [mod, modRoutes] of byModule) {
|
|
133
|
+
console.log("");
|
|
134
|
+
if (byModule.size > 1) {
|
|
135
|
+
console.log(chalk.gray(` ┌─ ${mod}`));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Sort routes: GET before POST before PUT before DELETE, then by path
|
|
139
|
+
const methodOrder: Record<string, number> = { GET: 0, POST: 1, PUT: 2, PATCH: 3, DELETE: 4 };
|
|
140
|
+
modRoutes.sort((a, b) => {
|
|
141
|
+
const orderA = methodOrder[a.method] ?? 5;
|
|
142
|
+
const orderB = methodOrder[b.method] ?? 5;
|
|
143
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
144
|
+
return a.path.localeCompare(b.path);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
for (const route of modRoutes) {
|
|
148
|
+
const methodColor = getMethodColor(route.method);
|
|
149
|
+
const methodStr = route.method.padEnd(maxMethodLen);
|
|
150
|
+
const pathStr = route.path.padEnd(maxPathLen);
|
|
151
|
+
const age = relativeTime(route.lastSeen);
|
|
152
|
+
|
|
153
|
+
// Build the line
|
|
154
|
+
const prefix = byModule.size > 1 ? " │ " : " ";
|
|
155
|
+
const line =
|
|
156
|
+
prefix +
|
|
157
|
+
methodColor(methodStr) +
|
|
158
|
+
" " +
|
|
159
|
+
chalk.white(pathStr) +
|
|
160
|
+
chalk.gray(" → ") +
|
|
161
|
+
chalk.cyan(route.returnSignature) +
|
|
162
|
+
chalk.gray(` ${age}`);
|
|
163
|
+
|
|
164
|
+
console.log(line);
|
|
165
|
+
|
|
166
|
+
// Show request body if present and non-empty
|
|
167
|
+
if (route.argsSignature && route.argsSignature !== "{ }") {
|
|
168
|
+
const argsLine =
|
|
169
|
+
prefix +
|
|
170
|
+
" ".repeat(maxMethodLen) +
|
|
171
|
+
" " +
|
|
172
|
+
" ".repeat(maxPathLen) +
|
|
173
|
+
chalk.gray(" ← ") +
|
|
174
|
+
chalk.yellow(route.argsSignature);
|
|
175
|
+
console.log(argsLine);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (byModule.size > 1) {
|
|
180
|
+
console.log(chalk.gray(" └─"));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log("");
|
|
185
|
+
const totalFields = routes.reduce((sum, r) => sum + r.fieldCount, 0);
|
|
186
|
+
console.log(
|
|
187
|
+
chalk.gray(` ${routes.length} routes, ${totalFields} fields observed`) +
|
|
188
|
+
chalk.gray(` · ${backendUrl}`),
|
|
189
|
+
);
|
|
190
|
+
console.log("");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseRoute(functionName: string): { method: string; path: string } {
|
|
194
|
+
const spaceIdx = functionName.indexOf(" ");
|
|
195
|
+
if (spaceIdx > 0) {
|
|
196
|
+
return {
|
|
197
|
+
method: functionName.slice(0, spaceIdx),
|
|
198
|
+
path: functionName.slice(spaceIdx + 1),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return { method: "", path: functionName };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getMethodColor(method: string): (s: string) => string {
|
|
205
|
+
switch (method) {
|
|
206
|
+
case "GET": return chalk.green;
|
|
207
|
+
case "POST": return chalk.yellow;
|
|
208
|
+
case "PUT": return chalk.blue;
|
|
209
|
+
case "PATCH": return chalk.magenta;
|
|
210
|
+
case "DELETE": return chalk.red;
|
|
211
|
+
default: return chalk.white;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Render a compact type signature from a TypeNode.
|
|
217
|
+
* Truncates to maxLen characters.
|
|
218
|
+
*/
|
|
219
|
+
function compactSignature(node: TypeNode, maxLen: number): string {
|
|
220
|
+
const sig = renderCompact(node);
|
|
221
|
+
if (sig.length <= maxLen) return sig;
|
|
222
|
+
return sig.slice(0, maxLen - 1) + "…";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function renderCompact(node: TypeNode): string {
|
|
226
|
+
switch (node.kind) {
|
|
227
|
+
case "primitive":
|
|
228
|
+
return node.name as string;
|
|
229
|
+
|
|
230
|
+
case "object": {
|
|
231
|
+
const props = node.properties as Record<string, TypeNode>;
|
|
232
|
+
const keys = Object.keys(props);
|
|
233
|
+
if (keys.length === 0) return "{ }";
|
|
234
|
+
|
|
235
|
+
const parts: string[] = [];
|
|
236
|
+
for (const key of keys) {
|
|
237
|
+
const val = props[key];
|
|
238
|
+
const valStr = renderCompactShort(val);
|
|
239
|
+
parts.push(`${key}: ${valStr}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const full = `{ ${parts.join(", ")} }`;
|
|
243
|
+
if (full.length <= 60) return full;
|
|
244
|
+
|
|
245
|
+
// Truncate: show first few fields
|
|
246
|
+
let result = "{ ";
|
|
247
|
+
for (let i = 0; i < parts.length; i++) {
|
|
248
|
+
if (i > 0) result += ", ";
|
|
249
|
+
if (result.length + parts[i].length > 55 && i > 0) {
|
|
250
|
+
result += `…+${parts.length - i}`;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
result += parts[i];
|
|
254
|
+
}
|
|
255
|
+
return result + " }";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case "array": {
|
|
259
|
+
const element = node.element as TypeNode;
|
|
260
|
+
return `${renderCompact(element)}[]`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case "union": {
|
|
264
|
+
const members = node.members as TypeNode[];
|
|
265
|
+
return members.map(renderCompactShort).join(" | ");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
default:
|
|
269
|
+
return node.kind;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function renderCompactShort(node: TypeNode): string {
|
|
274
|
+
switch (node.kind) {
|
|
275
|
+
case "primitive":
|
|
276
|
+
return node.name as string;
|
|
277
|
+
case "object": {
|
|
278
|
+
const props = node.properties as Record<string, TypeNode>;
|
|
279
|
+
const keys = Object.keys(props);
|
|
280
|
+
if (keys.length === 0) return "{}";
|
|
281
|
+
if (keys.length <= 3) {
|
|
282
|
+
return `{${keys.join(", ")}}`;
|
|
283
|
+
}
|
|
284
|
+
return `{${keys.slice(0, 2).join(", ")}, …+${keys.length - 2}}`;
|
|
285
|
+
}
|
|
286
|
+
case "array":
|
|
287
|
+
return `${renderCompactShort(node.element as TypeNode)}[]`;
|
|
288
|
+
case "union": {
|
|
289
|
+
const members = node.members as TypeNode[];
|
|
290
|
+
return members.map(renderCompactShort).join(" | ");
|
|
291
|
+
}
|
|
292
|
+
default:
|
|
293
|
+
return node.kind;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function countFields(node: TypeNode): number {
|
|
298
|
+
if (node.kind === "object") {
|
|
299
|
+
const props = node.properties as Record<string, TypeNode>;
|
|
300
|
+
let count = Object.keys(props).length;
|
|
301
|
+
for (const val of Object.values(props)) {
|
|
302
|
+
count += countFields(val);
|
|
303
|
+
}
|
|
304
|
+
return count;
|
|
305
|
+
}
|
|
306
|
+
if (node.kind === "array") {
|
|
307
|
+
return countFields(node.element as TypeNode);
|
|
308
|
+
}
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { listFunctions, listTypes } from "../api-client";
|
|
4
|
+
import { getBackendUrl } from "../config";
|
|
5
|
+
|
|
6
|
+
export interface PackOptions {
|
|
7
|
+
out?: string;
|
|
8
|
+
env?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PackedFunction {
|
|
12
|
+
functionName: string;
|
|
13
|
+
module: string;
|
|
14
|
+
language: string;
|
|
15
|
+
environment: string;
|
|
16
|
+
snapshots: PackedSnapshot[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface PackedSnapshot {
|
|
20
|
+
typeHash: string;
|
|
21
|
+
env: string;
|
|
22
|
+
argsType: unknown;
|
|
23
|
+
returnType: unknown;
|
|
24
|
+
sampleInput?: unknown;
|
|
25
|
+
sampleOutput?: unknown;
|
|
26
|
+
observedAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PackBundle {
|
|
30
|
+
version: 1;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
source: string;
|
|
33
|
+
functions: PackedFunction[];
|
|
34
|
+
stats: {
|
|
35
|
+
totalFunctions: number;
|
|
36
|
+
totalSnapshots: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `trickle pack` — Export all observed types as a portable bundle.
|
|
42
|
+
*
|
|
43
|
+
* Creates a JSON file containing all functions and their type snapshots
|
|
44
|
+
* that can be shared, committed to version control, or imported elsewhere.
|
|
45
|
+
*/
|
|
46
|
+
export async function packCommand(opts: PackOptions): Promise<void> {
|
|
47
|
+
const backendUrl = getBackendUrl();
|
|
48
|
+
|
|
49
|
+
// Check backend
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch(`${backendUrl}/api/health`, { signal: AbortSignal.timeout(3000) });
|
|
52
|
+
if (!res.ok) throw new Error("not ok");
|
|
53
|
+
} catch {
|
|
54
|
+
console.error(chalk.red(`\n Cannot reach trickle backend at ${chalk.bold(backendUrl)}\n`));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Use stderr for status when writing JSON to stdout
|
|
59
|
+
const log = opts.out ? console.log : (...args: unknown[]) => process.stderr.write(args.join(" ") + "\n");
|
|
60
|
+
|
|
61
|
+
log("");
|
|
62
|
+
log(chalk.bold(" trickle pack"));
|
|
63
|
+
log(chalk.gray(" " + "─".repeat(50)));
|
|
64
|
+
|
|
65
|
+
// Fetch all functions
|
|
66
|
+
const result = await listFunctions({ env: opts.env, limit: 10000 });
|
|
67
|
+
const { functions } = result;
|
|
68
|
+
|
|
69
|
+
if (functions.length === 0) {
|
|
70
|
+
log(chalk.yellow(" No observed types to pack."));
|
|
71
|
+
log(chalk.gray(" Run ") + chalk.white("trickle capture") + chalk.gray(" or ") + chalk.white("trickle dev") + chalk.gray(" first.\n"));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
log(chalk.gray(` Packing ${functions.length} functions...`));
|
|
76
|
+
|
|
77
|
+
// Fetch snapshots for each function
|
|
78
|
+
const packedFunctions: PackedFunction[] = [];
|
|
79
|
+
let totalSnapshots = 0;
|
|
80
|
+
|
|
81
|
+
for (const fn of functions) {
|
|
82
|
+
const typesResult = await listTypes(fn.id, { env: opts.env, limit: 100 });
|
|
83
|
+
const snapshots: PackedSnapshot[] = [];
|
|
84
|
+
|
|
85
|
+
for (const snap of typesResult.snapshots) {
|
|
86
|
+
snapshots.push({
|
|
87
|
+
typeHash: snap.type_hash,
|
|
88
|
+
env: snap.env,
|
|
89
|
+
argsType: snap.args_type,
|
|
90
|
+
returnType: snap.return_type,
|
|
91
|
+
sampleInput: snap.sample_input || undefined,
|
|
92
|
+
sampleOutput: snap.sample_output || undefined,
|
|
93
|
+
observedAt: snap.observed_at,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (snapshots.length > 0) {
|
|
98
|
+
packedFunctions.push({
|
|
99
|
+
functionName: fn.function_name,
|
|
100
|
+
module: fn.module,
|
|
101
|
+
language: fn.language,
|
|
102
|
+
environment: fn.environment,
|
|
103
|
+
snapshots,
|
|
104
|
+
});
|
|
105
|
+
totalSnapshots += snapshots.length;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const bundle: PackBundle = {
|
|
110
|
+
version: 1,
|
|
111
|
+
createdAt: new Date().toISOString(),
|
|
112
|
+
source: backendUrl,
|
|
113
|
+
functions: packedFunctions,
|
|
114
|
+
stats: {
|
|
115
|
+
totalFunctions: packedFunctions.length,
|
|
116
|
+
totalSnapshots,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const json = JSON.stringify(bundle, null, 2);
|
|
121
|
+
|
|
122
|
+
if (opts.out) {
|
|
123
|
+
fs.writeFileSync(opts.out, json, "utf-8");
|
|
124
|
+
log(chalk.green(` Packed ${packedFunctions.length} functions (${totalSnapshots} snapshots)`));
|
|
125
|
+
log(chalk.gray(` Written to ${opts.out}`));
|
|
126
|
+
const sizeKb = (Buffer.byteLength(json, "utf-8") / 1024).toFixed(1);
|
|
127
|
+
log(chalk.gray(` Size: ${sizeKb}KB`));
|
|
128
|
+
log("");
|
|
129
|
+
log(chalk.gray(" Share this file or import it with:"));
|
|
130
|
+
log(chalk.white(` trickle unpack ${opts.out}`));
|
|
131
|
+
log("");
|
|
132
|
+
} else {
|
|
133
|
+
// Write JSON to stdout for piping
|
|
134
|
+
process.stdout.write(json + "\n");
|
|
135
|
+
// Summary to stderr
|
|
136
|
+
log(chalk.green(` Packed ${packedFunctions.length} functions (${totalSnapshots} snapshots)`));
|
|
137
|
+
log("");
|
|
138
|
+
}
|
|
139
|
+
}
|