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,383 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.docsCommand = docsCommand;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const api_client_1 = require("../api-client");
|
|
43
|
+
/**
|
|
44
|
+
* `trickle docs` — Generate API documentation from observed runtime types.
|
|
45
|
+
*
|
|
46
|
+
* Produces clean Markdown (or self-contained HTML) documenting every
|
|
47
|
+
* observed API route with request/response types and example payloads.
|
|
48
|
+
*/
|
|
49
|
+
async function docsCommand(opts) {
|
|
50
|
+
const title = opts.title || "API Documentation";
|
|
51
|
+
// Fetch data
|
|
52
|
+
let routes;
|
|
53
|
+
let typesContent;
|
|
54
|
+
let totalFunctions;
|
|
55
|
+
try {
|
|
56
|
+
const [mockConfig, codegen, funcList] = await Promise.all([
|
|
57
|
+
(0, api_client_1.fetchMockConfig)(),
|
|
58
|
+
(0, api_client_1.fetchCodegen)({ env: opts.env }),
|
|
59
|
+
(0, api_client_1.listFunctions)({ env: opts.env, limit: 500 }),
|
|
60
|
+
]);
|
|
61
|
+
routes = mockConfig.routes;
|
|
62
|
+
typesContent = codegen.types;
|
|
63
|
+
totalFunctions = funcList.total;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
console.error(chalk_1.default.red("\n Cannot connect to trickle backend."));
|
|
67
|
+
console.error(chalk_1.default.gray(" Is the backend running?\n"));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (routes.length === 0) {
|
|
71
|
+
console.error(chalk_1.default.yellow("\n No observed API routes to document."));
|
|
72
|
+
console.error(chalk_1.default.gray(" Instrument your app and make some requests first.\n"));
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
// Parse type definitions to map route names to their types
|
|
76
|
+
const typeMap = buildTypeMap(typesContent);
|
|
77
|
+
// Group routes by resource
|
|
78
|
+
const groups = groupRoutesByResource(routes);
|
|
79
|
+
// Generate markdown
|
|
80
|
+
const markdown = generateMarkdown(title, groups, typeMap, totalFunctions);
|
|
81
|
+
if (opts.html) {
|
|
82
|
+
const html = wrapInHtml(title, markdown);
|
|
83
|
+
if (opts.out) {
|
|
84
|
+
fs.writeFileSync(opts.out, html, "utf-8");
|
|
85
|
+
console.log(chalk_1.default.green(`\n API docs written to ${chalk_1.default.bold(opts.out)}`));
|
|
86
|
+
console.log(chalk_1.default.gray(` ${routes.length} routes documented\n`));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(html);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (opts.out) {
|
|
94
|
+
fs.writeFileSync(opts.out, markdown, "utf-8");
|
|
95
|
+
console.log(chalk_1.default.green(`\n API docs written to ${chalk_1.default.bold(opts.out)}`));
|
|
96
|
+
console.log(chalk_1.default.gray(` ${routes.length} routes documented\n`));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(markdown);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function groupRoutesByResource(routes) {
|
|
104
|
+
const groups = {};
|
|
105
|
+
for (const route of routes) {
|
|
106
|
+
const parts = route.path.split("/").filter(Boolean);
|
|
107
|
+
let resource;
|
|
108
|
+
if (parts[0] === "api" && parts.length >= 2) {
|
|
109
|
+
resource = `/${parts[0]}/${parts[1]}`;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
resource = `/${parts[0] || "root"}`;
|
|
113
|
+
}
|
|
114
|
+
if (!groups[resource])
|
|
115
|
+
groups[resource] = [];
|
|
116
|
+
groups[resource].push(route);
|
|
117
|
+
}
|
|
118
|
+
// Sort groups alphabetically, routes by method order
|
|
119
|
+
const methodOrder = { GET: 0, POST: 1, PUT: 2, PATCH: 3, DELETE: 4 };
|
|
120
|
+
const result = [];
|
|
121
|
+
for (const [resource, resourceRoutes] of Object.entries(groups).sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
122
|
+
resourceRoutes.sort((a, b) => {
|
|
123
|
+
const ma = methodOrder[a.method] ?? 5;
|
|
124
|
+
const mb = methodOrder[b.method] ?? 5;
|
|
125
|
+
if (ma !== mb)
|
|
126
|
+
return ma - mb;
|
|
127
|
+
return a.path.localeCompare(b.path);
|
|
128
|
+
});
|
|
129
|
+
result.push({ resource, routes: resourceRoutes });
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extract type definitions from generated TypeScript code and map them
|
|
135
|
+
* to route function names.
|
|
136
|
+
*/
|
|
137
|
+
function buildTypeMap(typesContent) {
|
|
138
|
+
const map = new Map();
|
|
139
|
+
if (!typesContent)
|
|
140
|
+
return map;
|
|
141
|
+
// Find interface/type blocks and their associated route comments
|
|
142
|
+
const blocks = typesContent.split(/(?=\/\*\*|export interface|export type)/);
|
|
143
|
+
let currentComment = "";
|
|
144
|
+
for (const block of blocks) {
|
|
145
|
+
if (block.startsWith("/**")) {
|
|
146
|
+
currentComment = block;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const match = block.match(/export (?:interface|type) (\w+)/);
|
|
150
|
+
if (match) {
|
|
151
|
+
const typeName = match[1];
|
|
152
|
+
// Clean up the block
|
|
153
|
+
const cleanBlock = block.trim();
|
|
154
|
+
if (cleanBlock) {
|
|
155
|
+
map.set(typeName, cleanBlock);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
currentComment = "";
|
|
159
|
+
}
|
|
160
|
+
return map;
|
|
161
|
+
}
|
|
162
|
+
function toPascalCase(name) {
|
|
163
|
+
return name
|
|
164
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
165
|
+
.split(/\s+/)
|
|
166
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
167
|
+
.join("");
|
|
168
|
+
}
|
|
169
|
+
function generateMarkdown(title, groups, typeMap, totalFunctions) {
|
|
170
|
+
const lines = [];
|
|
171
|
+
const now = new Date().toISOString().split("T")[0];
|
|
172
|
+
lines.push(`# ${title}`);
|
|
173
|
+
lines.push("");
|
|
174
|
+
lines.push(`> Auto-generated by [trickle](https://github.com/yiheinchai/trickle) from runtime-observed types.`);
|
|
175
|
+
lines.push(`> Generated on ${now} — ${totalFunctions} functions observed.`);
|
|
176
|
+
lines.push("");
|
|
177
|
+
// Table of contents
|
|
178
|
+
lines.push("## Table of Contents");
|
|
179
|
+
lines.push("");
|
|
180
|
+
for (const group of groups) {
|
|
181
|
+
const anchor = group.resource.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
182
|
+
lines.push(`- [${group.resource}](#${anchor})`);
|
|
183
|
+
}
|
|
184
|
+
lines.push("");
|
|
185
|
+
lines.push("---");
|
|
186
|
+
lines.push("");
|
|
187
|
+
// Route groups
|
|
188
|
+
for (const group of groups) {
|
|
189
|
+
lines.push(`## ${group.resource}`);
|
|
190
|
+
lines.push("");
|
|
191
|
+
for (const route of group.routes) {
|
|
192
|
+
const methodBadge = `\`${route.method}\``;
|
|
193
|
+
lines.push(`### ${methodBadge} ${route.path}`);
|
|
194
|
+
lines.push("");
|
|
195
|
+
// Metadata
|
|
196
|
+
const observed = route.observedAt ? new Date(route.observedAt).toISOString().split("T")[0] : "unknown";
|
|
197
|
+
lines.push(`*Last observed: ${observed}*`);
|
|
198
|
+
lines.push("");
|
|
199
|
+
// Request body
|
|
200
|
+
const hasBody = ["POST", "PUT", "PATCH"].includes(route.method);
|
|
201
|
+
if (hasBody && route.sampleInput) {
|
|
202
|
+
const input = route.sampleInput;
|
|
203
|
+
const body = input.body || input;
|
|
204
|
+
if (body && typeof body === "object" && Object.keys(body).length > 0) {
|
|
205
|
+
lines.push("**Request Body**");
|
|
206
|
+
lines.push("");
|
|
207
|
+
lines.push("```typescript");
|
|
208
|
+
lines.push(formatTypeFromSample(body));
|
|
209
|
+
lines.push("```");
|
|
210
|
+
lines.push("");
|
|
211
|
+
lines.push("<details>");
|
|
212
|
+
lines.push("<summary>Example</summary>");
|
|
213
|
+
lines.push("");
|
|
214
|
+
lines.push("```json");
|
|
215
|
+
lines.push(JSON.stringify(body, null, 2));
|
|
216
|
+
lines.push("```");
|
|
217
|
+
lines.push("</details>");
|
|
218
|
+
lines.push("");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Response
|
|
222
|
+
if (route.sampleOutput) {
|
|
223
|
+
// Try to find the TypeScript type
|
|
224
|
+
const typeName = toPascalCase(route.functionName);
|
|
225
|
+
const responseTypeName = typeName + "Response";
|
|
226
|
+
const typeBlock = typeMap.get(responseTypeName);
|
|
227
|
+
lines.push("**Response**");
|
|
228
|
+
lines.push("");
|
|
229
|
+
if (typeBlock) {
|
|
230
|
+
lines.push("```typescript");
|
|
231
|
+
lines.push(typeBlock);
|
|
232
|
+
lines.push("```");
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
lines.push("```typescript");
|
|
236
|
+
lines.push(formatTypeFromSample(route.sampleOutput));
|
|
237
|
+
lines.push("```");
|
|
238
|
+
}
|
|
239
|
+
lines.push("");
|
|
240
|
+
lines.push("<details>");
|
|
241
|
+
lines.push("<summary>Example Response</summary>");
|
|
242
|
+
lines.push("");
|
|
243
|
+
lines.push("```json");
|
|
244
|
+
lines.push(JSON.stringify(route.sampleOutput, null, 2));
|
|
245
|
+
lines.push("```");
|
|
246
|
+
lines.push("</details>");
|
|
247
|
+
lines.push("");
|
|
248
|
+
}
|
|
249
|
+
lines.push("---");
|
|
250
|
+
lines.push("");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Footer
|
|
254
|
+
lines.push("*Generated by trickle — runtime type observability for JavaScript and Python.*");
|
|
255
|
+
lines.push("");
|
|
256
|
+
return lines.join("\n");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Infer a TypeScript type string from a sample JSON value.
|
|
260
|
+
*/
|
|
261
|
+
function formatTypeFromSample(value, indent = 0) {
|
|
262
|
+
const pad = " ".repeat(indent);
|
|
263
|
+
if (value === null)
|
|
264
|
+
return `${pad}null`;
|
|
265
|
+
if (value === undefined)
|
|
266
|
+
return `${pad}undefined`;
|
|
267
|
+
switch (typeof value) {
|
|
268
|
+
case "string": return `${pad}string`;
|
|
269
|
+
case "number": return `${pad}number`;
|
|
270
|
+
case "boolean": return `${pad}boolean`;
|
|
271
|
+
}
|
|
272
|
+
if (Array.isArray(value)) {
|
|
273
|
+
if (value.length === 0)
|
|
274
|
+
return `${pad}unknown[]`;
|
|
275
|
+
const elementType = formatTypeFromSample(value[0], 0);
|
|
276
|
+
if (elementType.includes("\n")) {
|
|
277
|
+
// Multi-line element
|
|
278
|
+
return `${pad}Array<${formatTypeFromSample(value[0], indent).trimStart()}>`;
|
|
279
|
+
}
|
|
280
|
+
return `${pad}${elementType.trim()}[]`;
|
|
281
|
+
}
|
|
282
|
+
if (typeof value === "object") {
|
|
283
|
+
const obj = value;
|
|
284
|
+
const keys = Object.keys(obj);
|
|
285
|
+
if (keys.length === 0)
|
|
286
|
+
return `${pad}{}`;
|
|
287
|
+
const lines = [];
|
|
288
|
+
lines.push(`${pad}{`);
|
|
289
|
+
for (const key of keys) {
|
|
290
|
+
const val = obj[key];
|
|
291
|
+
const valType = formatTypeFromSample(val, indent + 1).trimStart();
|
|
292
|
+
lines.push(`${pad} ${key}: ${valType};`);
|
|
293
|
+
}
|
|
294
|
+
lines.push(`${pad}}`);
|
|
295
|
+
return lines.join("\n");
|
|
296
|
+
}
|
|
297
|
+
return `${pad}unknown`;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Wrap markdown in a self-contained HTML document with a simple renderer.
|
|
301
|
+
*/
|
|
302
|
+
function wrapInHtml(title, markdown) {
|
|
303
|
+
// Escape for embedding in JS
|
|
304
|
+
const escapedMd = markdown
|
|
305
|
+
.replace(/\\/g, "\\\\")
|
|
306
|
+
.replace(/`/g, "\\`")
|
|
307
|
+
.replace(/\$/g, "\\$");
|
|
308
|
+
return `<!DOCTYPE html>
|
|
309
|
+
<html lang="en">
|
|
310
|
+
<head>
|
|
311
|
+
<meta charset="UTF-8">
|
|
312
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
313
|
+
<title>${escapeHtml(title)}</title>
|
|
314
|
+
<style>
|
|
315
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
316
|
+
body {
|
|
317
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
318
|
+
line-height: 1.6; color: #1a1a2e; background: #fafafa;
|
|
319
|
+
max-width: 900px; margin: 0 auto; padding: 2rem 1.5rem;
|
|
320
|
+
}
|
|
321
|
+
h1 { font-size: 2rem; margin-bottom: 0.5rem; border-bottom: 2px solid #e0e0e0; padding-bottom: 0.5rem; }
|
|
322
|
+
h2 { font-size: 1.5rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #2d3748; border-bottom: 1px solid #e2e8f0; padding-bottom: 0.3rem; }
|
|
323
|
+
h3 { font-size: 1.1rem; margin-top: 1.5rem; margin-bottom: 0.5rem; }
|
|
324
|
+
p { margin-bottom: 0.75rem; }
|
|
325
|
+
blockquote { border-left: 3px solid #cbd5e0; padding-left: 1rem; color: #718096; margin-bottom: 1rem; }
|
|
326
|
+
code { background: #f0f0f0; padding: 0.15em 0.4em; border-radius: 3px; font-size: 0.9em; }
|
|
327
|
+
pre { background: #1a1a2e; color: #e2e8f0; padding: 1rem; border-radius: 6px; overflow-x: auto; margin-bottom: 1rem; }
|
|
328
|
+
pre code { background: none; padding: 0; color: inherit; }
|
|
329
|
+
hr { border: none; border-top: 1px solid #e2e8f0; margin: 1.5rem 0; }
|
|
330
|
+
em { color: #718096; }
|
|
331
|
+
strong { color: #2d3748; }
|
|
332
|
+
ul { padding-left: 1.5rem; margin-bottom: 0.75rem; }
|
|
333
|
+
li { margin-bottom: 0.25rem; }
|
|
334
|
+
a { color: #3182ce; text-decoration: none; }
|
|
335
|
+
a:hover { text-decoration: underline; }
|
|
336
|
+
details { margin-bottom: 1rem; }
|
|
337
|
+
summary { cursor: pointer; color: #3182ce; font-weight: 500; margin-bottom: 0.5rem; }
|
|
338
|
+
summary:hover { text-decoration: underline; }
|
|
339
|
+
/* Method badges */
|
|
340
|
+
code:first-child { font-weight: bold; }
|
|
341
|
+
</style>
|
|
342
|
+
</head>
|
|
343
|
+
<body>
|
|
344
|
+
<div id="content"></div>
|
|
345
|
+
<script>
|
|
346
|
+
// Simple markdown renderer
|
|
347
|
+
function renderMd(md) {
|
|
348
|
+
let html = md;
|
|
349
|
+
// Code blocks (fenced)
|
|
350
|
+
html = html.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g, '<pre><code class="lang-$1">$2</code></pre>');
|
|
351
|
+
// Inline code
|
|
352
|
+
html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
|
353
|
+
// Headers
|
|
354
|
+
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
|
355
|
+
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
|
356
|
+
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
|
357
|
+
// Blockquotes
|
|
358
|
+
html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');
|
|
359
|
+
// Bold
|
|
360
|
+
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
|
|
361
|
+
// Italic
|
|
362
|
+
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
|
|
363
|
+
// Links
|
|
364
|
+
html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2">$1</a>');
|
|
365
|
+
// Unordered lists
|
|
366
|
+
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
367
|
+
html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');
|
|
368
|
+
// HR
|
|
369
|
+
html = html.replace(/^---$/gm, '<hr>');
|
|
370
|
+
// Details/summary (passthrough)
|
|
371
|
+
// Paragraphs
|
|
372
|
+
html = html.replace(/^(?!<[hupbold]|<li|<ul|<hr|<details|<summary|<\\/|<pre|<blockquote)(.+)$/gm, '<p>$1</p>');
|
|
373
|
+
return html;
|
|
374
|
+
}
|
|
375
|
+
const md = \`${escapedMd}\`;
|
|
376
|
+
document.getElementById('content').innerHTML = renderMd(md);
|
|
377
|
+
</script>
|
|
378
|
+
</body>
|
|
379
|
+
</html>`;
|
|
380
|
+
}
|
|
381
|
+
function escapeHtml(str) {
|
|
382
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
383
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.errorsCommand = errorsCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
9
|
+
const api_client_1 = require("../api-client");
|
|
10
|
+
const badges_1 = require("../ui/badges");
|
|
11
|
+
const helpers_1 = require("../ui/helpers");
|
|
12
|
+
const type_formatter_1 = require("../formatters/type-formatter");
|
|
13
|
+
async function errorsCommand(idOrUndefined, opts) {
|
|
14
|
+
// If an ID was provided, show detail mode
|
|
15
|
+
if (idOrUndefined !== undefined) {
|
|
16
|
+
const id = parseInt(idOrUndefined, 10);
|
|
17
|
+
if (isNaN(id)) {
|
|
18
|
+
console.error(chalk_1.default.red(`\n Invalid error ID: "${idOrUndefined}"\n`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
await showErrorDetail(id);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// List mode
|
|
25
|
+
let sinceIso;
|
|
26
|
+
if (opts.since) {
|
|
27
|
+
try {
|
|
28
|
+
sinceIso = (0, helpers_1.parseSince)(opts.since);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof Error) {
|
|
32
|
+
console.error(chalk_1.default.red(`\n ${err.message}\n`));
|
|
33
|
+
}
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const limit = opts.limit ? parseInt(opts.limit, 10) : undefined;
|
|
38
|
+
const result = await (0, api_client_1.listErrors)({
|
|
39
|
+
env: opts.env,
|
|
40
|
+
functionName: opts.function,
|
|
41
|
+
since: sinceIso,
|
|
42
|
+
limit,
|
|
43
|
+
});
|
|
44
|
+
const { errors } = result;
|
|
45
|
+
if (errors.length === 0) {
|
|
46
|
+
console.log(chalk_1.default.yellow("\n No errors found.\n"));
|
|
47
|
+
if (opts.env || opts.since || opts.function) {
|
|
48
|
+
console.log(chalk_1.default.gray(" Try adjusting your filters.\n"));
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log("");
|
|
53
|
+
const table = new cli_table3_1.default({
|
|
54
|
+
head: [
|
|
55
|
+
chalk_1.default.cyan.bold("ID"),
|
|
56
|
+
chalk_1.default.cyan.bold("Function"),
|
|
57
|
+
chalk_1.default.cyan.bold("Error Type"),
|
|
58
|
+
chalk_1.default.cyan.bold("Message"),
|
|
59
|
+
chalk_1.default.cyan.bold("Env"),
|
|
60
|
+
chalk_1.default.cyan.bold("Time"),
|
|
61
|
+
],
|
|
62
|
+
style: {
|
|
63
|
+
head: [],
|
|
64
|
+
border: ["gray"],
|
|
65
|
+
},
|
|
66
|
+
colWidths: [8, 22, 18, 36, 12, 12],
|
|
67
|
+
wordWrap: true,
|
|
68
|
+
chars: {
|
|
69
|
+
top: "─",
|
|
70
|
+
"top-mid": "┬",
|
|
71
|
+
"top-left": "┌",
|
|
72
|
+
"top-right": "┐",
|
|
73
|
+
bottom: "─",
|
|
74
|
+
"bottom-mid": "┴",
|
|
75
|
+
"bottom-left": "└",
|
|
76
|
+
"bottom-right": "┘",
|
|
77
|
+
left: "│",
|
|
78
|
+
"left-mid": "├",
|
|
79
|
+
mid: "─",
|
|
80
|
+
"mid-mid": "┼",
|
|
81
|
+
right: "│",
|
|
82
|
+
"right-mid": "┤",
|
|
83
|
+
middle: "│",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
for (const err of errors) {
|
|
87
|
+
table.push([
|
|
88
|
+
chalk_1.default.gray(String(err.id)),
|
|
89
|
+
chalk_1.default.white((0, helpers_1.truncate)(err.function_name || "", 20)),
|
|
90
|
+
(0, badges_1.errorTypeBadge)(err.error_type),
|
|
91
|
+
(0, helpers_1.truncate)(err.error_message, 34),
|
|
92
|
+
(0, badges_1.envBadge)(err.env),
|
|
93
|
+
(0, badges_1.timeBadge)(err.occurred_at),
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
console.log(table.toString());
|
|
97
|
+
console.log(chalk_1.default.gray(`\n Showing ${chalk_1.default.white.bold(String(errors.length))} errors`) +
|
|
98
|
+
(result.total > errors.length
|
|
99
|
+
? chalk_1.default.gray(` of ${result.total} total`)
|
|
100
|
+
: "") +
|
|
101
|
+
"\n");
|
|
102
|
+
}
|
|
103
|
+
async function showErrorDetail(id) {
|
|
104
|
+
const result = await (0, api_client_1.getError)(id);
|
|
105
|
+
const { error: err, snapshot } = result;
|
|
106
|
+
console.log("");
|
|
107
|
+
console.log(chalk_1.default.red.bold(" ━━━ Error Detail ━━━"));
|
|
108
|
+
console.log("");
|
|
109
|
+
// Error header
|
|
110
|
+
console.log(` ${(0, badges_1.errorTypeBadge)(err.error_type)} ${(0, badges_1.envBadge)(err.env)}`);
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(chalk_1.default.white.bold(` ${err.error_message}`));
|
|
113
|
+
console.log(chalk_1.default.gray(` ${(0, helpers_1.relativeTime)(err.occurred_at)} (${err.occurred_at})`));
|
|
114
|
+
// Stack trace
|
|
115
|
+
if (err.stack_trace) {
|
|
116
|
+
console.log("");
|
|
117
|
+
console.log(chalk_1.default.gray(" ── Stack Trace ──"));
|
|
118
|
+
console.log("");
|
|
119
|
+
const lines = err.stack_trace.split("\n");
|
|
120
|
+
for (const line of lines) {
|
|
121
|
+
if (line.trim().startsWith("at ")) {
|
|
122
|
+
console.log(chalk_1.default.gray(` ${line.trim()}`));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(chalk_1.default.red(` ${line.trim()}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Type context
|
|
130
|
+
console.log("");
|
|
131
|
+
console.log(chalk_1.default.cyan.bold(" ── Type Context at Point of Failure ──"));
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(chalk_1.default.gray(" Function: ") + chalk_1.default.white.bold(err.function_name || `id:${err.function_id}`));
|
|
134
|
+
console.log(chalk_1.default.gray(" Module: ") + chalk_1.default.white(err.module || "unknown"));
|
|
135
|
+
if (err.args_type) {
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log(chalk_1.default.gray(" Input types:"));
|
|
138
|
+
console.log(` ${(0, type_formatter_1.formatType)(err.args_type, 4)}`);
|
|
139
|
+
}
|
|
140
|
+
if (err.return_type) {
|
|
141
|
+
console.log("");
|
|
142
|
+
console.log(chalk_1.default.gray(" Return type:"));
|
|
143
|
+
console.log(` ${(0, type_formatter_1.formatType)(err.return_type, 4)}`);
|
|
144
|
+
}
|
|
145
|
+
if (err.args_snapshot !== undefined && err.args_snapshot !== null) {
|
|
146
|
+
console.log("");
|
|
147
|
+
console.log(chalk_1.default.gray(" Sample data:"));
|
|
148
|
+
const json = JSON.stringify(err.args_snapshot, null, 2);
|
|
149
|
+
if (json) {
|
|
150
|
+
const lines = json.split("\n");
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
const colored = line
|
|
153
|
+
.replace(/"([^"]+)":/g, (_, key) => `${chalk_1.default.white(`"${key}"`)}:`)
|
|
154
|
+
.replace(/: "([^"]*)"/g, (_, val) => `: ${chalk_1.default.green(`"${val}"`)}`)
|
|
155
|
+
.replace(/: (\d+\.?\d*)/g, (_, val) => `: ${chalk_1.default.yellow(val)}`)
|
|
156
|
+
.replace(/: (true|false)/g, (_, val) => `: ${chalk_1.default.blue(val)}`)
|
|
157
|
+
.replace(/: (null)/g, (_, val) => `: ${chalk_1.default.gray(val)}`);
|
|
158
|
+
console.log(` ${colored}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Expected types (from the latest non-error snapshot)
|
|
163
|
+
if (snapshot) {
|
|
164
|
+
console.log("");
|
|
165
|
+
console.log(chalk_1.default.green.bold(" ── Expected Types (Happy Path) ──"));
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log(chalk_1.default.gray(" Last successful snapshot:") + ` ${(0, badges_1.envBadge)(snapshot.env)} ${(0, badges_1.timeBadge)(snapshot.observed_at)}`);
|
|
168
|
+
if (snapshot.args_type) {
|
|
169
|
+
console.log("");
|
|
170
|
+
console.log(chalk_1.default.gray(" Expected input types:"));
|
|
171
|
+
console.log(` ${(0, type_formatter_1.formatType)(snapshot.args_type, 4)}`);
|
|
172
|
+
}
|
|
173
|
+
if (snapshot.return_type) {
|
|
174
|
+
console.log("");
|
|
175
|
+
console.log(chalk_1.default.gray(" Expected return type:"));
|
|
176
|
+
console.log(` ${(0, type_formatter_1.formatType)(snapshot.return_type, 4)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
console.log("");
|
|
180
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ExportOptions {
|
|
2
|
+
dir?: string;
|
|
3
|
+
env?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* `trickle export` — Generate all output formats into a directory at once.
|
|
7
|
+
*
|
|
8
|
+
* Creates a complete `.trickle/` directory with:
|
|
9
|
+
* - types.d.ts — TypeScript type declarations
|
|
10
|
+
* - api-client.ts — Typed fetch-based API client
|
|
11
|
+
* - handlers.d.ts — Express handler type aliases
|
|
12
|
+
* - schemas.ts — Zod validation schemas
|
|
13
|
+
* - hooks.ts — TanStack React Query hooks
|
|
14
|
+
* - guards.ts — Runtime type guard functions
|
|
15
|
+
* - openapi.json — OpenAPI 3.0 specification
|
|
16
|
+
* - api.test.ts — Generated API test scaffolds
|
|
17
|
+
*/
|
|
18
|
+
export declare function exportCommand(opts: ExportOptions): Promise<void>;
|