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,395 @@
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.initCommand = initCommand;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ function detectProject(dir, forcePython) {
44
+ const info = {
45
+ dir,
46
+ hasPackageJson: false,
47
+ hasTsConfig: false,
48
+ isPython: forcePython,
49
+ framework: null,
50
+ entryFile: null,
51
+ packageJson: null,
52
+ tsConfig: null,
53
+ };
54
+ // Check for package.json
55
+ const pkgPath = path.join(dir, "package.json");
56
+ if (fs.existsSync(pkgPath)) {
57
+ info.hasPackageJson = true;
58
+ try {
59
+ info.packageJson = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
60
+ }
61
+ catch {
62
+ // ignore parse errors
63
+ }
64
+ }
65
+ // Check for tsconfig.json
66
+ const tsPath = path.join(dir, "tsconfig.json");
67
+ if (fs.existsSync(tsPath)) {
68
+ info.hasTsConfig = true;
69
+ try {
70
+ // Strip comments (simple approach: remove // and /* */ comments)
71
+ const raw = fs.readFileSync(tsPath, "utf-8");
72
+ const cleaned = raw
73
+ .replace(/\/\/.*$/gm, "")
74
+ .replace(/\/\*[\s\S]*?\*\//g, "");
75
+ info.tsConfig = JSON.parse(cleaned);
76
+ }
77
+ catch {
78
+ // ignore parse errors
79
+ }
80
+ }
81
+ // Check for Python project
82
+ if (fs.existsSync(path.join(dir, "pyproject.toml")) ||
83
+ fs.existsSync(path.join(dir, "setup.py")) ||
84
+ fs.existsSync(path.join(dir, "requirements.txt"))) {
85
+ info.isPython = true;
86
+ }
87
+ // Detect framework from dependencies
88
+ if (info.packageJson) {
89
+ const deps = {
90
+ ...info.packageJson.dependencies,
91
+ ...info.packageJson.devDependencies,
92
+ };
93
+ if (deps.express)
94
+ info.framework = "express";
95
+ }
96
+ if (info.isPython) {
97
+ // Check requirements.txt or pyproject.toml for framework
98
+ const reqPath = path.join(dir, "requirements.txt");
99
+ if (fs.existsSync(reqPath)) {
100
+ const reqs = fs.readFileSync(reqPath, "utf-8").toLowerCase();
101
+ if (reqs.includes("fastapi"))
102
+ info.framework = "fastapi";
103
+ else if (reqs.includes("flask"))
104
+ info.framework = "flask";
105
+ else if (reqs.includes("django"))
106
+ info.framework = "django";
107
+ }
108
+ }
109
+ // Detect entry file
110
+ if (info.packageJson) {
111
+ const main = info.packageJson.main;
112
+ if (main && fs.existsSync(path.join(dir, main))) {
113
+ info.entryFile = main;
114
+ }
115
+ else {
116
+ // Common entry points
117
+ for (const candidate of ["src/index.ts", "src/index.js", "index.ts", "index.js", "app.ts", "app.js", "server.ts", "server.js"]) {
118
+ if (fs.existsSync(path.join(dir, candidate))) {
119
+ info.entryFile = candidate;
120
+ break;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ if (info.isPython && !info.entryFile) {
126
+ for (const candidate of ["app.py", "main.py", "server.py", "wsgi.py"]) {
127
+ if (fs.existsSync(path.join(dir, candidate))) {
128
+ info.entryFile = candidate;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ return info;
134
+ }
135
+ function createTrickleConfig(dir, info) {
136
+ const configPath = path.join(dir, ".tricklerc.json");
137
+ if (fs.existsSync(configPath))
138
+ return false;
139
+ // Also check for package.json "trickle" field
140
+ if (info.packageJson && info.packageJson.trickle) {
141
+ return false;
142
+ }
143
+ const config = {};
144
+ // Suggest stubs directory based on project structure
145
+ if (fs.existsSync(path.join(dir, "src"))) {
146
+ config.stubs = "src/";
147
+ }
148
+ else {
149
+ config.stubs = ".";
150
+ }
151
+ // Default exclude patterns
152
+ config.exclude = ["node_modules", "dist", "build", "__pycache__"];
153
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
154
+ return true;
155
+ }
156
+ function ensureTrickleDir(dir) {
157
+ const trickleDir = path.join(dir, ".trickle");
158
+ if (!fs.existsSync(trickleDir)) {
159
+ fs.mkdirSync(trickleDir, { recursive: true });
160
+ }
161
+ return trickleDir;
162
+ }
163
+ function writeInitialTypes(trickleDir, isPython) {
164
+ if (isPython) {
165
+ const pyPath = path.join(trickleDir, "types.pyi");
166
+ if (!fs.existsSync(pyPath)) {
167
+ fs.writeFileSync(pyPath, [
168
+ "# Auto-generated by trickle from runtime type observations",
169
+ "# Run your app to populate types, then: trickle codegen --python --out .trickle/types.pyi",
170
+ "#",
171
+ "# This file will be updated automatically when using: trickle codegen --python --watch --out .trickle/types.pyi",
172
+ "",
173
+ "from typing import TypedDict",
174
+ "",
175
+ "# Types will appear here after your app processes its first requests.",
176
+ "",
177
+ ].join("\n"), "utf-8");
178
+ }
179
+ }
180
+ else {
181
+ const tsPath = path.join(trickleDir, "types.d.ts");
182
+ if (!fs.existsSync(tsPath)) {
183
+ fs.writeFileSync(tsPath, [
184
+ "// Auto-generated by trickle from runtime type observations",
185
+ "// Run your app to populate types, then: trickle codegen --out .trickle/types.d.ts",
186
+ "//",
187
+ "// This file will be updated automatically when using: trickle codegen --watch --out .trickle/types.d.ts",
188
+ "",
189
+ "// Types will appear here after your app processes its first requests.",
190
+ "",
191
+ ].join("\n"), "utf-8");
192
+ }
193
+ // Also create an api-client placeholder
194
+ const clientPath = path.join(trickleDir, "api-client.ts");
195
+ if (!fs.existsSync(clientPath)) {
196
+ fs.writeFileSync(clientPath, [
197
+ "// Auto-generated typed API client by trickle",
198
+ "// Run your app to populate types, then: trickle codegen --client --out .trickle/api-client.ts",
199
+ "",
200
+ "// A fully-typed fetch client will appear here after your app serves its first API requests.",
201
+ "",
202
+ ].join("\n"), "utf-8");
203
+ }
204
+ }
205
+ }
206
+ function updateTsConfig(dir, info) {
207
+ const tsConfigPath = path.join(dir, "tsconfig.json");
208
+ if (!info.hasTsConfig || !info.tsConfig) {
209
+ // No tsconfig — create a minimal one that includes .trickle
210
+ if (!info.isPython) {
211
+ const newConfig = {
212
+ compilerOptions: {
213
+ target: "ES2022",
214
+ module: "commonjs",
215
+ strict: true,
216
+ esModuleInterop: true,
217
+ skipLibCheck: true,
218
+ outDir: "./dist",
219
+ rootDir: "./src",
220
+ },
221
+ include: ["src", ".trickle"],
222
+ };
223
+ fs.writeFileSync(tsConfigPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
224
+ return true;
225
+ }
226
+ return false;
227
+ }
228
+ // Read the raw file to preserve formatting as much as possible
229
+ const raw = fs.readFileSync(tsConfigPath, "utf-8");
230
+ const config = info.tsConfig;
231
+ // Check if .trickle is already included
232
+ const include = config.include;
233
+ if (include && include.some((p) => p === ".trickle" || p.startsWith(".trickle/"))) {
234
+ return false; // Already configured
235
+ }
236
+ // Add .trickle to include array
237
+ if (include) {
238
+ include.push(".trickle");
239
+ }
240
+ else {
241
+ config.include = ["src", ".trickle"];
242
+ }
243
+ fs.writeFileSync(tsConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
244
+ return true;
245
+ }
246
+ function updatePackageJson(dir, info) {
247
+ if (!info.hasPackageJson || !info.packageJson) {
248
+ return { scriptsAdded: [] };
249
+ }
250
+ const pkgPath = path.join(dir, "package.json");
251
+ const pkg = info.packageJson;
252
+ const scripts = pkg.scripts || {};
253
+ const added = [];
254
+ // Add trickle:dev script for watch mode
255
+ if (!scripts["trickle:dev"]) {
256
+ const outFile = info.isPython ? ".trickle/types.pyi" : ".trickle/types.d.ts";
257
+ const langFlag = info.isPython ? " --python" : "";
258
+ scripts["trickle:dev"] = `trickle codegen${langFlag} --watch --out ${outFile}`;
259
+ added.push("trickle:dev");
260
+ }
261
+ // Add trickle:client script for API client generation
262
+ if (!info.isPython && !scripts["trickle:client"]) {
263
+ scripts["trickle:client"] = "trickle codegen --client --out .trickle/api-client.ts";
264
+ added.push("trickle:client");
265
+ }
266
+ // Add trickle:mock script
267
+ if (!scripts["trickle:mock"]) {
268
+ scripts["trickle:mock"] = "trickle mock";
269
+ added.push("trickle:mock");
270
+ }
271
+ // Detect current start script and create a trickle-wrapped version
272
+ const startScript = scripts.start || scripts.dev;
273
+ if (startScript && !scripts["trickle:start"]) {
274
+ // Check if it's a node command we can add -r to
275
+ if (startScript.match(/\bnode\s/)) {
276
+ scripts["trickle:start"] = startScript.replace(/\bnode\s/, "node -r trickle-observe/register ");
277
+ added.push("trickle:start");
278
+ }
279
+ else if (startScript.match(/\bts-node\s/)) {
280
+ scripts["trickle:start"] = startScript.replace(/\bts-node\s/, "ts-node -r trickle-observe/register ");
281
+ added.push("trickle:start");
282
+ }
283
+ else if (startScript.match(/\bnodemon\s/)) {
284
+ scripts["trickle:start"] = startScript.replace(/\bnodemon\s/, "nodemon -r trickle-observe/register ");
285
+ added.push("trickle:start");
286
+ }
287
+ }
288
+ if (added.length > 0) {
289
+ pkg.scripts = scripts;
290
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
291
+ }
292
+ return { scriptsAdded: added };
293
+ }
294
+ function updateGitignore(dir) {
295
+ const giPath = path.join(dir, ".gitignore");
296
+ let content = "";
297
+ if (fs.existsSync(giPath)) {
298
+ content = fs.readFileSync(giPath, "utf-8");
299
+ if (content.includes(".trickle")) {
300
+ return false; // Already has it
301
+ }
302
+ }
303
+ const addition = content.endsWith("\n") || content === ""
304
+ ? ".trickle/\n"
305
+ : "\n.trickle/\n";
306
+ fs.writeFileSync(giPath, content + addition, "utf-8");
307
+ return true;
308
+ }
309
+ async function initCommand(opts) {
310
+ const dir = path.resolve(opts.dir || ".");
311
+ console.log("");
312
+ console.log(chalk_1.default.bold(" trickle init"));
313
+ console.log("");
314
+ // Step 1: Detect project
315
+ const info = detectProject(dir, opts.python === true);
316
+ if (!info.hasPackageJson && !info.isPython) {
317
+ console.log(chalk_1.default.yellow(" No package.json or Python project detected."));
318
+ console.log(chalk_1.default.gray(" Run this command from your project root.\n"));
319
+ process.exit(1);
320
+ }
321
+ const lang = info.isPython ? "Python" : "Node.js";
322
+ console.log(chalk_1.default.gray(` Detected: ${lang} project${info.framework ? ` (${info.framework})` : ""}`));
323
+ if (info.entryFile) {
324
+ console.log(chalk_1.default.gray(` Entry: ${info.entryFile}`));
325
+ }
326
+ console.log("");
327
+ // Step 2: Create .tricklerc.json
328
+ const configCreated = createTrickleConfig(dir, info);
329
+ if (configCreated) {
330
+ console.log(` ${chalk_1.default.green("+")} Created ${chalk_1.default.bold(".tricklerc.json")} — project config`);
331
+ }
332
+ else {
333
+ console.log(` ${chalk_1.default.gray("-")} .tricklerc.json already exists`);
334
+ }
335
+ // Step 3: Create .trickle directory
336
+ const trickleDir = ensureTrickleDir(dir);
337
+ console.log(` ${chalk_1.default.green("+")} Created ${chalk_1.default.bold(".trickle/")} directory`);
338
+ // Step 3: Write initial type files
339
+ writeInitialTypes(trickleDir, info.isPython);
340
+ if (info.isPython) {
341
+ console.log(` ${chalk_1.default.green("+")} Created ${chalk_1.default.bold(".trickle/types.pyi")} (type stubs)`);
342
+ }
343
+ else {
344
+ console.log(` ${chalk_1.default.green("+")} Created ${chalk_1.default.bold(".trickle/types.d.ts")} (type declarations)`);
345
+ console.log(` ${chalk_1.default.green("+")} Created ${chalk_1.default.bold(".trickle/api-client.ts")} (typed API client)`);
346
+ }
347
+ // Step 4: Update tsconfig.json
348
+ if (!info.isPython) {
349
+ const tsUpdated = updateTsConfig(dir, info);
350
+ if (tsUpdated) {
351
+ if (info.hasTsConfig) {
352
+ console.log(` ${chalk_1.default.green("~")} Updated ${chalk_1.default.bold("tsconfig.json")} — added .trickle to include`);
353
+ }
354
+ else {
355
+ console.log(` ${chalk_1.default.green("+")} Created ${chalk_1.default.bold("tsconfig.json")} with .trickle included`);
356
+ }
357
+ }
358
+ else {
359
+ console.log(` ${chalk_1.default.gray("-")} tsconfig.json already includes .trickle`);
360
+ }
361
+ }
362
+ // Step 5: Update package.json scripts
363
+ if (info.hasPackageJson) {
364
+ const { scriptsAdded } = updatePackageJson(dir, info);
365
+ if (scriptsAdded.length > 0) {
366
+ for (const name of scriptsAdded) {
367
+ console.log(` ${chalk_1.default.green("+")} Added npm script: ${chalk_1.default.bold(name)}`);
368
+ }
369
+ }
370
+ }
371
+ // Step 6: Update .gitignore
372
+ const giUpdated = updateGitignore(dir);
373
+ if (giUpdated) {
374
+ console.log(` ${chalk_1.default.green("~")} Updated ${chalk_1.default.bold(".gitignore")} — added .trickle/`);
375
+ }
376
+ // Step 7: Print next steps
377
+ console.log("");
378
+ console.log(chalk_1.default.bold(" Next steps:"));
379
+ console.log("");
380
+ const entryFile = info.entryFile || (info.isPython ? "app.py" : "app.js");
381
+ console.log(chalk_1.default.white(" 1. Run your app with trickle (one command does everything):"));
382
+ console.log(chalk_1.default.cyan(` trickle run ${entryFile}`));
383
+ console.log("");
384
+ console.log(chalk_1.default.white(" 2. That's it! trickle auto-detects the runtime, observes types,"));
385
+ console.log(chalk_1.default.white(" and generates stubs from .tricklerc.json settings."));
386
+ console.log("");
387
+ console.log(chalk_1.default.gray(" Customize .tricklerc.json:"));
388
+ console.log(chalk_1.default.gray(' { "stubs": "src/", "annotate": "src/", "exclude": ["test"] }'));
389
+ console.log("");
390
+ console.log(chalk_1.default.gray(" Other commands:"));
391
+ console.log(chalk_1.default.gray(" trickle functions — list observed functions"));
392
+ console.log(chalk_1.default.gray(" trickle types <name> — see types + sample data"));
393
+ console.log(chalk_1.default.gray(" trickle annotate src/ — add type annotations to source files"));
394
+ console.log("");
395
+ }
@@ -0,0 +1,5 @@
1
+ export interface MockOptions {
2
+ port?: string;
3
+ cors?: boolean;
4
+ }
5
+ export declare function mockCommand(opts: MockOptions): Promise<void>;
@@ -0,0 +1,232 @@
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.mockCommand = mockCommand;
40
+ const http = __importStar(require("http"));
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const api_client_1 = require("../api-client");
43
+ /**
44
+ * Convert an Express-style path like `/api/users/:id` to a regex
45
+ * that captures named path params.
46
+ */
47
+ function pathToRegex(routePath) {
48
+ const paramNames = [];
49
+ const pattern = routePath.replace(/:(\w+)/g, (_match, paramName) => {
50
+ paramNames.push(paramName);
51
+ return "([^/]+)";
52
+ });
53
+ return { regex: new RegExp(`^${pattern}$`), paramNames };
54
+ }
55
+ /**
56
+ * Build a description of the mock server routes for the startup banner.
57
+ */
58
+ function formatRouteTable(routes) {
59
+ const lines = [];
60
+ const methodColors = {
61
+ GET: chalk_1.default.green,
62
+ POST: chalk_1.default.yellow,
63
+ PUT: chalk_1.default.blue,
64
+ DELETE: chalk_1.default.red,
65
+ PATCH: chalk_1.default.magenta,
66
+ };
67
+ for (const route of routes) {
68
+ const color = methodColors[route.method] || chalk_1.default.white;
69
+ const method = color(route.method.padEnd(7));
70
+ const path = chalk_1.default.white(route.path);
71
+ const age = formatTimeAgo(route.observedAt);
72
+ lines.push(` ${method} ${path} ${chalk_1.default.gray(`(sample from ${age})`)}`);
73
+ }
74
+ return lines.join("\n");
75
+ }
76
+ function formatTimeAgo(isoDate) {
77
+ try {
78
+ const date = new Date(isoDate.replace(" ", "T") + (isoDate.includes("Z") ? "" : "Z"));
79
+ const now = Date.now();
80
+ const diffSec = Math.floor((now - date.getTime()) / 1000);
81
+ if (diffSec < 60)
82
+ return `${diffSec}s ago`;
83
+ const diffMin = Math.floor(diffSec / 60);
84
+ if (diffMin < 60)
85
+ return `${diffMin}m ago`;
86
+ const diffHr = Math.floor(diffMin / 60);
87
+ if (diffHr < 24)
88
+ return `${diffHr}h ago`;
89
+ const diffDay = Math.floor(diffHr / 24);
90
+ return `${diffDay}d ago`;
91
+ }
92
+ catch {
93
+ return isoDate;
94
+ }
95
+ }
96
+ /**
97
+ * Substitute path params in sample output to match the requested values.
98
+ * For example, if the sample output has { id: 1 } but the request has :id = "42",
99
+ * replace numeric id fields with the requested value.
100
+ */
101
+ function substituteSampleOutput(sample, paramValues) {
102
+ if (sample === null || sample === undefined)
103
+ return sample;
104
+ if (typeof sample !== "object")
105
+ return sample;
106
+ if (Array.isArray(sample)) {
107
+ return sample.map((item) => substituteSampleOutput(item, paramValues));
108
+ }
109
+ const result = {};
110
+ for (const [key, value] of Object.entries(sample)) {
111
+ // If this key matches a path param name, substitute the value
112
+ if (key in paramValues) {
113
+ const paramVal = paramValues[key];
114
+ // Try to preserve the original type (number vs string)
115
+ if (typeof value === "number") {
116
+ const num = Number(paramVal);
117
+ result[key] = isNaN(num) ? paramVal : num;
118
+ }
119
+ else {
120
+ result[key] = paramVal;
121
+ }
122
+ }
123
+ else if (typeof value === "object" && value !== null) {
124
+ result[key] = substituteSampleOutput(value, paramValues);
125
+ }
126
+ else {
127
+ result[key] = value;
128
+ }
129
+ }
130
+ return result;
131
+ }
132
+ async function mockCommand(opts) {
133
+ const port = parseInt(opts.port || "3000", 10);
134
+ const enableCors = opts.cors !== false;
135
+ // Fetch mock configuration from the backend
136
+ let routes;
137
+ try {
138
+ const config = await (0, api_client_1.fetchMockConfig)();
139
+ routes = config.routes;
140
+ }
141
+ catch (err) {
142
+ if (err instanceof Error) {
143
+ console.error(chalk_1.default.red(`\n Error fetching mock config: ${err.message}\n`));
144
+ }
145
+ process.exit(1);
146
+ }
147
+ if (routes.length === 0) {
148
+ console.log("");
149
+ console.log(chalk_1.default.yellow(" No API routes found."));
150
+ console.log(chalk_1.default.gray(" Instrument your app and make some requests first."));
151
+ console.log("");
152
+ process.exit(0);
153
+ }
154
+ // Build route matchers
155
+ const matchers = routes.map((route) => {
156
+ const { regex, paramNames } = pathToRegex(route.path);
157
+ return { route, regex, paramNames };
158
+ });
159
+ // Create the mock HTTP server
160
+ const server = http.createServer((req, res) => {
161
+ const reqUrl = new URL(req.url || "/", `http://localhost:${port}`);
162
+ const reqMethod = (req.method || "GET").toUpperCase();
163
+ const reqPath = reqUrl.pathname;
164
+ // CORS headers
165
+ if (enableCors) {
166
+ res.setHeader("Access-Control-Allow-Origin", "*");
167
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
168
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
169
+ }
170
+ // Handle preflight
171
+ if (reqMethod === "OPTIONS") {
172
+ res.writeHead(204);
173
+ res.end();
174
+ return;
175
+ }
176
+ // Find matching route
177
+ for (const { route, regex, paramNames } of matchers) {
178
+ if (route.method !== reqMethod)
179
+ continue;
180
+ const match = reqPath.match(regex);
181
+ if (!match)
182
+ continue;
183
+ // Extract path params
184
+ const paramValues = {};
185
+ for (let i = 0; i < paramNames.length; i++) {
186
+ paramValues[paramNames[i]] = match[i + 1];
187
+ }
188
+ // Get sample output, substituting path param values
189
+ let output = route.sampleOutput;
190
+ if (output && Object.keys(paramValues).length > 0) {
191
+ output = substituteSampleOutput(output, paramValues);
192
+ }
193
+ // Log the request
194
+ const methodColor = reqMethod === "GET" ? chalk_1.default.green :
195
+ reqMethod === "POST" ? chalk_1.default.yellow :
196
+ reqMethod === "PUT" ? chalk_1.default.blue :
197
+ reqMethod === "DELETE" ? chalk_1.default.red :
198
+ chalk_1.default.white;
199
+ console.log(` ${chalk_1.default.gray(new Date().toLocaleTimeString())} ${methodColor(reqMethod.padEnd(7))} ${reqPath} ${chalk_1.default.gray("→ 200")}`);
200
+ res.writeHead(200, { "Content-Type": "application/json" });
201
+ res.end(JSON.stringify(output ?? {}));
202
+ return;
203
+ }
204
+ // No route matched
205
+ console.log(` ${chalk_1.default.gray(new Date().toLocaleTimeString())} ${chalk_1.default.red(reqMethod.padEnd(7))} ${reqPath} ${chalk_1.default.red("→ 404")}`);
206
+ res.writeHead(404, { "Content-Type": "application/json" });
207
+ res.end(JSON.stringify({ error: "Not found", path: reqPath, method: reqMethod }));
208
+ });
209
+ server.listen(port, () => {
210
+ console.log("");
211
+ console.log(chalk_1.default.bold(" Trickle Mock Server"));
212
+ console.log("");
213
+ console.log(chalk_1.default.gray(" Routes (from runtime observations):"));
214
+ console.log(formatRouteTable(routes));
215
+ console.log("");
216
+ console.log(` Listening on ${chalk_1.default.cyan(`http://localhost:${port}`)}`);
217
+ if (enableCors) {
218
+ console.log(chalk_1.default.gray(" CORS enabled (Access-Control-Allow-Origin: *)"));
219
+ }
220
+ console.log(chalk_1.default.gray(" Press Ctrl+C to stop.\n"));
221
+ });
222
+ // Handle graceful shutdown
223
+ process.on("SIGINT", () => {
224
+ console.log(chalk_1.default.gray("\n Stopping mock server...\n"));
225
+ server.close(() => process.exit(0));
226
+ });
227
+ process.on("SIGTERM", () => {
228
+ server.close(() => process.exit(0));
229
+ });
230
+ // Keep process alive
231
+ await new Promise(() => { });
232
+ }
@@ -0,0 +1,8 @@
1
+ export interface OpenApiOptions {
2
+ out?: string;
3
+ env?: string;
4
+ title?: string;
5
+ apiVersion?: string;
6
+ server?: string;
7
+ }
8
+ export declare function openapiCommand(opts: OpenApiOptions): Promise<void>;