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,238 @@
|
|
|
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.exportCommand = exportCommand;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const api_client_1 = require("../api-client");
|
|
44
|
+
/**
|
|
45
|
+
* `trickle export` — Generate all output formats into a directory at once.
|
|
46
|
+
*
|
|
47
|
+
* Creates a complete `.trickle/` directory with:
|
|
48
|
+
* - types.d.ts — TypeScript type declarations
|
|
49
|
+
* - api-client.ts — Typed fetch-based API client
|
|
50
|
+
* - handlers.d.ts — Express handler type aliases
|
|
51
|
+
* - schemas.ts — Zod validation schemas
|
|
52
|
+
* - hooks.ts — TanStack React Query hooks
|
|
53
|
+
* - guards.ts — Runtime type guard functions
|
|
54
|
+
* - openapi.json — OpenAPI 3.0 specification
|
|
55
|
+
* - api.test.ts — Generated API test scaffolds
|
|
56
|
+
*/
|
|
57
|
+
async function exportCommand(opts) {
|
|
58
|
+
const outDir = path.resolve(opts.dir || ".trickle");
|
|
59
|
+
// Ensure directory exists
|
|
60
|
+
if (!fs.existsSync(outDir)) {
|
|
61
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
console.log("");
|
|
64
|
+
console.log(chalk_1.default.bold(" trickle export"));
|
|
65
|
+
console.log(chalk_1.default.gray(" " + "─".repeat(50)));
|
|
66
|
+
console.log(chalk_1.default.gray(` Output directory: ${outDir}`));
|
|
67
|
+
if (opts.env) {
|
|
68
|
+
console.log(chalk_1.default.gray(` Environment: ${opts.env}`));
|
|
69
|
+
}
|
|
70
|
+
console.log(chalk_1.default.gray(" " + "─".repeat(50)));
|
|
71
|
+
console.log("");
|
|
72
|
+
const results = [];
|
|
73
|
+
const queryOpts = { env: opts.env };
|
|
74
|
+
// 1. TypeScript types
|
|
75
|
+
results.push(await generateFile(path.join(outDir, "types.d.ts"), "TypeScript types", () => (0, api_client_1.fetchCodegen)({ ...queryOpts }).then((r) => r.types), countInterfaces));
|
|
76
|
+
// 2. API client
|
|
77
|
+
results.push(await generateFile(path.join(outDir, "api-client.ts"), "Typed API client", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "client" }).then((r) => r.types), countFunctions));
|
|
78
|
+
// 3. Express handler types
|
|
79
|
+
results.push(await generateFile(path.join(outDir, "handlers.d.ts"), "Express handler types", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "handlers" }).then((r) => r.types), countHandlers));
|
|
80
|
+
// 4. Zod schemas
|
|
81
|
+
results.push(await generateFile(path.join(outDir, "schemas.ts"), "Zod schemas", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "zod" }).then((r) => r.types), countSchemas));
|
|
82
|
+
// 5. React Query hooks
|
|
83
|
+
results.push(await generateFile(path.join(outDir, "hooks.ts"), "React Query hooks", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "react-query" }).then((r) => r.types), countHooks));
|
|
84
|
+
// 6. Type guards
|
|
85
|
+
results.push(await generateFile(path.join(outDir, "guards.ts"), "Type guards", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "guards" }).then((r) => r.types), countGuards));
|
|
86
|
+
// OpenAPI spec
|
|
87
|
+
results.push(await generateFile(path.join(outDir, "openapi.json"), "OpenAPI 3.0 spec", async () => {
|
|
88
|
+
const spec = await (0, api_client_1.fetchOpenApiSpec)({ env: opts.env });
|
|
89
|
+
return JSON.stringify(spec, null, 2);
|
|
90
|
+
}, countPaths));
|
|
91
|
+
// API tests
|
|
92
|
+
results.push(await generateFile(path.join(outDir, "api.test.ts"), "API test scaffolds", async () => {
|
|
93
|
+
const { routes } = await (0, api_client_1.fetchMockConfig)();
|
|
94
|
+
if (routes.length === 0)
|
|
95
|
+
return null;
|
|
96
|
+
// Use the testGenCommand's internal logic by fetching via codegen format?
|
|
97
|
+
// Actually, let's generate a simple version directly
|
|
98
|
+
return generateTestContent(routes);
|
|
99
|
+
}, countTests));
|
|
100
|
+
// Summary
|
|
101
|
+
console.log("");
|
|
102
|
+
const successCount = results.filter((r) => r.ok).length;
|
|
103
|
+
const skipCount = results.filter((r) => !r.ok).length;
|
|
104
|
+
for (const r of results) {
|
|
105
|
+
if (r.ok) {
|
|
106
|
+
const countStr = r.count ? chalk_1.default.gray(` (${r.count})`) : "";
|
|
107
|
+
console.log(chalk_1.default.green(" ✓ ") + chalk_1.default.bold(r.file) + countStr);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log(chalk_1.default.yellow(" ─ ") + chalk_1.default.gray(r.file) + chalk_1.default.gray(" (skipped — no data)"));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
console.log("");
|
|
114
|
+
if (successCount > 0) {
|
|
115
|
+
console.log(chalk_1.default.green(` ${successCount} files generated`) + (skipCount > 0 ? chalk_1.default.gray(`, ${skipCount} skipped`) : ""));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.log(chalk_1.default.yellow(" No files generated — instrument your app and make some requests first."));
|
|
119
|
+
}
|
|
120
|
+
console.log("");
|
|
121
|
+
}
|
|
122
|
+
async function generateFile(filePath, label, generator, counter) {
|
|
123
|
+
const fileName = path.basename(filePath);
|
|
124
|
+
try {
|
|
125
|
+
const content = await generator();
|
|
126
|
+
if (!content || content.includes("No functions found") || content.includes("No API routes found") || content.includes("No observations")) {
|
|
127
|
+
return { file: fileName, label, ok: false };
|
|
128
|
+
}
|
|
129
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
130
|
+
const count = counter(content);
|
|
131
|
+
return { file: fileName, label, ok: true, count };
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return { file: fileName, label, ok: false };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function countInterfaces(content) {
|
|
138
|
+
const count = (content.match(/export (interface|type) /g) || []).length;
|
|
139
|
+
return count > 0 ? `${count} types` : undefined;
|
|
140
|
+
}
|
|
141
|
+
function countFunctions(content) {
|
|
142
|
+
if (content.includes("createTrickleClient"))
|
|
143
|
+
return "client factory";
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
function countHandlers(content) {
|
|
147
|
+
const count = (content.match(/export type \w+Handler/g) || []).length;
|
|
148
|
+
return count > 0 ? `${count} handlers` : undefined;
|
|
149
|
+
}
|
|
150
|
+
function countSchemas(content) {
|
|
151
|
+
const count = (content.match(/Schema = /g) || []).length;
|
|
152
|
+
return count > 0 ? `${count} schemas` : undefined;
|
|
153
|
+
}
|
|
154
|
+
function countHooks(content) {
|
|
155
|
+
const count = (content.match(/export function use\w+/g) || []).length;
|
|
156
|
+
return count > 0 ? `${count} hooks` : undefined;
|
|
157
|
+
}
|
|
158
|
+
function countPaths(content) {
|
|
159
|
+
try {
|
|
160
|
+
const spec = JSON.parse(content);
|
|
161
|
+
const paths = Object.keys(spec.paths || {}).length;
|
|
162
|
+
return paths > 0 ? `${paths} paths` : undefined;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function countTests(content) {
|
|
169
|
+
const count = (content.match(/it\("/g) || []).length;
|
|
170
|
+
return count > 0 ? `${count} tests` : undefined;
|
|
171
|
+
}
|
|
172
|
+
function countGuards(content) {
|
|
173
|
+
const count = (content.match(/export function is\w+/g) || []).length;
|
|
174
|
+
return count > 0 ? `${count} guards` : undefined;
|
|
175
|
+
}
|
|
176
|
+
function generateTestContent(routes) {
|
|
177
|
+
const lines = [];
|
|
178
|
+
lines.push("// Auto-generated API tests by trickle");
|
|
179
|
+
lines.push(`// Generated at ${new Date().toISOString()}`);
|
|
180
|
+
lines.push("// Do not edit manually — re-run `trickle export` to update");
|
|
181
|
+
lines.push("");
|
|
182
|
+
lines.push('import { describe, it, expect } from "vitest";');
|
|
183
|
+
lines.push("");
|
|
184
|
+
lines.push('const BASE_URL = process.env.TEST_API_URL || "http://localhost:3000";');
|
|
185
|
+
lines.push("");
|
|
186
|
+
// Group by resource
|
|
187
|
+
const groups = {};
|
|
188
|
+
for (const r of routes) {
|
|
189
|
+
const parts = r.path.split("/").filter(Boolean);
|
|
190
|
+
const resource = parts[0] === "api" && parts.length >= 2 ? `/api/${parts[1]}` : `/${parts[0] || "root"}`;
|
|
191
|
+
if (!groups[resource])
|
|
192
|
+
groups[resource] = [];
|
|
193
|
+
groups[resource].push(r);
|
|
194
|
+
}
|
|
195
|
+
for (const [resource, resourceRoutes] of Object.entries(groups)) {
|
|
196
|
+
lines.push(`describe("${resource}", () => {`);
|
|
197
|
+
for (const route of resourceRoutes) {
|
|
198
|
+
const hasBody = ["POST", "PUT", "PATCH"].includes(route.method);
|
|
199
|
+
const fetchPath = route.path.replace(/:(\w+)/g, "test-$1");
|
|
200
|
+
lines.push(` it("${route.method} ${route.path} — returns expected shape", async () => {`);
|
|
201
|
+
lines.push(` const res = await fetch(\`\${BASE_URL}${fetchPath}\`, {`);
|
|
202
|
+
lines.push(` method: "${route.method}",`);
|
|
203
|
+
if (hasBody && route.sampleInput) {
|
|
204
|
+
const body = typeof route.sampleInput === "object" && route.sampleInput !== null
|
|
205
|
+
? route.sampleInput.body || route.sampleInput
|
|
206
|
+
: route.sampleInput;
|
|
207
|
+
if (body && typeof body === "object" && Object.keys(body).length > 0) {
|
|
208
|
+
lines.push(` headers: { "Content-Type": "application/json" },`);
|
|
209
|
+
lines.push(` body: JSON.stringify(${JSON.stringify(body)}),`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
lines.push(" });");
|
|
213
|
+
lines.push(" expect(res.ok).toBe(true);");
|
|
214
|
+
if (route.sampleOutput && typeof route.sampleOutput === "object") {
|
|
215
|
+
lines.push(" const body = await res.json();");
|
|
216
|
+
for (const [key, value] of Object.entries(route.sampleOutput)) {
|
|
217
|
+
if (Array.isArray(value)) {
|
|
218
|
+
lines.push(` expect(Array.isArray(body.${key})).toBe(true);`);
|
|
219
|
+
}
|
|
220
|
+
else if (typeof value === "string") {
|
|
221
|
+
lines.push(` expect(typeof body.${key}).toBe("string");`);
|
|
222
|
+
}
|
|
223
|
+
else if (typeof value === "number") {
|
|
224
|
+
lines.push(` expect(typeof body.${key}).toBe("number");`);
|
|
225
|
+
}
|
|
226
|
+
else if (typeof value === "boolean") {
|
|
227
|
+
lines.push(` expect(typeof body.${key}).toBe("boolean");`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
lines.push(" });");
|
|
232
|
+
lines.push("");
|
|
233
|
+
}
|
|
234
|
+
lines.push("});");
|
|
235
|
+
lines.push("");
|
|
236
|
+
}
|
|
237
|
+
return lines.join("\n").trimEnd() + "\n";
|
|
238
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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.functionsCommand = functionsCommand;
|
|
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
|
+
async function functionsCommand(opts) {
|
|
12
|
+
const result = await (0, api_client_1.listFunctions)({
|
|
13
|
+
env: opts.env,
|
|
14
|
+
language: opts.lang,
|
|
15
|
+
search: opts.search,
|
|
16
|
+
});
|
|
17
|
+
const { functions } = result;
|
|
18
|
+
if (functions.length === 0) {
|
|
19
|
+
console.log(chalk_1.default.yellow("\n No functions found.\n"));
|
|
20
|
+
if (opts.env || opts.lang || opts.search) {
|
|
21
|
+
console.log(chalk_1.default.gray(" Try adjusting your filters.\n"));
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.log("");
|
|
26
|
+
const table = new cli_table3_1.default({
|
|
27
|
+
head: [
|
|
28
|
+
chalk_1.default.cyan.bold("Name"),
|
|
29
|
+
chalk_1.default.cyan.bold("Module"),
|
|
30
|
+
chalk_1.default.cyan.bold("Language"),
|
|
31
|
+
chalk_1.default.cyan.bold("Environment"),
|
|
32
|
+
chalk_1.default.cyan.bold("Last Seen"),
|
|
33
|
+
],
|
|
34
|
+
style: {
|
|
35
|
+
head: [],
|
|
36
|
+
border: ["gray"],
|
|
37
|
+
},
|
|
38
|
+
chars: {
|
|
39
|
+
top: "─",
|
|
40
|
+
"top-mid": "┬",
|
|
41
|
+
"top-left": "┌",
|
|
42
|
+
"top-right": "┐",
|
|
43
|
+
bottom: "─",
|
|
44
|
+
"bottom-mid": "┴",
|
|
45
|
+
"bottom-left": "└",
|
|
46
|
+
"bottom-right": "┘",
|
|
47
|
+
left: "│",
|
|
48
|
+
"left-mid": "├",
|
|
49
|
+
mid: "─",
|
|
50
|
+
"mid-mid": "┼",
|
|
51
|
+
right: "│",
|
|
52
|
+
"right-mid": "┤",
|
|
53
|
+
middle: "│",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
for (const fn of functions) {
|
|
57
|
+
table.push([
|
|
58
|
+
chalk_1.default.white.bold(fn.function_name),
|
|
59
|
+
chalk_1.default.gray(fn.module),
|
|
60
|
+
(0, badges_1.langBadge)(fn.language),
|
|
61
|
+
(0, badges_1.envBadge)(fn.environment),
|
|
62
|
+
(0, badges_1.timeBadge)(fn.last_seen_at),
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
console.log(table.toString());
|
|
66
|
+
console.log(chalk_1.default.gray(`\n Showing ${chalk_1.default.white.bold(String(functions.length))} functions`) +
|
|
67
|
+
(result.total > functions.length
|
|
68
|
+
? chalk_1.default.gray(` of ${result.total} total`)
|
|
69
|
+
: "") +
|
|
70
|
+
"\n");
|
|
71
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface InferOptions {
|
|
2
|
+
name: string;
|
|
3
|
+
env?: string;
|
|
4
|
+
module?: string;
|
|
5
|
+
requestBody?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* `trickle infer <file>` — Infer types from a JSON file or stdin and store them.
|
|
9
|
+
*
|
|
10
|
+
* Reads JSON from a file (or stdin if file is "-" or omitted with piped input),
|
|
11
|
+
* infers TypeNode from the data, and sends the observation to the trickle backend.
|
|
12
|
+
* Works offline with saved API responses, test fixtures, or piped command output.
|
|
13
|
+
*/
|
|
14
|
+
export declare function inferCommand(file: string | undefined, opts: InferOptions): Promise<void>;
|
|
@@ -0,0 +1,275 @@
|
|
|
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.inferCommand = inferCommand;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const crypto = __importStar(require("crypto"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const config_1 = require("../config");
|
|
44
|
+
/**
|
|
45
|
+
* `trickle infer <file>` — Infer types from a JSON file or stdin and store them.
|
|
46
|
+
*
|
|
47
|
+
* Reads JSON from a file (or stdin if file is "-" or omitted with piped input),
|
|
48
|
+
* infers TypeNode from the data, and sends the observation to the trickle backend.
|
|
49
|
+
* Works offline with saved API responses, test fixtures, or piped command output.
|
|
50
|
+
*/
|
|
51
|
+
async function inferCommand(file, opts) {
|
|
52
|
+
const backendUrl = (0, config_1.getBackendUrl)();
|
|
53
|
+
// Check backend connectivity
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(`${backendUrl}/api/health`, { signal: AbortSignal.timeout(3000) });
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
throw new Error("not ok");
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
console.error(chalk_1.default.red(`\n Cannot reach trickle backend at ${chalk_1.default.bold(backendUrl)}\n`));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
// Read JSON input
|
|
64
|
+
let jsonText;
|
|
65
|
+
let sourceName;
|
|
66
|
+
if (!file || file === "-") {
|
|
67
|
+
// Read from stdin
|
|
68
|
+
if (process.stdin.isTTY) {
|
|
69
|
+
console.error(chalk_1.default.red("\n No input provided."));
|
|
70
|
+
console.error(chalk_1.default.gray(" Pipe JSON via stdin or provide a file path:\n"));
|
|
71
|
+
console.error(chalk_1.default.gray(' echo \'{"key":"value"}\' | trickle infer --name "GET /api/data"'));
|
|
72
|
+
console.error(chalk_1.default.gray(' trickle infer response.json --name "GET /api/data"\n'));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
jsonText = await readStdin();
|
|
76
|
+
sourceName = "stdin";
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Read from file
|
|
80
|
+
if (!fs.existsSync(file)) {
|
|
81
|
+
console.error(chalk_1.default.red(`\n File not found: ${file}\n`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
jsonText = fs.readFileSync(file, "utf-8");
|
|
85
|
+
sourceName = file;
|
|
86
|
+
}
|
|
87
|
+
// Parse JSON
|
|
88
|
+
let jsonData;
|
|
89
|
+
try {
|
|
90
|
+
jsonData = JSON.parse(jsonText.trim());
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
console.error(chalk_1.default.red("\n Input is not valid JSON.\n"));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
// Build type observations
|
|
97
|
+
const functionName = opts.name;
|
|
98
|
+
const returnType = jsonToTypeNode(jsonData);
|
|
99
|
+
const argsProperties = {};
|
|
100
|
+
// Parse request body example if provided
|
|
101
|
+
if (opts.requestBody) {
|
|
102
|
+
try {
|
|
103
|
+
const reqJson = JSON.parse(opts.requestBody);
|
|
104
|
+
argsProperties.body = jsonToTypeNode(reqJson);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
console.error(chalk_1.default.red("\n --request-body is not valid JSON.\n"));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const argsType = Object.keys(argsProperties).length > 0
|
|
112
|
+
? { kind: "object", properties: argsProperties }
|
|
113
|
+
: { kind: "object", properties: {} };
|
|
114
|
+
const typeHash = computeTypeHash(argsType, returnType);
|
|
115
|
+
const payload = {
|
|
116
|
+
functionName,
|
|
117
|
+
module: opts.module || "infer",
|
|
118
|
+
language: "js",
|
|
119
|
+
environment: opts.env || "development",
|
|
120
|
+
typeHash,
|
|
121
|
+
argsType,
|
|
122
|
+
returnType,
|
|
123
|
+
sampleInput: Object.keys(argsProperties).length > 0 ? argsProperties : undefined,
|
|
124
|
+
sampleOutput: jsonData,
|
|
125
|
+
};
|
|
126
|
+
// Send to backend
|
|
127
|
+
try {
|
|
128
|
+
const res = await fetch(`${backendUrl}/api/ingest`, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { "Content-Type": "application/json" },
|
|
131
|
+
body: JSON.stringify(payload),
|
|
132
|
+
signal: AbortSignal.timeout(5000),
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok)
|
|
135
|
+
throw new Error(`HTTP ${res.status}`);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
139
|
+
console.error(chalk_1.default.red(`\n Failed to send types to backend: ${msg}\n`));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
console.log("");
|
|
143
|
+
console.log(chalk_1.default.bold(" trickle infer"));
|
|
144
|
+
console.log(chalk_1.default.gray(" " + "─".repeat(50)));
|
|
145
|
+
console.log(chalk_1.default.gray(` Source: `) + chalk_1.default.white(sourceName));
|
|
146
|
+
console.log(chalk_1.default.gray(` Name: `) + chalk_1.default.white(functionName));
|
|
147
|
+
console.log(chalk_1.default.gray(` Backend: `) + chalk_1.default.white(backendUrl));
|
|
148
|
+
console.log(chalk_1.default.gray(" " + "─".repeat(50)));
|
|
149
|
+
console.log(chalk_1.default.green(" Types inferred and stored successfully!"));
|
|
150
|
+
// Show a preview of the inferred shape
|
|
151
|
+
const shape = describeShape(returnType, 1);
|
|
152
|
+
console.log("");
|
|
153
|
+
console.log(chalk_1.default.gray(" Inferred shape:"));
|
|
154
|
+
for (const line of shape) {
|
|
155
|
+
console.log(chalk_1.default.gray(" ") + line);
|
|
156
|
+
}
|
|
157
|
+
if (opts.requestBody) {
|
|
158
|
+
const reqShape = describeShape(argsProperties.body, 1);
|
|
159
|
+
console.log("");
|
|
160
|
+
console.log(chalk_1.default.gray(" Request body shape:"));
|
|
161
|
+
for (const line of reqShape) {
|
|
162
|
+
console.log(chalk_1.default.gray(" ") + line);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
console.log("");
|
|
166
|
+
console.log(chalk_1.default.gray(" Run ") + chalk_1.default.white("trickle codegen") + chalk_1.default.gray(" to generate type definitions."));
|
|
167
|
+
console.log("");
|
|
168
|
+
}
|
|
169
|
+
function readStdin() {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
let data = "";
|
|
172
|
+
process.stdin.setEncoding("utf-8");
|
|
173
|
+
process.stdin.on("data", (chunk) => { data += chunk; });
|
|
174
|
+
process.stdin.on("end", () => resolve(data));
|
|
175
|
+
process.stdin.on("error", reject);
|
|
176
|
+
// Timeout after 10 seconds
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
if (data.length === 0) {
|
|
179
|
+
reject(new Error("No input received from stdin"));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
resolve(data);
|
|
183
|
+
}
|
|
184
|
+
}, 10000);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function jsonToTypeNode(value) {
|
|
188
|
+
if (value === null)
|
|
189
|
+
return { kind: "primitive", name: "null" };
|
|
190
|
+
if (value === undefined)
|
|
191
|
+
return { kind: "primitive", name: "undefined" };
|
|
192
|
+
switch (typeof value) {
|
|
193
|
+
case "string": return { kind: "primitive", name: "string" };
|
|
194
|
+
case "number": return { kind: "primitive", name: "number" };
|
|
195
|
+
case "boolean": return { kind: "primitive", name: "boolean" };
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(value)) {
|
|
198
|
+
if (value.length === 0)
|
|
199
|
+
return { kind: "array", element: { kind: "unknown" } };
|
|
200
|
+
return { kind: "array", element: jsonToTypeNode(value[0]) };
|
|
201
|
+
}
|
|
202
|
+
const obj = value;
|
|
203
|
+
const properties = {};
|
|
204
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
205
|
+
properties[key] = jsonToTypeNode(val);
|
|
206
|
+
}
|
|
207
|
+
return { kind: "object", properties };
|
|
208
|
+
}
|
|
209
|
+
function computeTypeHash(argsType, returnType) {
|
|
210
|
+
const data = JSON.stringify({ a: argsType, r: returnType });
|
|
211
|
+
return crypto.createHash("sha256").update(data).digest("hex").slice(0, 16);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Describe a TypeNode shape as human-readable lines.
|
|
215
|
+
*/
|
|
216
|
+
function describeShape(node, maxDepth, depth = 0) {
|
|
217
|
+
const indent = " ".repeat(depth);
|
|
218
|
+
const lines = [];
|
|
219
|
+
if (node.kind === "primitive") {
|
|
220
|
+
lines.push(indent + chalk_1.default.cyan(node.name));
|
|
221
|
+
}
|
|
222
|
+
else if (node.kind === "array") {
|
|
223
|
+
const element = node.element;
|
|
224
|
+
if (element.kind === "object" && depth < maxDepth) {
|
|
225
|
+
lines.push(indent + chalk_1.default.yellow("Array<{"));
|
|
226
|
+
const subLines = describeShape(element, maxDepth, depth + 1);
|
|
227
|
+
lines.push(...subLines);
|
|
228
|
+
lines.push(indent + chalk_1.default.yellow("}>"));
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
lines.push(indent + chalk_1.default.yellow(`${describeTypeCompact(element)}[]`));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (node.kind === "object") {
|
|
235
|
+
const props = node.properties;
|
|
236
|
+
const keys = Object.keys(props);
|
|
237
|
+
if (keys.length === 0) {
|
|
238
|
+
lines.push(indent + chalk_1.default.gray("{}"));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
for (const key of keys) {
|
|
242
|
+
const propType = props[key];
|
|
243
|
+
const typeStr = describeTypeCompact(propType);
|
|
244
|
+
lines.push(indent + chalk_1.default.white(key) + chalk_1.default.gray(": ") + chalk_1.default.cyan(typeStr));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
lines.push(indent + chalk_1.default.gray(node.kind));
|
|
250
|
+
}
|
|
251
|
+
return lines;
|
|
252
|
+
}
|
|
253
|
+
function describeTypeCompact(node) {
|
|
254
|
+
switch (node.kind) {
|
|
255
|
+
case "primitive":
|
|
256
|
+
return node.name;
|
|
257
|
+
case "array": {
|
|
258
|
+
const element = node.element;
|
|
259
|
+
return `${describeTypeCompact(element)}[]`;
|
|
260
|
+
}
|
|
261
|
+
case "object": {
|
|
262
|
+
const props = node.properties;
|
|
263
|
+
const keys = Object.keys(props);
|
|
264
|
+
if (keys.length === 0)
|
|
265
|
+
return "{}";
|
|
266
|
+
if (keys.length <= 3) {
|
|
267
|
+
const inner = keys.map((k) => `${k}: ${describeTypeCompact(props[k])}`).join(", ");
|
|
268
|
+
return `{ ${inner} }`;
|
|
269
|
+
}
|
|
270
|
+
return `{ ${keys.slice(0, 2).map((k) => `${k}: ${describeTypeCompact(props[k])}`).join(", ")}, ... }`;
|
|
271
|
+
}
|
|
272
|
+
default:
|
|
273
|
+
return node.kind;
|
|
274
|
+
}
|
|
275
|
+
}
|