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,268 @@
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.autoCommand = autoCommand;
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
+ * Detect which codegen formats are relevant based on package.json dependencies.
46
+ */
47
+ function detectFormats(projectDir) {
48
+ const pkgPath = path.join(projectDir, "package.json");
49
+ if (!fs.existsSync(pkgPath)) {
50
+ return [];
51
+ }
52
+ let pkg;
53
+ try {
54
+ pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
55
+ }
56
+ catch {
57
+ return [];
58
+ }
59
+ const deps = {
60
+ ...(pkg.dependencies || {}),
61
+ ...(pkg.devDependencies || {}),
62
+ };
63
+ const formats = [];
64
+ // Always generate base TypeScript types
65
+ formats.push({
66
+ format: "",
67
+ fileName: "types.d.ts",
68
+ label: "TypeScript types",
69
+ reason: "always generated",
70
+ });
71
+ // Axios client
72
+ if (deps["axios"]) {
73
+ formats.push({
74
+ format: "axios",
75
+ fileName: "axios-client.ts",
76
+ label: "Axios client",
77
+ reason: "axios detected",
78
+ });
79
+ }
80
+ // Fetch-based client (always useful as a fallback if no axios)
81
+ if (!deps["axios"]) {
82
+ formats.push({
83
+ format: "client",
84
+ fileName: "api-client.ts",
85
+ label: "Fetch API client",
86
+ reason: "default HTTP client",
87
+ });
88
+ }
89
+ // React Query / TanStack Query
90
+ if (deps["@tanstack/react-query"] || deps["react-query"]) {
91
+ formats.push({
92
+ format: "react-query",
93
+ fileName: "hooks.ts",
94
+ label: "React Query hooks",
95
+ reason: deps["@tanstack/react-query"] ? "@tanstack/react-query" : "react-query",
96
+ });
97
+ }
98
+ // SWR
99
+ if (deps["swr"]) {
100
+ formats.push({
101
+ format: "swr",
102
+ fileName: "swr-hooks.ts",
103
+ label: "SWR hooks",
104
+ reason: "swr detected",
105
+ });
106
+ }
107
+ // Zod
108
+ if (deps["zod"]) {
109
+ formats.push({
110
+ format: "zod",
111
+ fileName: "schemas.ts",
112
+ label: "Zod schemas",
113
+ reason: "zod detected",
114
+ });
115
+ }
116
+ // tRPC
117
+ if (deps["@trpc/server"] || deps["@trpc/client"]) {
118
+ formats.push({
119
+ format: "trpc",
120
+ fileName: "trpc-router.ts",
121
+ label: "tRPC router",
122
+ reason: deps["@trpc/server"] ? "@trpc/server" : "@trpc/client",
123
+ });
124
+ }
125
+ // class-validator / NestJS
126
+ if (deps["class-validator"] || deps["@nestjs/common"]) {
127
+ formats.push({
128
+ format: "class-validator",
129
+ fileName: "dtos.ts",
130
+ label: "class-validator DTOs",
131
+ reason: deps["class-validator"] ? "class-validator" : "@nestjs/common",
132
+ });
133
+ }
134
+ // Express handler types
135
+ if (deps["express"] || deps["@types/express"]) {
136
+ formats.push({
137
+ format: "handlers",
138
+ fileName: "handlers.d.ts",
139
+ label: "Express handler types",
140
+ reason: deps["express"] ? "express" : "@types/express",
141
+ });
142
+ formats.push({
143
+ format: "middleware",
144
+ fileName: "middleware.ts",
145
+ label: "Express middleware",
146
+ reason: deps["express"] ? "express" : "@types/express",
147
+ });
148
+ }
149
+ // MSW
150
+ if (deps["msw"]) {
151
+ formats.push({
152
+ format: "msw",
153
+ fileName: "msw-handlers.ts",
154
+ label: "MSW mock handlers",
155
+ reason: "msw detected",
156
+ });
157
+ }
158
+ // Pydantic (Python projects)
159
+ if (fs.existsSync(path.join(projectDir, "requirements.txt")) ||
160
+ fs.existsSync(path.join(projectDir, "pyproject.toml"))) {
161
+ // Check if pydantic is in requirements
162
+ let hasPydantic = false;
163
+ try {
164
+ const reqPath = path.join(projectDir, "requirements.txt");
165
+ if (fs.existsSync(reqPath)) {
166
+ const reqs = fs.readFileSync(reqPath, "utf-8");
167
+ if (reqs.toLowerCase().includes("pydantic"))
168
+ hasPydantic = true;
169
+ }
170
+ const pyprojectPath = path.join(projectDir, "pyproject.toml");
171
+ if (fs.existsSync(pyprojectPath)) {
172
+ const pyproject = fs.readFileSync(pyprojectPath, "utf-8");
173
+ if (pyproject.toLowerCase().includes("pydantic"))
174
+ hasPydantic = true;
175
+ }
176
+ }
177
+ catch { }
178
+ if (hasPydantic) {
179
+ formats.push({
180
+ format: "pydantic",
181
+ fileName: "models.py",
182
+ label: "Pydantic models",
183
+ reason: "pydantic detected",
184
+ });
185
+ }
186
+ }
187
+ // Type guards (always useful)
188
+ formats.push({
189
+ format: "guards",
190
+ fileName: "guards.ts",
191
+ label: "Type guards",
192
+ reason: "runtime type checking",
193
+ });
194
+ return formats;
195
+ }
196
+ /**
197
+ * `trickle auto` — Auto-detect project deps and generate only relevant type files.
198
+ */
199
+ async function autoCommand(opts) {
200
+ const projectDir = process.cwd();
201
+ const outDir = path.resolve(opts.dir || ".trickle");
202
+ // Detect formats
203
+ const formats = detectFormats(projectDir);
204
+ if (formats.length === 0) {
205
+ console.error(chalk_1.default.red("\n No package.json found in current directory."));
206
+ console.error(chalk_1.default.gray(" Run this command from your project root.\n"));
207
+ process.exit(1);
208
+ }
209
+ // Ensure output directory
210
+ if (!fs.existsSync(outDir)) {
211
+ fs.mkdirSync(outDir, { recursive: true });
212
+ }
213
+ console.log("");
214
+ console.log(chalk_1.default.bold(" trickle auto"));
215
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
216
+ console.log(chalk_1.default.gray(` Project: ${projectDir}`));
217
+ console.log(chalk_1.default.gray(` Output: ${outDir}`));
218
+ if (opts.env) {
219
+ console.log(chalk_1.default.gray(` Env: ${opts.env}`));
220
+ }
221
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
222
+ console.log("");
223
+ // Show detected formats
224
+ console.log(chalk_1.default.gray(" Detected dependencies:"));
225
+ for (const f of formats) {
226
+ console.log(chalk_1.default.gray(` ${chalk_1.default.white(f.label)} ← ${f.reason}`));
227
+ }
228
+ console.log("");
229
+ // Generate each format
230
+ const queryOpts = { env: opts.env };
231
+ let generated = 0;
232
+ let skipped = 0;
233
+ for (const f of formats) {
234
+ const filePath = path.join(outDir, f.fileName);
235
+ try {
236
+ const result = await (0, api_client_1.fetchCodegen)({
237
+ ...queryOpts,
238
+ format: f.format || undefined,
239
+ });
240
+ const content = result.types;
241
+ if (!content || content.includes("No functions found") || content.includes("No API routes found")) {
242
+ console.log(chalk_1.default.yellow(" ─ ") + chalk_1.default.gray(`${f.fileName} (no data)`));
243
+ skipped++;
244
+ continue;
245
+ }
246
+ fs.writeFileSync(filePath, content, "utf-8");
247
+ generated++;
248
+ const size = content.split("\n").length;
249
+ console.log(chalk_1.default.green(" ✓ ") +
250
+ chalk_1.default.bold(f.fileName) +
251
+ chalk_1.default.gray(` (${size} lines)`));
252
+ }
253
+ catch {
254
+ console.log(chalk_1.default.yellow(" ─ ") + chalk_1.default.gray(`${f.fileName} (error)`));
255
+ skipped++;
256
+ }
257
+ }
258
+ console.log("");
259
+ if (generated > 0) {
260
+ console.log(chalk_1.default.green(` ${generated} files generated`) +
261
+ (skipped > 0 ? chalk_1.default.gray(`, ${skipped} skipped`) : ""));
262
+ console.log(chalk_1.default.gray(` Output directory: ${outDir}`));
263
+ }
264
+ else {
265
+ console.log(chalk_1.default.yellow(" No files generated — instrument your app and make some requests first."));
266
+ }
267
+ console.log("");
268
+ }
@@ -0,0 +1,14 @@
1
+ export interface CaptureOptions {
2
+ header?: string[];
3
+ body?: string;
4
+ env?: string;
5
+ module?: string;
6
+ }
7
+ /**
8
+ * `trickle capture <method> <url>` — Capture types from a live API endpoint.
9
+ *
10
+ * Makes an HTTP request to the given URL, infers TypeNode from the response,
11
+ * and sends the observation to the trickle backend. Zero instrumentation needed —
12
+ * just point at any API and start collecting types.
13
+ */
14
+ export declare function captureCommand(method: string, url: string, opts: CaptureOptions): Promise<void>;
@@ -0,0 +1,271 @@
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.captureCommand = captureCommand;
40
+ const crypto = __importStar(require("crypto"));
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const config_1 = require("../config");
43
+ /**
44
+ * `trickle capture <method> <url>` — Capture types from a live API endpoint.
45
+ *
46
+ * Makes an HTTP request to the given URL, infers TypeNode from the response,
47
+ * and sends the observation to the trickle backend. Zero instrumentation needed —
48
+ * just point at any API and start collecting types.
49
+ */
50
+ async function captureCommand(method, url, opts) {
51
+ const backendUrl = (0, config_1.getBackendUrl)();
52
+ // Validate method
53
+ const httpMethod = method.toUpperCase();
54
+ const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
55
+ if (!validMethods.includes(httpMethod)) {
56
+ console.error(chalk_1.default.red(`\n Invalid HTTP method: ${method}`));
57
+ console.error(chalk_1.default.gray(` Valid methods: ${validMethods.join(", ")}\n`));
58
+ process.exit(1);
59
+ }
60
+ // Parse URL
61
+ let parsedUrl;
62
+ try {
63
+ parsedUrl = new URL(url);
64
+ }
65
+ catch {
66
+ console.error(chalk_1.default.red(`\n Invalid URL: ${url}\n`));
67
+ process.exit(1);
68
+ }
69
+ // Build request headers
70
+ const headers = {
71
+ "Accept": "application/json",
72
+ };
73
+ if (opts.header) {
74
+ for (const h of opts.header) {
75
+ const colonIdx = h.indexOf(":");
76
+ if (colonIdx === -1) {
77
+ console.error(chalk_1.default.red(`\n Invalid header format: ${h}`));
78
+ console.error(chalk_1.default.gray(' Use "Header-Name: value" format\n'));
79
+ process.exit(1);
80
+ }
81
+ const key = h.slice(0, colonIdx).trim();
82
+ const value = h.slice(colonIdx + 1).trim();
83
+ headers[key] = value;
84
+ }
85
+ }
86
+ // Parse request body
87
+ let reqBody;
88
+ let reqJson = undefined;
89
+ if (opts.body) {
90
+ reqBody = opts.body;
91
+ headers["Content-Type"] = headers["Content-Type"] || "application/json";
92
+ try {
93
+ reqJson = JSON.parse(opts.body);
94
+ }
95
+ catch {
96
+ // Not JSON body — that's fine
97
+ }
98
+ }
99
+ // Check backend connectivity
100
+ try {
101
+ const res = await fetch(`${backendUrl}/api/health`, { signal: AbortSignal.timeout(3000) });
102
+ if (!res.ok)
103
+ throw new Error("not ok");
104
+ }
105
+ catch {
106
+ console.error(chalk_1.default.red(`\n Cannot reach trickle backend at ${chalk_1.default.bold(backendUrl)}`));
107
+ console.error(chalk_1.default.gray(" Start the backend first: npx trickle-backend\n"));
108
+ process.exit(1);
109
+ }
110
+ console.log("");
111
+ console.log(chalk_1.default.bold(" trickle capture"));
112
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
113
+ console.log(chalk_1.default.gray(` ${chalk_1.default.bold(httpMethod)} ${url}`));
114
+ // Make the request
115
+ let response;
116
+ try {
117
+ response = await fetch(url, {
118
+ method: httpMethod,
119
+ headers,
120
+ body: reqBody,
121
+ signal: AbortSignal.timeout(30000),
122
+ });
123
+ }
124
+ catch (err) {
125
+ const msg = err instanceof Error ? err.message : "Unknown error";
126
+ console.error(chalk_1.default.red(`\n Request failed: ${msg}\n`));
127
+ process.exit(1);
128
+ }
129
+ const status = response.status;
130
+ const statusColor = status < 400 ? chalk_1.default.green : chalk_1.default.red;
131
+ console.log(chalk_1.default.gray(` Status: `) + statusColor(`${status} ${response.statusText}`));
132
+ // Read response body
133
+ const resText = await response.text();
134
+ let resJson = undefined;
135
+ const contentType = response.headers.get("content-type") || "";
136
+ if (contentType.includes("json") && resText.length > 0) {
137
+ try {
138
+ resJson = JSON.parse(resText);
139
+ }
140
+ catch {
141
+ console.error(chalk_1.default.yellow("\n Response is not valid JSON — cannot capture types.\n"));
142
+ process.exit(1);
143
+ }
144
+ }
145
+ else {
146
+ console.error(chalk_1.default.yellow("\n Response is not JSON — cannot capture types."));
147
+ console.error(chalk_1.default.gray(` Content-Type: ${contentType}\n`));
148
+ process.exit(1);
149
+ }
150
+ // Build type observations
151
+ const routePath = normalizePath(parsedUrl.pathname);
152
+ const functionName = `${httpMethod} ${routePath}`;
153
+ const argsProperties = {};
154
+ if (reqJson !== undefined && reqJson !== null) {
155
+ argsProperties.body = jsonToTypeNode(reqJson);
156
+ }
157
+ // Extract query params
158
+ if (parsedUrl.search) {
159
+ const queryProps = {};
160
+ for (const [key] of parsedUrl.searchParams) {
161
+ queryProps[key] = { kind: "primitive", name: "string" };
162
+ }
163
+ if (Object.keys(queryProps).length > 0) {
164
+ argsProperties.query = { kind: "object", properties: queryProps };
165
+ }
166
+ }
167
+ const argsType = Object.keys(argsProperties).length > 0
168
+ ? { kind: "object", properties: argsProperties }
169
+ : { kind: "object", properties: {} };
170
+ const returnType = jsonToTypeNode(resJson);
171
+ const typeHash = computeTypeHash(argsType, returnType);
172
+ const payload = {
173
+ functionName,
174
+ module: opts.module || "capture",
175
+ language: "js",
176
+ environment: opts.env || "development",
177
+ typeHash,
178
+ argsType,
179
+ returnType,
180
+ sampleInput: Object.keys(argsProperties).length > 0 ? argsProperties : undefined,
181
+ sampleOutput: resJson,
182
+ };
183
+ // Send to backend
184
+ try {
185
+ const res = await fetch(`${backendUrl}/api/ingest`, {
186
+ method: "POST",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify(payload),
189
+ signal: AbortSignal.timeout(5000),
190
+ });
191
+ if (!res.ok)
192
+ throw new Error(`HTTP ${res.status}`);
193
+ }
194
+ catch (err) {
195
+ const msg = err instanceof Error ? err.message : "Unknown error";
196
+ console.error(chalk_1.default.red(`\n Failed to send types to backend: ${msg}\n`));
197
+ process.exit(1);
198
+ }
199
+ console.log(chalk_1.default.gray(` Route: `) + chalk_1.default.white(functionName));
200
+ console.log(chalk_1.default.gray(` Backend: `) + chalk_1.default.white(backendUrl));
201
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
202
+ console.log(chalk_1.default.green(" Types captured successfully!"));
203
+ // Show a preview of what was captured
204
+ const fieldCount = countFields(returnType);
205
+ console.log(chalk_1.default.gray(` Response shape: ${fieldCount} fields observed`));
206
+ if (reqJson) {
207
+ const reqFieldCount = countFields(argsProperties.body || { kind: "object", properties: {} });
208
+ console.log(chalk_1.default.gray(` Request body: ${reqFieldCount} fields observed`));
209
+ }
210
+ console.log("");
211
+ console.log(chalk_1.default.gray(" Run ") + chalk_1.default.white(`trickle codegen`) + chalk_1.default.gray(" to generate type definitions."));
212
+ console.log("");
213
+ }
214
+ /**
215
+ * Infer a TypeNode from a JSON value.
216
+ */
217
+ function jsonToTypeNode(value) {
218
+ if (value === null)
219
+ return { kind: "primitive", name: "null" };
220
+ if (value === undefined)
221
+ return { kind: "primitive", name: "undefined" };
222
+ switch (typeof value) {
223
+ case "string": return { kind: "primitive", name: "string" };
224
+ case "number": return { kind: "primitive", name: "number" };
225
+ case "boolean": return { kind: "primitive", name: "boolean" };
226
+ }
227
+ if (Array.isArray(value)) {
228
+ if (value.length === 0)
229
+ return { kind: "array", element: { kind: "unknown" } };
230
+ const elementType = jsonToTypeNode(value[0]);
231
+ return { kind: "array", element: elementType };
232
+ }
233
+ // Object
234
+ const obj = value;
235
+ const properties = {};
236
+ for (const [key, val] of Object.entries(obj)) {
237
+ properties[key] = jsonToTypeNode(val);
238
+ }
239
+ return { kind: "object", properties };
240
+ }
241
+ /**
242
+ * Normalize URL path: replace dynamic segments with :param patterns.
243
+ */
244
+ function normalizePath(urlPath) {
245
+ const parts = urlPath.split("/");
246
+ return parts
247
+ .map((part, i) => {
248
+ if (!part)
249
+ return part;
250
+ if (/^\d+$/.test(part))
251
+ return ":id";
252
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(part))
253
+ return ":id";
254
+ if (/^[0-9a-f]{16,}$/i.test(part) && i > 1)
255
+ return ":id";
256
+ return part;
257
+ })
258
+ .join("/");
259
+ }
260
+ function computeTypeHash(argsType, returnType) {
261
+ const data = JSON.stringify({ a: argsType, r: returnType });
262
+ return crypto.createHash("sha256").update(data).digest("hex").slice(0, 16);
263
+ }
264
+ function countFields(node) {
265
+ if (node.kind === "object" && node.properties) {
266
+ return Object.keys(node.properties).length;
267
+ }
268
+ if (node.kind === "array")
269
+ return 1;
270
+ return 1;
271
+ }
@@ -0,0 +1,6 @@
1
+ export interface CheckOptions {
2
+ save?: string;
3
+ against?: string;
4
+ env?: string;
5
+ }
6
+ export declare function checkCommand(opts: CheckOptions): Promise<void>;