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.
Files changed (125) hide show
  1. package/dist/api-client.d.ts +208 -0
  2. package/dist/api-client.js +237 -0
  3. package/dist/commands/annotate.d.ts +6 -0
  4. package/dist/commands/annotate.js +433 -0
  5. package/dist/commands/audit.d.ts +7 -0
  6. package/dist/commands/audit.js +82 -0
  7. package/dist/commands/auto.d.ts +8 -0
  8. package/dist/commands/auto.js +268 -0
  9. package/dist/commands/capture.d.ts +14 -0
  10. package/dist/commands/capture.js +271 -0
  11. package/dist/commands/check.d.ts +6 -0
  12. package/dist/commands/check.js +408 -0
  13. package/dist/commands/codegen.d.ts +21 -0
  14. package/dist/commands/codegen.js +129 -0
  15. package/dist/commands/coverage.d.ts +13 -0
  16. package/dist/commands/coverage.js +126 -0
  17. package/dist/commands/dashboard.d.ts +1 -0
  18. package/dist/commands/dashboard.js +83 -0
  19. package/dist/commands/dev.d.ts +14 -0
  20. package/dist/commands/dev.js +319 -0
  21. package/dist/commands/diff.d.ts +7 -0
  22. package/dist/commands/diff.js +79 -0
  23. package/dist/commands/docs.d.ts +13 -0
  24. package/dist/commands/docs.js +383 -0
  25. package/dist/commands/errors.d.ts +7 -0
  26. package/dist/commands/errors.js +180 -0
  27. package/dist/commands/export.d.ts +18 -0
  28. package/dist/commands/export.js +238 -0
  29. package/dist/commands/functions.d.ts +6 -0
  30. package/dist/commands/functions.js +71 -0
  31. package/dist/commands/infer.d.ts +14 -0
  32. package/dist/commands/infer.js +275 -0
  33. package/dist/commands/init.d.ts +5 -0
  34. package/dist/commands/init.js +395 -0
  35. package/dist/commands/mock.d.ts +5 -0
  36. package/dist/commands/mock.js +232 -0
  37. package/dist/commands/openapi.d.ts +8 -0
  38. package/dist/commands/openapi.js +82 -0
  39. package/dist/commands/overview.d.ts +11 -0
  40. package/dist/commands/overview.js +266 -0
  41. package/dist/commands/pack.d.ts +11 -0
  42. package/dist/commands/pack.js +133 -0
  43. package/dist/commands/proxy.d.ts +13 -0
  44. package/dist/commands/proxy.js +312 -0
  45. package/dist/commands/replay.d.ts +14 -0
  46. package/dist/commands/replay.js +289 -0
  47. package/dist/commands/run.d.ts +17 -0
  48. package/dist/commands/run.js +997 -0
  49. package/dist/commands/sample.d.ts +13 -0
  50. package/dist/commands/sample.js +260 -0
  51. package/dist/commands/search.d.ts +5 -0
  52. package/dist/commands/search.js +80 -0
  53. package/dist/commands/stubs.d.ts +6 -0
  54. package/dist/commands/stubs.js +187 -0
  55. package/dist/commands/tail.d.ts +4 -0
  56. package/dist/commands/tail.js +76 -0
  57. package/dist/commands/test-gen.d.ts +13 -0
  58. package/dist/commands/test-gen.js +237 -0
  59. package/dist/commands/trace.d.ts +14 -0
  60. package/dist/commands/trace.js +417 -0
  61. package/dist/commands/types.d.ts +7 -0
  62. package/dist/commands/types.js +128 -0
  63. package/dist/commands/unpack.d.ts +11 -0
  64. package/dist/commands/unpack.js +166 -0
  65. package/dist/commands/validate.d.ts +13 -0
  66. package/dist/commands/validate.js +310 -0
  67. package/dist/commands/watch.d.ts +9 -0
  68. package/dist/commands/watch.js +267 -0
  69. package/dist/config.d.ts +1 -0
  70. package/dist/config.js +66 -0
  71. package/dist/formatters/diff-formatter.d.ts +5 -0
  72. package/dist/formatters/diff-formatter.js +43 -0
  73. package/dist/formatters/type-formatter.d.ts +22 -0
  74. package/dist/formatters/type-formatter.js +135 -0
  75. package/dist/index.d.ts +2 -0
  76. package/dist/index.js +419 -0
  77. package/dist/local-codegen.d.ts +22 -0
  78. package/dist/local-codegen.js +762 -0
  79. package/dist/ui/badges.d.ts +16 -0
  80. package/dist/ui/badges.js +71 -0
  81. package/dist/ui/helpers.d.ts +13 -0
  82. package/dist/ui/helpers.js +85 -0
  83. package/package.json +23 -0
  84. package/src/api-client.ts +407 -0
  85. package/src/commands/annotate.ts +450 -0
  86. package/src/commands/audit.ts +103 -0
  87. package/src/commands/auto.ts +268 -0
  88. package/src/commands/capture.ts +257 -0
  89. package/src/commands/check.ts +437 -0
  90. package/src/commands/codegen.ts +128 -0
  91. package/src/commands/coverage.ts +170 -0
  92. package/src/commands/dashboard.ts +46 -0
  93. package/src/commands/dev.ts +323 -0
  94. package/src/commands/diff.ts +99 -0
  95. package/src/commands/docs.ts +392 -0
  96. package/src/commands/errors.ts +205 -0
  97. package/src/commands/export.ts +287 -0
  98. package/src/commands/functions.ts +81 -0
  99. package/src/commands/infer.ts +260 -0
  100. package/src/commands/init.ts +419 -0
  101. package/src/commands/mock.ts +220 -0
  102. package/src/commands/openapi.ts +53 -0
  103. package/src/commands/overview.ts +310 -0
  104. package/src/commands/pack.ts +139 -0
  105. package/src/commands/proxy.ts +314 -0
  106. package/src/commands/replay.ts +356 -0
  107. package/src/commands/run.ts +1190 -0
  108. package/src/commands/sample.ts +259 -0
  109. package/src/commands/search.ts +107 -0
  110. package/src/commands/stubs.ts +211 -0
  111. package/src/commands/tail.ts +94 -0
  112. package/src/commands/test-gen.ts +236 -0
  113. package/src/commands/trace.ts +440 -0
  114. package/src/commands/types.ts +161 -0
  115. package/src/commands/unpack.ts +179 -0
  116. package/src/commands/validate.ts +368 -0
  117. package/src/commands/watch.ts +277 -0
  118. package/src/config.ts +38 -0
  119. package/src/formatters/diff-formatter.ts +51 -0
  120. package/src/formatters/type-formatter.ts +161 -0
  121. package/src/index.ts +454 -0
  122. package/src/local-codegen.ts +859 -0
  123. package/src/ui/badges.ts +66 -0
  124. package/src/ui/helpers.ts +80 -0
  125. package/tsconfig.json +8 -0
@@ -0,0 +1,13 @@
1
+ export interface SampleOptions {
2
+ format?: string;
3
+ out?: string;
4
+ route?: string;
5
+ }
6
+ /**
7
+ * `trickle sample` — Generate test fixtures from observed runtime data.
8
+ *
9
+ * Produces JSON, TypeScript constants, or factory functions from the
10
+ * actual sample inputs and outputs captured by trickle. Great for tests,
11
+ * seed scripts, and Storybook data.
12
+ */
13
+ export declare function sampleCommand(routeFilter: string | undefined, opts: SampleOptions): Promise<void>;
@@ -0,0 +1,260 @@
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.sampleCommand = sampleCommand;
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 sample` — Generate test fixtures from observed runtime data.
46
+ *
47
+ * Produces JSON, TypeScript constants, or factory functions from the
48
+ * actual sample inputs and outputs captured by trickle. Great for tests,
49
+ * seed scripts, and Storybook data.
50
+ */
51
+ async function sampleCommand(routeFilter, opts) {
52
+ let routes;
53
+ try {
54
+ const config = await (0, api_client_1.fetchMockConfig)();
55
+ routes = config.routes;
56
+ }
57
+ catch {
58
+ console.error(chalk_1.default.red("\n Cannot connect to trickle backend."));
59
+ console.error(chalk_1.default.gray(" Is the backend running?\n"));
60
+ process.exit(1);
61
+ }
62
+ // Filter routes
63
+ if (routeFilter) {
64
+ const filter = routeFilter.toLowerCase();
65
+ routes = routes.filter((r) => r.functionName.toLowerCase().includes(filter) ||
66
+ r.path.toLowerCase().includes(filter));
67
+ }
68
+ // Only include routes with sample data
69
+ routes = routes.filter((r) => r.sampleOutput !== null && r.sampleOutput !== undefined);
70
+ if (routes.length === 0) {
71
+ console.error(chalk_1.default.yellow("\n No sample data found."));
72
+ if (routeFilter) {
73
+ console.error(chalk_1.default.gray(` No routes matching "${routeFilter}" with sample data.\n`));
74
+ }
75
+ else {
76
+ console.error(chalk_1.default.gray(" Instrument your app and make some requests first.\n"));
77
+ }
78
+ process.exit(0);
79
+ }
80
+ const format = (opts.format || "json").toLowerCase();
81
+ let output;
82
+ switch (format) {
83
+ case "json":
84
+ output = generateJson(routes);
85
+ break;
86
+ case "ts":
87
+ case "typescript":
88
+ output = generateTypeScript(routes);
89
+ break;
90
+ case "factory":
91
+ output = generateFactories(routes);
92
+ break;
93
+ default:
94
+ console.error(chalk_1.default.red(`\n Unknown format: ${format}`));
95
+ console.error(chalk_1.default.gray(" Supported: json, ts, factory\n"));
96
+ process.exit(1);
97
+ }
98
+ if (opts.out) {
99
+ const resolvedPath = path.resolve(opts.out);
100
+ const dir = path.dirname(resolvedPath);
101
+ if (!fs.existsSync(dir)) {
102
+ fs.mkdirSync(dir, { recursive: true });
103
+ }
104
+ fs.writeFileSync(resolvedPath, output, "utf-8");
105
+ console.log(chalk_1.default.green(`\n Fixtures written to ${chalk_1.default.bold(opts.out)}`));
106
+ console.log(chalk_1.default.gray(` ${routes.length} routes, format: ${format}\n`));
107
+ }
108
+ else {
109
+ console.log(output);
110
+ }
111
+ }
112
+ function toPascalCase(name) {
113
+ return name
114
+ .replace(/[^a-zA-Z0-9]+/g, " ")
115
+ .split(/\s+/)
116
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
117
+ .join("");
118
+ }
119
+ function toCamelCase(name) {
120
+ const pascal = toPascalCase(name);
121
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
122
+ }
123
+ // ── JSON format ──
124
+ function generateJson(routes) {
125
+ const samples = {};
126
+ for (const route of routes) {
127
+ const key = `${route.method} ${route.path}`;
128
+ const entry = {
129
+ response: route.sampleOutput,
130
+ };
131
+ if (route.sampleInput) {
132
+ const input = route.sampleInput;
133
+ const body = input.body || input;
134
+ if (body && typeof body === "object" && Object.keys(body).length > 0) {
135
+ entry.request = body;
136
+ }
137
+ }
138
+ samples[key] = entry;
139
+ }
140
+ return JSON.stringify(samples, null, 2) + "\n";
141
+ }
142
+ // ── TypeScript constants ──
143
+ function generateTypeScript(routes) {
144
+ const lines = [];
145
+ lines.push("// Auto-generated test fixtures by trickle");
146
+ lines.push(`// Generated at ${new Date().toISOString()}`);
147
+ lines.push("// Do not edit manually — re-run `trickle sample --format ts` to update");
148
+ lines.push("");
149
+ for (const route of routes) {
150
+ const varName = toCamelCase(route.functionName);
151
+ // Response sample
152
+ lines.push(`/** Sample response for ${route.method} ${route.path} */`);
153
+ lines.push(`export const ${varName}Response = ${formatValue(route.sampleOutput, 0)} as const;`);
154
+ lines.push("");
155
+ // Request body sample (for POST/PUT/PATCH)
156
+ if (["POST", "PUT", "PATCH"].includes(route.method) && route.sampleInput) {
157
+ const input = route.sampleInput;
158
+ const body = input.body || input;
159
+ if (body && typeof body === "object" && Object.keys(body).length > 0) {
160
+ lines.push(`/** Sample request body for ${route.method} ${route.path} */`);
161
+ lines.push(`export const ${varName}Request = ${formatValue(body, 0)} as const;`);
162
+ lines.push("");
163
+ }
164
+ }
165
+ }
166
+ return lines.join("\n").trimEnd() + "\n";
167
+ }
168
+ // ── Factory functions ──
169
+ function generateFactories(routes) {
170
+ const lines = [];
171
+ lines.push("// Auto-generated test fixture factories by trickle");
172
+ lines.push(`// Generated at ${new Date().toISOString()}`);
173
+ lines.push("// Do not edit manually — re-run `trickle sample --format factory` to update");
174
+ lines.push("");
175
+ // First emit the base samples as constants
176
+ for (const route of routes) {
177
+ const varName = toCamelCase(route.functionName);
178
+ lines.push(`const _${varName}Response = ${formatValue(route.sampleOutput, 0)};`);
179
+ lines.push("");
180
+ if (["POST", "PUT", "PATCH"].includes(route.method) && route.sampleInput) {
181
+ const input = route.sampleInput;
182
+ const body = input.body || input;
183
+ if (body && typeof body === "object" && Object.keys(body).length > 0) {
184
+ lines.push(`const _${varName}Request = ${formatValue(body, 0)};`);
185
+ lines.push("");
186
+ }
187
+ }
188
+ }
189
+ // Then emit factory functions
190
+ for (const route of routes) {
191
+ const varName = toCamelCase(route.functionName);
192
+ const typeName = toPascalCase(route.functionName);
193
+ // Response factory
194
+ if (route.sampleOutput && typeof route.sampleOutput === "object" && !Array.isArray(route.sampleOutput)) {
195
+ lines.push(`/** Create a test fixture for ${route.method} ${route.path} response */`);
196
+ lines.push(`export function create${typeName}Response(overrides?: Partial<typeof _${varName}Response>): typeof _${varName}Response {`);
197
+ lines.push(` return { ..._${varName}Response, ...overrides };`);
198
+ lines.push(`}`);
199
+ lines.push("");
200
+ }
201
+ else {
202
+ // For non-object responses (arrays, primitives), just export the constant
203
+ lines.push(`/** Sample response for ${route.method} ${route.path} */`);
204
+ lines.push(`export const ${varName}Response = _${varName}Response;`);
205
+ lines.push("");
206
+ }
207
+ // Request body factory
208
+ if (["POST", "PUT", "PATCH"].includes(route.method) && route.sampleInput) {
209
+ const input = route.sampleInput;
210
+ const body = input.body || input;
211
+ if (body && typeof body === "object" && !Array.isArray(body) && Object.keys(body).length > 0) {
212
+ lines.push(`/** Create a test fixture for ${route.method} ${route.path} request body */`);
213
+ lines.push(`export function create${typeName}Request(overrides?: Partial<typeof _${varName}Request>): typeof _${varName}Request {`);
214
+ lines.push(` return { ..._${varName}Request, ...overrides };`);
215
+ lines.push(`}`);
216
+ lines.push("");
217
+ }
218
+ }
219
+ }
220
+ return lines.join("\n").trimEnd() + "\n";
221
+ }
222
+ // ── Value formatting ──
223
+ function formatValue(value, indent) {
224
+ const pad = " ".repeat(indent);
225
+ const innerPad = " ".repeat(indent + 1);
226
+ if (value === null)
227
+ return "null";
228
+ if (value === undefined)
229
+ return "undefined";
230
+ switch (typeof value) {
231
+ case "string":
232
+ return JSON.stringify(value);
233
+ case "number":
234
+ case "boolean":
235
+ return String(value);
236
+ }
237
+ if (Array.isArray(value)) {
238
+ if (value.length === 0)
239
+ return "[]";
240
+ if (value.length === 1 && typeof value[0] !== "object") {
241
+ return `[${formatValue(value[0], 0)}]`;
242
+ }
243
+ const items = value.map((v) => `${innerPad}${formatValue(v, indent + 1)}`);
244
+ return `[\n${items.join(",\n")}\n${pad}]`;
245
+ }
246
+ if (typeof value === "object") {
247
+ const obj = value;
248
+ const keys = Object.keys(obj);
249
+ if (keys.length === 0)
250
+ return "{}";
251
+ const entries = keys.map((key) => {
252
+ const formattedVal = formatValue(obj[key], indent + 1);
253
+ // Use identifier-safe keys without quotes, quoted otherwise
254
+ const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
255
+ return `${innerPad}${safeKey}: ${formattedVal}`;
256
+ });
257
+ return `{\n${entries.join(",\n")}\n${pad}}`;
258
+ }
259
+ return JSON.stringify(value);
260
+ }
@@ -0,0 +1,5 @@
1
+ export interface SearchOptions {
2
+ env?: string;
3
+ json?: boolean;
4
+ }
5
+ export declare function searchCommand(query: string, opts: SearchOptions): Promise<void>;
@@ -0,0 +1,80 @@
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.searchCommand = searchCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../config");
9
+ async function searchCommand(query, opts) {
10
+ const backendUrl = (0, config_1.getBackendUrl)();
11
+ const url = new URL("/api/search", backendUrl);
12
+ url.searchParams.set("q", query);
13
+ if (opts.env) {
14
+ url.searchParams.set("env", opts.env);
15
+ }
16
+ let data;
17
+ try {
18
+ const res = await fetch(url.toString());
19
+ if (!res.ok) {
20
+ const body = await res.text();
21
+ throw new Error(`HTTP ${res.status}: ${body}`);
22
+ }
23
+ data = await res.json();
24
+ }
25
+ catch (err) {
26
+ if (err instanceof Error && err.message.startsWith("HTTP ")) {
27
+ console.error(chalk_1.default.red(`\n Error: ${err.message}\n`));
28
+ }
29
+ else {
30
+ console.error(chalk_1.default.red(`\n Cannot connect to trickle backend at ${chalk_1.default.bold(backendUrl)}.`));
31
+ console.error(chalk_1.default.red(" Is the backend running?\n"));
32
+ }
33
+ process.exit(1);
34
+ }
35
+ if (opts.json) {
36
+ console.log(JSON.stringify(data, null, 2));
37
+ return;
38
+ }
39
+ console.log("");
40
+ console.log(chalk_1.default.bold(` Search: "${query}"`));
41
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
42
+ if (data.total === 0) {
43
+ console.log(chalk_1.default.gray(" No matches found.\n"));
44
+ return;
45
+ }
46
+ console.log(chalk_1.default.gray(` ${data.total} function${data.total === 1 ? "" : "s"} matched\n`));
47
+ for (const result of data.results) {
48
+ // Function name with method coloring
49
+ const fnName = result.functionName;
50
+ const methodMatch = fnName.match(/^(GET|POST|PUT|PATCH|DELETE)\s/);
51
+ if (methodMatch) {
52
+ const method = methodMatch[1];
53
+ const rest = fnName.slice(method.length);
54
+ const methodColors = {
55
+ GET: chalk_1.default.green,
56
+ POST: chalk_1.default.yellow,
57
+ PUT: chalk_1.default.blue,
58
+ PATCH: chalk_1.default.cyan,
59
+ DELETE: chalk_1.default.red,
60
+ };
61
+ const colorFn = methodColors[method] || chalk_1.default.white;
62
+ console.log(` ${colorFn(chalk_1.default.bold(method))}${chalk_1.default.white(rest)}`);
63
+ }
64
+ else {
65
+ console.log(` ${chalk_1.default.white(chalk_1.default.bold(fnName))}`);
66
+ }
67
+ console.log(chalk_1.default.gray(` module: ${result.module} env: ${result.environment}`));
68
+ // Show matching fields
69
+ for (const match of result.matches) {
70
+ const typeStr = match.typeName ? chalk_1.default.cyan(match.typeName) : chalk_1.default.gray(match.kind);
71
+ if (match.kind === "name") {
72
+ console.log(chalk_1.default.gray(" → ") + chalk_1.default.yellow("function name match"));
73
+ }
74
+ else {
75
+ console.log(chalk_1.default.gray(" → ") + chalk_1.default.white(match.path) + chalk_1.default.gray(": ") + typeStr);
76
+ }
77
+ }
78
+ console.log("");
79
+ }
80
+ }
@@ -0,0 +1,6 @@
1
+ export interface StubsOptions {
2
+ env?: string;
3
+ dryRun?: boolean;
4
+ silent?: boolean;
5
+ }
6
+ export declare function stubsCommand(dir: string, opts: StubsOptions): Promise<void>;
@@ -0,0 +1,187 @@
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.stubsCommand = stubsCommand;
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
+ * Map of file extensions to their stub extension.
46
+ */
47
+ const JS_EXTS = new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
48
+ const PY_EXTS = new Set([".py"]);
49
+ /**
50
+ * Normalize a module name for matching against file stems.
51
+ * Module names from trickle may use dashes or underscores.
52
+ * File stems use whatever the OS has.
53
+ */
54
+ function normalizeForMatch(name) {
55
+ return name.replace(/[-_]/g, "").toLowerCase();
56
+ }
57
+ /**
58
+ * Recursively find all source files in a directory.
59
+ */
60
+ function findSourceFiles(dir) {
61
+ const results = [];
62
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
63
+ for (const entry of entries) {
64
+ const fullPath = path.join(dir, entry.name);
65
+ if (entry.isDirectory()) {
66
+ // Skip node_modules, __pycache__, .git, etc.
67
+ if (entry.name === "node_modules" ||
68
+ entry.name === "__pycache__" ||
69
+ entry.name === ".git" ||
70
+ entry.name === "dist" ||
71
+ entry.name === "build" ||
72
+ entry.name === ".trickle") {
73
+ continue;
74
+ }
75
+ results.push(...findSourceFiles(fullPath));
76
+ }
77
+ else if (entry.isFile()) {
78
+ const ext = path.extname(entry.name).toLowerCase();
79
+ if (JS_EXTS.has(ext) || PY_EXTS.has(ext)) {
80
+ results.push(fullPath);
81
+ }
82
+ }
83
+ }
84
+ return results;
85
+ }
86
+ async function stubsCommand(dir, opts) {
87
+ const targetDir = path.resolve(dir);
88
+ if (!fs.existsSync(targetDir)) {
89
+ console.error(chalk_1.default.red(`\n Directory not found: ${targetDir}\n`));
90
+ process.exit(1);
91
+ }
92
+ if (!fs.statSync(targetDir).isDirectory()) {
93
+ console.error(chalk_1.default.red(`\n Not a directory: ${targetDir}\n`));
94
+ process.exit(1);
95
+ }
96
+ // Fetch per-module stubs from backend
97
+ const { stubs } = await (0, api_client_1.fetchStubs)({ env: opts.env });
98
+ if (!stubs || Object.keys(stubs).length === 0) {
99
+ if (!opts.silent) {
100
+ console.log(chalk_1.default.yellow("\n No observed types found. Run your code with trickle first.\n"));
101
+ }
102
+ return;
103
+ }
104
+ // Find all source files in the target directory
105
+ const sourceFiles = findSourceFiles(targetDir);
106
+ if (sourceFiles.length === 0) {
107
+ if (!opts.silent) {
108
+ console.log(chalk_1.default.yellow(`\n No source files found in ${targetDir}\n`));
109
+ }
110
+ return;
111
+ }
112
+ // Build a map: normalized stem → source file path
113
+ const fileMap = new Map();
114
+ for (const filePath of sourceFiles) {
115
+ const ext = path.extname(filePath);
116
+ const stem = path.basename(filePath, ext);
117
+ const key = normalizeForMatch(stem);
118
+ if (!fileMap.has(key))
119
+ fileMap.set(key, []);
120
+ fileMap.get(key).push(filePath);
121
+ }
122
+ const written = [];
123
+ const writtenPaths = new Set();
124
+ const skipped = [];
125
+ for (const [moduleName, moduleStubs] of Object.entries(stubs)) {
126
+ const normalizedModule = normalizeForMatch(moduleName);
127
+ // Find matching source files
128
+ const matchingFiles = fileMap.get(normalizedModule);
129
+ if (!matchingFiles || matchingFiles.length === 0) {
130
+ skipped.push(moduleName);
131
+ continue;
132
+ }
133
+ for (const sourceFile of matchingFiles) {
134
+ const ext = path.extname(sourceFile).toLowerCase();
135
+ const isPython = PY_EXTS.has(ext);
136
+ const stubContent = isPython ? moduleStubs.python : moduleStubs.ts;
137
+ const stubExt = isPython ? ".pyi" : ".d.ts";
138
+ // Generate stub file path next to source file
139
+ const sourceDir = path.dirname(sourceFile);
140
+ const sourceStem = path.basename(sourceFile, ext);
141
+ const stubPath = path.join(sourceDir, `${sourceStem}${stubExt}`);
142
+ // Skip if already written (multiple modules may normalize to same name)
143
+ if (writtenPaths.has(stubPath))
144
+ continue;
145
+ writtenPaths.add(stubPath);
146
+ if (opts.dryRun) {
147
+ const relPath = path.relative(process.cwd(), stubPath);
148
+ console.log(chalk_1.default.cyan(` Would create: ${relPath}`));
149
+ console.log(chalk_1.default.gray(` (from module "${moduleName}" → ${path.basename(sourceFile)})`));
150
+ written.push(relPath);
151
+ continue;
152
+ }
153
+ // Write stub file
154
+ fs.writeFileSync(stubPath, stubContent, "utf-8");
155
+ const relPath = path.relative(process.cwd(), stubPath);
156
+ written.push(relPath);
157
+ }
158
+ }
159
+ // Output summary
160
+ if (opts.silent)
161
+ return;
162
+ console.log();
163
+ if (opts.dryRun) {
164
+ console.log(chalk_1.default.cyan(" Dry run — no files written.\n"));
165
+ if (written.length > 0) {
166
+ console.log(chalk_1.default.gray(` ${written.length} stub file(s) would be created.\n`));
167
+ }
168
+ }
169
+ else if (written.length > 0) {
170
+ console.log(chalk_1.default.green(` Generated ${written.length} type stub file(s):\n`));
171
+ for (const f of written) {
172
+ console.log(chalk_1.default.gray(` ${f}`));
173
+ }
174
+ console.log();
175
+ console.log(chalk_1.default.gray(" Your IDE should now pick up these types automatically."));
176
+ console.log(chalk_1.default.gray(" Add *.d.ts / *.pyi to .gitignore if you don't want to commit them."));
177
+ console.log();
178
+ }
179
+ else {
180
+ console.log(chalk_1.default.yellow(" No matching source files found for observed modules.\n"));
181
+ if (skipped.length > 0) {
182
+ console.log(chalk_1.default.gray(` Observed modules: ${skipped.join(", ")}`));
183
+ console.log(chalk_1.default.gray(` Source files in: ${path.relative(process.cwd(), targetDir) || "."}`));
184
+ console.log(chalk_1.default.gray(" Make sure module names match file names (e.g., module 'helpers' → helpers.js)\n"));
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,4 @@
1
+ export interface TailOptions {
2
+ filter?: string;
3
+ }
4
+ export declare function tailCommand(opts: TailOptions): Promise<void>;
@@ -0,0 +1,76 @@
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.tailCommand = tailCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const api_client_1 = require("../api-client");
9
+ const badges_1 = require("../ui/badges");
10
+ const config_1 = require("../config");
11
+ function eventBadge(event) {
12
+ const lower = event.toLowerCase();
13
+ if (lower === "error" || lower.includes("error")) {
14
+ return chalk_1.default.bgRed.white.bold(" ERROR ");
15
+ }
16
+ if (lower === "new_type" || lower.includes("new")) {
17
+ return chalk_1.default.bgGreen.black.bold(" NEW_TYPE ");
18
+ }
19
+ if (lower === "type_changed" || lower.includes("changed")) {
20
+ return chalk_1.default.bgYellow.black.bold(" TYPE_CHANGED ");
21
+ }
22
+ if (lower === "ingest" || lower.includes("ingest")) {
23
+ return chalk_1.default.bgBlue.white.bold(" INGEST ");
24
+ }
25
+ return chalk_1.default.bgGray.white.bold(` ${event.toUpperCase()} `);
26
+ }
27
+ function formatEventDetail(event) {
28
+ const data = event.data;
29
+ const parts = [];
30
+ if (data.functionName) {
31
+ parts.push(chalk_1.default.white.bold(String(data.functionName)));
32
+ }
33
+ if (data.module) {
34
+ parts.push(chalk_1.default.gray(`(${data.module})`));
35
+ }
36
+ if (data.env) {
37
+ parts.push((0, badges_1.envBadge)(String(data.env)));
38
+ }
39
+ if (data.error && typeof data.error === "object") {
40
+ const err = data.error;
41
+ if (err.message) {
42
+ parts.push(chalk_1.default.red(String(err.message)));
43
+ }
44
+ }
45
+ if (data.error_message) {
46
+ parts.push(chalk_1.default.red(String(data.error_message)));
47
+ }
48
+ return parts.join(" ");
49
+ }
50
+ async function tailCommand(opts) {
51
+ const url = (0, config_1.getBackendUrl)();
52
+ console.log(chalk_1.default.gray(`\n Connecting to trickle at ${url}...`));
53
+ const cleanup = (0, api_client_1.tailEvents)((event) => {
54
+ const now = new Date().toISOString();
55
+ const timeStr = chalk_1.default.gray(now.replace(/T/, " ").replace(/\..+/, ""));
56
+ const badge = eventBadge(event.event);
57
+ const detail = formatEventDetail(event);
58
+ console.log(` ${timeStr} ${badge} ${detail}`);
59
+ }, opts.filter);
60
+ // Give it a moment to connect, then show the listening message
61
+ setTimeout(() => {
62
+ console.log(chalk_1.default.green(" Listening for events...") + chalk_1.default.gray(" (Ctrl+C to stop)\n"));
63
+ }, 500);
64
+ // Keep the process alive and handle graceful shutdown
65
+ const onSignal = () => {
66
+ console.log(chalk_1.default.gray("\n Disconnected.\n"));
67
+ cleanup();
68
+ process.exit(0);
69
+ };
70
+ process.on("SIGINT", onSignal);
71
+ process.on("SIGTERM", onSignal);
72
+ // Keep the event loop alive
73
+ await new Promise(() => {
74
+ // Never resolves — the process stays alive until killed
75
+ });
76
+ }
@@ -0,0 +1,13 @@
1
+ export interface TestGenOptions {
2
+ out?: string;
3
+ framework?: string;
4
+ baseUrl?: string;
5
+ }
6
+ /**
7
+ * `trickle test --generate` — Generate API test files from runtime observations.
8
+ *
9
+ * Uses real sample request/response data captured at runtime to generate
10
+ * ready-to-run test files with correct endpoints, request bodies, and
11
+ * response shape assertions.
12
+ */
13
+ export declare function testGenCommand(opts: TestGenOptions): Promise<void>;