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,433 @@
|
|
|
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.annotateCommand = annotateCommand;
|
|
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 file kind from extension.
|
|
46
|
+
* JavaScript files get JSDoc comments; TypeScript files get inline type annotations.
|
|
47
|
+
*/
|
|
48
|
+
function detectFileKind(filePath) {
|
|
49
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
50
|
+
if ([".ts", ".tsx"].includes(ext))
|
|
51
|
+
return "typescript";
|
|
52
|
+
if ([".js", ".jsx", ".mjs", ".cjs"].includes(ext))
|
|
53
|
+
return "javascript";
|
|
54
|
+
if ([".py", ".pyi"].includes(ext))
|
|
55
|
+
return "python";
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
// ── Shared regex for JS/TS function detection ──
|
|
59
|
+
const funcDeclRe = /^(\s*(?:export\s+)?(?:async\s+)?function\s+)(\w+)\s*\(([^)]*)\)/;
|
|
60
|
+
const arrowRe = /^(\s*(?:export\s+)?(?:const|let|var)\s+)(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)/;
|
|
61
|
+
const methodRe = /^(\s+)(\w+)\s*\(([^)]*)\)\s*\{/;
|
|
62
|
+
/**
|
|
63
|
+
* Extract function name and raw params from a line, if it matches a function pattern.
|
|
64
|
+
*/
|
|
65
|
+
function matchFunction(line) {
|
|
66
|
+
let match = funcDeclRe.exec(line);
|
|
67
|
+
if (match)
|
|
68
|
+
return { fnName: match[2], rawParams: match[3].trim(), kind: "function", match };
|
|
69
|
+
match = arrowRe.exec(line);
|
|
70
|
+
if (match)
|
|
71
|
+
return { fnName: match[2], rawParams: match[3].trim(), kind: "arrow", match };
|
|
72
|
+
match = methodRe.exec(line);
|
|
73
|
+
if (match)
|
|
74
|
+
return { fnName: match[2], rawParams: match[3].trim(), kind: "method", match };
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get param names from raw param string, filtering out complex patterns.
|
|
79
|
+
*/
|
|
80
|
+
function getParamNames(rawParams) {
|
|
81
|
+
if (!rawParams)
|
|
82
|
+
return [];
|
|
83
|
+
return rawParams.split(",").map((p) => p.trim());
|
|
84
|
+
}
|
|
85
|
+
// ── JSDoc annotation for JavaScript files ──
|
|
86
|
+
/**
|
|
87
|
+
* Annotate a JavaScript file with JSDoc comments above functions.
|
|
88
|
+
* Produces valid JavaScript that IDEs understand for type hints.
|
|
89
|
+
*/
|
|
90
|
+
function annotateJSDoc(source, annotations) {
|
|
91
|
+
const lines = source.split("\n");
|
|
92
|
+
const result = [];
|
|
93
|
+
for (let i = 0; i < lines.length; i++) {
|
|
94
|
+
const line = lines[i];
|
|
95
|
+
const info = matchFunction(line);
|
|
96
|
+
if (!info) {
|
|
97
|
+
result.push(line);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const annotation = annotations[info.fnName];
|
|
101
|
+
if (!annotation) {
|
|
102
|
+
result.push(line);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Skip if there's already a JSDoc comment right above
|
|
106
|
+
const prevLineIdx = result.length - 1;
|
|
107
|
+
if (prevLineIdx >= 0) {
|
|
108
|
+
const prevLine = result[prevLineIdx].trim();
|
|
109
|
+
if (prevLine === "*/" || prevLine.endsWith("*/")) {
|
|
110
|
+
// Check if this is a JSDoc block that already has @param or @returns
|
|
111
|
+
let j = prevLineIdx;
|
|
112
|
+
while (j >= 0 && !result[j].trim().startsWith("/**"))
|
|
113
|
+
j--;
|
|
114
|
+
if (j >= 0) {
|
|
115
|
+
const block = result.slice(j, prevLineIdx + 1).join("\n");
|
|
116
|
+
if (block.includes("@param") || block.includes("@returns")) {
|
|
117
|
+
result.push(line);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Build JSDoc comment
|
|
124
|
+
const paramNames = getParamNames(info.rawParams);
|
|
125
|
+
const indent = line.match(/^(\s*)/)?.[1] || "";
|
|
126
|
+
const jsdocLines = [];
|
|
127
|
+
jsdocLines.push(`${indent}/**`);
|
|
128
|
+
// Add @param tags
|
|
129
|
+
for (const pName of paramNames) {
|
|
130
|
+
if (pName.startsWith("...") || pName.startsWith("{") || pName.includes("=")) {
|
|
131
|
+
// Handle rest, destructured, default params
|
|
132
|
+
const baseName = pName.startsWith("...")
|
|
133
|
+
? pName.slice(3)
|
|
134
|
+
: pName.includes("=")
|
|
135
|
+
? pName.slice(0, pName.indexOf("=")).trim()
|
|
136
|
+
: pName;
|
|
137
|
+
const paramType = annotation.params.find((a) => a.name === baseName);
|
|
138
|
+
if (paramType) {
|
|
139
|
+
jsdocLines.push(`${indent} * @param {${paramType.type}} ${baseName}`);
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Try name match first, then positional
|
|
144
|
+
let paramType = annotation.params.find((a) => a.name === pName);
|
|
145
|
+
if (!paramType) {
|
|
146
|
+
const idx = paramNames.indexOf(pName);
|
|
147
|
+
if (idx >= 0 && idx < annotation.params.length) {
|
|
148
|
+
paramType = annotation.params[idx];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (paramType) {
|
|
152
|
+
jsdocLines.push(`${indent} * @param {${paramType.type}} ${pName}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Add @returns tag
|
|
156
|
+
if (annotation.returnType && annotation.returnType !== "void" && annotation.returnType !== "undefined") {
|
|
157
|
+
jsdocLines.push(`${indent} * @returns {${annotation.returnType}}`);
|
|
158
|
+
}
|
|
159
|
+
jsdocLines.push(`${indent} */`);
|
|
160
|
+
// Only add JSDoc if we have useful content
|
|
161
|
+
if (jsdocLines.length > 2) {
|
|
162
|
+
// Insert JSDoc before the function line
|
|
163
|
+
result.push(...jsdocLines);
|
|
164
|
+
}
|
|
165
|
+
result.push(line);
|
|
166
|
+
}
|
|
167
|
+
return result.join("\n");
|
|
168
|
+
}
|
|
169
|
+
// ── TypeScript annotation (inline types) ──
|
|
170
|
+
/**
|
|
171
|
+
* Annotate a TypeScript file with inline type annotations on function signatures.
|
|
172
|
+
*/
|
|
173
|
+
function annotateTS(source, annotations) {
|
|
174
|
+
const lines = source.split("\n");
|
|
175
|
+
const result = [];
|
|
176
|
+
for (let i = 0; i < lines.length; i++) {
|
|
177
|
+
const line = lines[i];
|
|
178
|
+
const info = matchFunction(line);
|
|
179
|
+
if (!info) {
|
|
180
|
+
result.push(line);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const annotation = annotations[info.fnName];
|
|
184
|
+
if (!annotation) {
|
|
185
|
+
result.push(line);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
// Skip if already typed (has a colon in param list or return annotation)
|
|
189
|
+
if (info.rawParams.includes(":") || line.includes("): ")) {
|
|
190
|
+
result.push(line);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Build typed param list
|
|
194
|
+
const paramNames = getParamNames(info.rawParams);
|
|
195
|
+
const typedParams = paramNames.map((pName) => {
|
|
196
|
+
if (pName.startsWith("...") || pName.startsWith("{") || pName.includes("=")) {
|
|
197
|
+
return pName;
|
|
198
|
+
}
|
|
199
|
+
const paramType = annotation.params.find((a) => a.name === pName);
|
|
200
|
+
if (paramType) {
|
|
201
|
+
return `${pName}: ${paramType.type}`;
|
|
202
|
+
}
|
|
203
|
+
const idx = paramNames.indexOf(pName);
|
|
204
|
+
if (idx >= 0 && idx < annotation.params.length) {
|
|
205
|
+
return `${pName}: ${annotation.params[idx].type}`;
|
|
206
|
+
}
|
|
207
|
+
return pName;
|
|
208
|
+
});
|
|
209
|
+
const typedParamStr = typedParams.join(", ");
|
|
210
|
+
const retType = annotation.returnType;
|
|
211
|
+
if (info.kind === "function") {
|
|
212
|
+
const prefix = info.match[1];
|
|
213
|
+
const rest = line.slice(info.match[0].length);
|
|
214
|
+
const hasReturnType = rest.match(/^\s*:/);
|
|
215
|
+
if (hasReturnType) {
|
|
216
|
+
result.push(`${prefix}${info.fnName}(${typedParamStr})${rest}`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
result.push(`${prefix}${info.fnName}(${typedParamStr}): ${retType}${rest}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (info.kind === "arrow") {
|
|
223
|
+
const prefix = info.match[1];
|
|
224
|
+
const afterMatch = line.slice(info.match[0].length);
|
|
225
|
+
const asyncMatch = line.match(new RegExp(`${info.fnName}\\s*=\\s*(async\\s+)?\\(`));
|
|
226
|
+
const asyncPrefix = asyncMatch?.[1] || "";
|
|
227
|
+
result.push(`${prefix}${info.fnName} = ${asyncPrefix}(${typedParamStr}): ${retType}${afterMatch}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const indent = info.match[1];
|
|
231
|
+
const rest = line.slice(info.match[0].length);
|
|
232
|
+
result.push(`${indent}${info.fnName}(${typedParamStr}): ${retType} {${rest}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result.join("\n");
|
|
236
|
+
}
|
|
237
|
+
// ── Python annotation ──
|
|
238
|
+
/**
|
|
239
|
+
* Annotate a Python file with inline type annotations.
|
|
240
|
+
*/
|
|
241
|
+
function annotatePython(source, annotations) {
|
|
242
|
+
const lines = source.split("\n");
|
|
243
|
+
const result = [];
|
|
244
|
+
const defRe = /^(\s*(?:async\s+)?def\s+)(\w+)\s*\(([^)]*)\)\s*(->\s*\S+\s*)?:/;
|
|
245
|
+
for (let i = 0; i < lines.length; i++) {
|
|
246
|
+
const line = lines[i];
|
|
247
|
+
const match = defRe.exec(line);
|
|
248
|
+
if (!match) {
|
|
249
|
+
result.push(line);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const prefix = match[1];
|
|
253
|
+
const fnName = match[2];
|
|
254
|
+
const rawParams = match[3].trim();
|
|
255
|
+
const existingReturn = match[4];
|
|
256
|
+
const annotation = annotations[fnName];
|
|
257
|
+
if (!annotation) {
|
|
258
|
+
result.push(line);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const paramsHaveTypes = rawParams.split(",").some((p) => {
|
|
262
|
+
const trimmed = p.trim();
|
|
263
|
+
if (trimmed === "self" || trimmed === "cls")
|
|
264
|
+
return false;
|
|
265
|
+
return trimmed.includes(":") && !trimmed.startsWith("*");
|
|
266
|
+
});
|
|
267
|
+
if (paramsHaveTypes && existingReturn) {
|
|
268
|
+
result.push(line);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const paramNames = rawParams ? rawParams.split(",").map((p) => p.trim()) : [];
|
|
272
|
+
const typedParams = paramNames.map((pName) => {
|
|
273
|
+
if (pName === "self" || pName === "cls" || pName.startsWith("*") || pName.includes(":")) {
|
|
274
|
+
return pName;
|
|
275
|
+
}
|
|
276
|
+
const eqIdx = pName.indexOf("=");
|
|
277
|
+
const baseName = eqIdx >= 0 ? pName.slice(0, eqIdx).trim() : pName;
|
|
278
|
+
const defaultPart = eqIdx >= 0 ? ` = ${pName.slice(eqIdx + 1).trim()}` : "";
|
|
279
|
+
const paramType = annotation.params.find((a) => a.name === baseName);
|
|
280
|
+
if (paramType) {
|
|
281
|
+
return `${baseName}: ${paramType.type}${defaultPart}`;
|
|
282
|
+
}
|
|
283
|
+
const nonSelfParams = paramNames.filter((p) => p !== "self" && p !== "cls" && !p.startsWith("*"));
|
|
284
|
+
const idx = nonSelfParams.indexOf(pName);
|
|
285
|
+
if (idx >= 0 && idx < annotation.params.length) {
|
|
286
|
+
return `${baseName}: ${annotation.params[idx].type}${defaultPart}`;
|
|
287
|
+
}
|
|
288
|
+
return pName;
|
|
289
|
+
});
|
|
290
|
+
const typedParamStr = typedParams.join(", ");
|
|
291
|
+
const retAnnotation = existingReturn || ` -> ${annotation.returnType}`;
|
|
292
|
+
result.push(`${prefix}${fnName}(${typedParamStr})${retAnnotation}:`);
|
|
293
|
+
}
|
|
294
|
+
return result.join("\n");
|
|
295
|
+
}
|
|
296
|
+
// ── Main command ──
|
|
297
|
+
async function annotateCommand(file, opts) {
|
|
298
|
+
const filePath = path.resolve(file);
|
|
299
|
+
if (!fs.existsSync(filePath)) {
|
|
300
|
+
console.error(chalk_1.default.red(`\n File not found: ${filePath}\n`));
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
const fileKind = detectFileKind(filePath);
|
|
304
|
+
if (!fileKind) {
|
|
305
|
+
console.error(chalk_1.default.red(`\n Unsupported file type: ${path.extname(filePath)}\n`));
|
|
306
|
+
console.error(chalk_1.default.gray(" Supported: .ts, .tsx, .js, .jsx, .mjs, .cjs, .py, .pyi\n"));
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
// Determine annotation language for the API
|
|
310
|
+
const apiLanguage = fileKind === "python" ? "python" : undefined;
|
|
311
|
+
// Determine annotation mode
|
|
312
|
+
// --jsdoc flag forces JSDoc mode; otherwise JS files get JSDoc automatically
|
|
313
|
+
const useJSDoc = opts.jsdoc || fileKind === "javascript";
|
|
314
|
+
const { annotations } = await (0, api_client_1.fetchAnnotations)({
|
|
315
|
+
env: opts.env,
|
|
316
|
+
language: apiLanguage,
|
|
317
|
+
});
|
|
318
|
+
if (!annotations || Object.keys(annotations).length === 0) {
|
|
319
|
+
console.log(chalk_1.default.yellow("\n No observed types found. Run your code with trickle first.\n"));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const source = fs.readFileSync(filePath, "utf-8");
|
|
323
|
+
// Apply annotations based on file type
|
|
324
|
+
let annotated;
|
|
325
|
+
let mode;
|
|
326
|
+
if (fileKind === "python") {
|
|
327
|
+
annotated = annotatePython(source, annotations);
|
|
328
|
+
mode = "Python type annotations";
|
|
329
|
+
}
|
|
330
|
+
else if (useJSDoc) {
|
|
331
|
+
annotated = annotateJSDoc(source, annotations);
|
|
332
|
+
mode = "JSDoc comments";
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
annotated = annotateTS(source, annotations);
|
|
336
|
+
mode = "TypeScript annotations";
|
|
337
|
+
}
|
|
338
|
+
// Count changes
|
|
339
|
+
const originalLines = source.split("\n");
|
|
340
|
+
const annotatedLines = annotated.split("\n");
|
|
341
|
+
// For JSDoc, count functions annotated (not lines changed, since JSDoc adds lines)
|
|
342
|
+
let changeCount = 0;
|
|
343
|
+
if (useJSDoc && fileKind !== "python") {
|
|
344
|
+
// Count new JSDoc blocks added
|
|
345
|
+
for (let i = 0; i < annotatedLines.length; i++) {
|
|
346
|
+
if (annotatedLines[i].trimEnd().endsWith("/**") && (i >= originalLines.length || originalLines[i] !== annotatedLines[i])) {
|
|
347
|
+
changeCount++;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
for (let i = 0; i < Math.max(originalLines.length, annotatedLines.length); i++) {
|
|
353
|
+
if ((originalLines[i] || "") !== (annotatedLines[i] || ""))
|
|
354
|
+
changeCount++;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (changeCount === 0) {
|
|
358
|
+
console.log(chalk_1.default.gray("\n No annotations to add — functions already typed or not observed.\n"));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (opts.dryRun) {
|
|
362
|
+
console.log(chalk_1.default.cyan(`\n Dry run — ${mode} that would be added:\n`));
|
|
363
|
+
if (useJSDoc && fileKind !== "python") {
|
|
364
|
+
// Show JSDoc blocks that would be inserted
|
|
365
|
+
let inNew = false;
|
|
366
|
+
for (let i = 0; i < annotatedLines.length; i++) {
|
|
367
|
+
const isNewLine = i >= originalLines.length || originalLines[i] !== annotatedLines[i];
|
|
368
|
+
if (isNewLine && !inNew) {
|
|
369
|
+
// Find the function line that follows this JSDoc
|
|
370
|
+
inNew = true;
|
|
371
|
+
}
|
|
372
|
+
if (isNewLine) {
|
|
373
|
+
console.log(chalk_1.default.green(` + ${annotatedLines[i]}`));
|
|
374
|
+
if (annotatedLines[i].trimEnd().endsWith("*/")) {
|
|
375
|
+
inNew = false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
for (let i = 0; i < Math.max(originalLines.length, annotatedLines.length); i++) {
|
|
382
|
+
if ((originalLines[i] || "") !== (annotatedLines[i] || "")) {
|
|
383
|
+
console.log(chalk_1.default.red(` - ${(originalLines[i] || "").trim()}`));
|
|
384
|
+
console.log(chalk_1.default.green(` + ${(annotatedLines[i] || "").trim()}`));
|
|
385
|
+
console.log();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
console.log(chalk_1.default.gray(`\n ${changeCount} function(s) would be annotated with ${mode}.\n`));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// Write annotated file
|
|
393
|
+
fs.writeFileSync(filePath, annotated, "utf-8");
|
|
394
|
+
console.log(chalk_1.default.green(`\n Annotated ${changeCount} function(s) in ${path.relative(process.cwd(), filePath)} (${mode})\n`));
|
|
395
|
+
// Show what changed
|
|
396
|
+
if (useJSDoc && fileKind !== "python") {
|
|
397
|
+
// Show JSDoc blocks that were added
|
|
398
|
+
let inBlock = false;
|
|
399
|
+
let blockLines = [];
|
|
400
|
+
for (let i = 0; i < annotatedLines.length; i++) {
|
|
401
|
+
const isNew = i >= originalLines.length || originalLines[i] !== annotatedLines[i];
|
|
402
|
+
if (isNew && annotatedLines[i].trimEnd().endsWith("/**")) {
|
|
403
|
+
inBlock = true;
|
|
404
|
+
blockLines = [annotatedLines[i]];
|
|
405
|
+
}
|
|
406
|
+
else if (inBlock) {
|
|
407
|
+
blockLines.push(annotatedLines[i]);
|
|
408
|
+
if (annotatedLines[i].trimEnd().endsWith("*/")) {
|
|
409
|
+
inBlock = false;
|
|
410
|
+
// Next line should be the function
|
|
411
|
+
const fnLine = annotatedLines[i + 1] || "";
|
|
412
|
+
const fnMatch = fnLine.match(/(?:function|const|let|var)\s+(\w+)/);
|
|
413
|
+
const fnName = fnMatch?.[1] || "function";
|
|
414
|
+
console.log(chalk_1.default.gray(` ${fnName}:`));
|
|
415
|
+
for (const bl of blockLines) {
|
|
416
|
+
console.log(chalk_1.default.gray(` ${bl.trim()}`));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
for (let i = 0; i < annotatedLines.length; i++) {
|
|
424
|
+
if (i < originalLines.length && originalLines[i] !== annotatedLines[i]) {
|
|
425
|
+
const fnMatch = annotatedLines[i].match(/(?:function|def|const|let|var)\s+(\w+)/);
|
|
426
|
+
const fnName = fnMatch?.[1] || "unknown";
|
|
427
|
+
console.log(chalk_1.default.gray(` ${fnName}:`));
|
|
428
|
+
console.log(chalk_1.default.gray(` ${annotatedLines[i].trim()}`));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
console.log();
|
|
433
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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.auditCommand = auditCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
async function auditCommand(opts) {
|
|
10
|
+
const base = (0, config_1.getBackendUrl)();
|
|
11
|
+
const url = new URL("/api/audit", base);
|
|
12
|
+
if (opts.env)
|
|
13
|
+
url.searchParams.set("env", opts.env);
|
|
14
|
+
let data;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(url.toString());
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
throw new Error(`HTTP ${res.status}`);
|
|
19
|
+
data = (await res.json());
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
console.error(chalk_1.default.red(`\n Cannot connect to trickle backend at ${chalk_1.default.bold(base)}.`));
|
|
23
|
+
console.error(chalk_1.default.red(" Is the backend running?\n"));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
if (opts.json) {
|
|
27
|
+
console.log(JSON.stringify(data, null, 2));
|
|
28
|
+
if (opts.failOnError && data.summary.errors > 0)
|
|
29
|
+
process.exit(1);
|
|
30
|
+
if (opts.failOnWarning && (data.summary.errors + data.summary.warnings) > 0)
|
|
31
|
+
process.exit(1);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log(chalk_1.default.bold(" API Audit Report"));
|
|
36
|
+
console.log(chalk_1.default.gray(` ${data.summary.routesAnalyzed} routes analyzed\n`));
|
|
37
|
+
if (data.issues.length === 0) {
|
|
38
|
+
console.log(chalk_1.default.green(" ✓ No issues found — your API looks clean!\n"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Group by severity
|
|
42
|
+
const errors = data.issues.filter((i) => i.severity === "error");
|
|
43
|
+
const warnings = data.issues.filter((i) => i.severity === "warning");
|
|
44
|
+
const infos = data.issues.filter((i) => i.severity === "info");
|
|
45
|
+
if (errors.length > 0) {
|
|
46
|
+
console.log(chalk_1.default.red.bold(` ✗ ${errors.length} error${errors.length > 1 ? "s" : ""}`));
|
|
47
|
+
for (const issue of errors) {
|
|
48
|
+
const route = issue.route ? chalk_1.default.gray(` [${issue.route}]`) : "";
|
|
49
|
+
console.log(chalk_1.default.red(` • ${issue.message}${route}`));
|
|
50
|
+
}
|
|
51
|
+
console.log("");
|
|
52
|
+
}
|
|
53
|
+
if (warnings.length > 0) {
|
|
54
|
+
console.log(chalk_1.default.yellow.bold(` ⚠ ${warnings.length} warning${warnings.length > 1 ? "s" : ""}`));
|
|
55
|
+
for (const issue of warnings) {
|
|
56
|
+
const route = issue.route ? chalk_1.default.gray(` [${issue.route}]`) : "";
|
|
57
|
+
console.log(chalk_1.default.yellow(` • ${issue.message}${route}`));
|
|
58
|
+
}
|
|
59
|
+
console.log("");
|
|
60
|
+
}
|
|
61
|
+
if (infos.length > 0) {
|
|
62
|
+
console.log(chalk_1.default.blue.bold(` ℹ ${infos.length} info`));
|
|
63
|
+
for (const issue of infos) {
|
|
64
|
+
const route = issue.route ? chalk_1.default.gray(` [${issue.route}]`) : "";
|
|
65
|
+
console.log(chalk_1.default.blue(` • ${issue.message}${route}`));
|
|
66
|
+
}
|
|
67
|
+
console.log("");
|
|
68
|
+
}
|
|
69
|
+
// Summary line
|
|
70
|
+
const parts = [];
|
|
71
|
+
if (errors.length > 0)
|
|
72
|
+
parts.push(chalk_1.default.red(`${errors.length} errors`));
|
|
73
|
+
if (warnings.length > 0)
|
|
74
|
+
parts.push(chalk_1.default.yellow(`${warnings.length} warnings`));
|
|
75
|
+
if (infos.length > 0)
|
|
76
|
+
parts.push(chalk_1.default.blue(`${infos.length} info`));
|
|
77
|
+
console.log(chalk_1.default.gray(` Total: ${parts.join(", ")}\n`));
|
|
78
|
+
if (opts.failOnError && data.summary.errors > 0)
|
|
79
|
+
process.exit(1);
|
|
80
|
+
if (opts.failOnWarning && (data.summary.errors + data.summary.warnings) > 0)
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|