@hustle-together/api-dev-tools 3.11.1 โ 3.12.1
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/.claude/agents/code-reviewer.md +170 -0
- package/.claude/agents/docs-generator.md +80 -0
- package/.claude/agents/implementation-reviewer.md +119 -0
- package/.claude/agents/parallel-researcher.md +52 -0
- package/.claude/agents/research-validator.md +116 -0
- package/.claude/agents/schema-generator.md +70 -0
- package/.claude/agents/test-writer.md +104 -0
- package/.claude/api-dev-state.json +228 -56
- package/.claude/commands/README.md +21 -10
- package/.claude/commands/add-command.md +8 -5
- package/.claude/commands/api-create.md +36 -25
- package/.claude/commands/api-env.md +1 -0
- package/.claude/commands/api-interview.md +32 -19
- package/.claude/commands/api-research.md +47 -21
- package/.claude/commands/api-status.md +21 -1
- package/.claude/commands/api-verify.md +14 -13
- package/.claude/commands/beepboop.md +4 -5
- package/.claude/commands/busycommit.md +2 -3
- package/.claude/commands/commit.md +2 -3
- package/.claude/commands/cycle.md +2 -7
- package/.claude/commands/gap.md +2 -3
- package/.claude/commands/green.md +2 -7
- package/.claude/commands/issue.md +3 -8
- package/.claude/commands/ntfy-setup.md +91 -0
- package/.claude/commands/ntfy-test.md +74 -0
- package/.claude/commands/plan.md +2 -3
- package/.claude/commands/pr.md +2 -3
- package/.claude/commands/publish.md +40 -0
- package/.claude/commands/red.md +2 -7
- package/.claude/commands/refactor.md +2 -7
- package/.claude/commands/spike.md +2 -7
- package/.claude/commands/summarize.md +2 -3
- package/.claude/commands/tdd.md +2 -7
- package/.claude/commands/worktree-add.md +208 -216
- package/.claude/commands/worktree-cleanup.md +172 -178
- package/.claude/settings.json +63 -12
- package/.claude/settings.local.json +2 -1
- package/.claude-plugin/marketplace.json +2 -11
- package/.skills/README.md +55 -53
- package/.skills/_shared/settings.json +1 -1
- package/.skills/add-command/SKILL.md +10 -5
- package/.skills/api-create/SKILL.md +146 -35
- package/.skills/api-env/SKILL.md +1 -0
- package/.skills/api-interview/SKILL.md +32 -19
- package/.skills/api-research/SKILL.md +47 -21
- package/.skills/api-status/SKILL.md +21 -1
- package/.skills/api-verify/SKILL.md +14 -13
- package/.skills/beepboop/SKILL.md +6 -5
- package/.skills/busycommit/SKILL.md +4 -3
- package/.skills/commit/SKILL.md +4 -3
- package/.skills/cycle/SKILL.md +4 -7
- package/.skills/gap/SKILL.md +4 -3
- package/.skills/green/SKILL.md +4 -7
- package/.skills/issue/SKILL.md +5 -8
- package/.skills/plan/SKILL.md +4 -3
- package/.skills/pr/SKILL.md +4 -3
- package/.skills/publish/SKILL.md +160 -0
- package/.skills/red/SKILL.md +4 -7
- package/.skills/refactor/SKILL.md +4 -7
- package/.skills/spike/SKILL.md +4 -7
- package/.skills/summarize/SKILL.md +4 -3
- package/.skills/tdd/SKILL.md +4 -7
- package/.skills/update-todos/SKILL.md +22 -0
- package/.skills/worktree-add/SKILL.md +210 -216
- package/.skills/worktree-cleanup/SKILL.md +183 -187
- package/CHANGELOG.md +97 -79
- package/README.md +161 -7142
- package/bin/cli.js +448 -805
- package/commands/README.md +66 -31
- package/commands/add-command.md +8 -5
- package/commands/beepboop.md +4 -5
- package/commands/busycommit.md +2 -3
- package/commands/commit.md +2 -3
- package/commands/cycle.md +2 -7
- package/commands/gap.md +2 -3
- package/commands/green.md +2 -7
- package/commands/hustle-api-continue.md +8 -5
- package/commands/hustle-api-create.md +70 -29
- package/commands/hustle-api-env.md +1 -0
- package/commands/hustle-api-interview.md +32 -19
- package/commands/hustle-api-research.md +47 -21
- package/commands/hustle-api-sessions.md +8 -7
- package/commands/hustle-api-status.md +21 -1
- package/commands/hustle-api-verify.md +14 -13
- package/commands/hustle-combine.md +488 -241
- package/commands/hustle-ui-create-page.md +113 -50
- package/commands/hustle-ui-create.md +179 -26
- package/commands/issue.md +3 -8
- package/commands/plan.md +2 -3
- package/commands/pr.md +2 -3
- package/commands/red.md +2 -7
- package/commands/refactor.md +2 -7
- package/commands/spike.md +2 -7
- package/commands/summarize.md +2 -3
- package/commands/tdd.md +2 -7
- package/commands/worktree-add.md +208 -216
- package/commands/worktree-cleanup.md +172 -178
- package/hooks/api-workflow-check.py +5 -3
- package/hooks/enforce-component-type-confirm.py +97 -0
- package/hooks/lib/__init__.py +1 -0
- package/hooks/lib/greptile.py +355 -0
- package/hooks/lib/ntfy.py +209 -0
- package/hooks/notify-input-needed.py +73 -0
- package/hooks/notify-phase-complete.py +90 -0
- package/hooks/run-code-review.py +246 -0
- package/hooks/track-token-usage.py +121 -0
- package/package.json +13 -3
- package/scripts/collect-test-results.ts +102 -77
- package/scripts/extract-parameters.ts +112 -70
- package/scripts/generate-test-manifest.ts +118 -77
- package/templates/.env.example +57 -0
- package/templates/BRAND_GUIDE.md +92 -52
- package/templates/CLAUDE-SECTION.md +40 -37
- package/templates/SPEC.json +186 -38
- package/templates/api-dev-state.json +33 -4
- package/templates/api-showcase/_components/APICard.tsx +22 -18
- package/templates/api-showcase/_components/APIModal.tsx +110 -64
- package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
- package/templates/api-showcase/_components/APITester.tsx +128 -67
- package/templates/api-showcase/page.tsx +4 -4
- package/templates/api-test/page.tsx +51 -30
- package/templates/api-test/test-structure/route.ts +43 -34
- package/templates/component/Component.stories.tsx +41 -39
- package/templates/component/Component.test.tsx +96 -78
- package/templates/component/Component.tsx +63 -52
- package/templates/component/Component.types.ts +10 -6
- package/templates/component/Component.visual.spec.ts +170 -0
- package/templates/component/index.ts +2 -2
- package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
- package/templates/dev-tools/page.tsx +4 -3
- package/templates/mcp-servers.json +30 -2
- package/templates/page/page.e2e.test.ts +56 -48
- package/templates/page/page.tsx +3 -3
- package/templates/shared/HeroHeader.tsx +16 -15
- package/templates/shared/index.ts +1 -1
- package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
- package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
- package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
- package/templates/ui-showcase/page.tsx +4 -4
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* @generated by @hustle-together/api-dev-tools v3.0
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import fs from
|
|
21
|
-
import path from
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path from "path";
|
|
22
22
|
|
|
23
23
|
// ============================================
|
|
24
24
|
// Types
|
|
@@ -26,7 +26,7 @@ import path from 'path';
|
|
|
26
26
|
|
|
27
27
|
interface ParameterDefinition {
|
|
28
28
|
name: string;
|
|
29
|
-
location:
|
|
29
|
+
location: "query" | "body" | "header" | "path";
|
|
30
30
|
type: string;
|
|
31
31
|
required: boolean;
|
|
32
32
|
description?: string;
|
|
@@ -62,7 +62,11 @@ interface ParameterMatrix {
|
|
|
62
62
|
// File Discovery
|
|
63
63
|
// ============================================
|
|
64
64
|
|
|
65
|
-
function findFiles(
|
|
65
|
+
function findFiles(
|
|
66
|
+
baseDir: string,
|
|
67
|
+
pattern: RegExp,
|
|
68
|
+
exclude: string[] = ["node_modules", ".git", "dist"],
|
|
69
|
+
): string[] {
|
|
66
70
|
const files: string[] = [];
|
|
67
71
|
|
|
68
72
|
function walk(dir: string) {
|
|
@@ -120,7 +124,10 @@ function extractFromZodSchema(content: string): ParameterDefinition[] {
|
|
|
120
124
|
return params;
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
function parseZodChain(
|
|
127
|
+
function parseZodChain(
|
|
128
|
+
name: string,
|
|
129
|
+
chain: string,
|
|
130
|
+
): ParameterDefinition | null {
|
|
124
131
|
// Determine base type
|
|
125
132
|
const typeMatch = chain.match(/z\.(\w+)/);
|
|
126
133
|
if (!typeMatch) return null;
|
|
@@ -129,13 +136,13 @@ function parseZodChain(name: string, chain: string): ParameterDefinition | null
|
|
|
129
136
|
|
|
130
137
|
const param: ParameterDefinition = {
|
|
131
138
|
name,
|
|
132
|
-
location:
|
|
139
|
+
location: "body", // Default, will be refined based on context
|
|
133
140
|
type: mapZodType(baseType),
|
|
134
|
-
required: true
|
|
141
|
+
required: true,
|
|
135
142
|
};
|
|
136
143
|
|
|
137
144
|
// Check for optional
|
|
138
|
-
if (chain.includes(
|
|
145
|
+
if (chain.includes(".optional()") || chain.includes(".nullable()")) {
|
|
139
146
|
param.required = false;
|
|
140
147
|
}
|
|
141
148
|
|
|
@@ -151,18 +158,18 @@ function parseZodChain(name: string, chain: string): ParameterDefinition | null
|
|
|
151
158
|
try {
|
|
152
159
|
param.default = JSON.parse(defaultMatch[1].replace(/'/g, '"'));
|
|
153
160
|
} catch {
|
|
154
|
-
param.default = defaultMatch[1].replace(/['"]/g,
|
|
161
|
+
param.default = defaultMatch[1].replace(/['"]/g, "");
|
|
155
162
|
}
|
|
156
163
|
param.required = false; // Has default = not required
|
|
157
164
|
}
|
|
158
165
|
|
|
159
166
|
// Extract enum values
|
|
160
|
-
if (baseType ===
|
|
167
|
+
if (baseType === "enum") {
|
|
161
168
|
const enumMatch = chain.match(/z\.enum\s*\(\s*\[([^\]]+)\]/);
|
|
162
169
|
if (enumMatch) {
|
|
163
170
|
param.enum = enumMatch[1]
|
|
164
|
-
.split(
|
|
165
|
-
.map(s => s.trim().replace(/['"`]/g,
|
|
171
|
+
.split(",")
|
|
172
|
+
.map((s) => s.trim().replace(/['"`]/g, ""));
|
|
166
173
|
}
|
|
167
174
|
}
|
|
168
175
|
|
|
@@ -182,16 +189,16 @@ function parseZodChain(name: string, chain: string): ParameterDefinition | null
|
|
|
182
189
|
|
|
183
190
|
function mapZodType(zodType: string): string {
|
|
184
191
|
const typeMap: Record<string, string> = {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
string: "string",
|
|
193
|
+
number: "number",
|
|
194
|
+
boolean: "boolean",
|
|
195
|
+
array: "array",
|
|
196
|
+
object: "object",
|
|
197
|
+
enum: "enum",
|
|
198
|
+
literal: "literal",
|
|
199
|
+
union: "union",
|
|
200
|
+
date: "string (ISO date)",
|
|
201
|
+
coerce: "coerced",
|
|
195
202
|
};
|
|
196
203
|
return typeMap[zodType] || zodType;
|
|
197
204
|
}
|
|
@@ -200,7 +207,10 @@ function mapZodType(zodType: string): string {
|
|
|
200
207
|
// Route File Extractor
|
|
201
208
|
// ============================================
|
|
202
209
|
|
|
203
|
-
function extractFromRouteFile(
|
|
210
|
+
function extractFromRouteFile(
|
|
211
|
+
content: string,
|
|
212
|
+
existingParams: ParameterDefinition[],
|
|
213
|
+
): ParameterDefinition[] {
|
|
204
214
|
const params: ParameterDefinition[] = [];
|
|
205
215
|
|
|
206
216
|
// Extract query parameters from searchParams usage
|
|
@@ -209,12 +219,15 @@ function extractFromRouteFile(content: string, existingParams: ParameterDefiniti
|
|
|
209
219
|
let match;
|
|
210
220
|
while ((match = queryParamRegex.exec(content)) !== null) {
|
|
211
221
|
const name = match[1];
|
|
212
|
-
if (
|
|
222
|
+
if (
|
|
223
|
+
!existingParams.some((p) => p.name === name) &&
|
|
224
|
+
!params.some((p) => p.name === name)
|
|
225
|
+
) {
|
|
213
226
|
params.push({
|
|
214
227
|
name,
|
|
215
|
-
location:
|
|
216
|
-
type:
|
|
217
|
-
required: false // Query params are typically optional
|
|
228
|
+
location: "query",
|
|
229
|
+
type: "string",
|
|
230
|
+
required: false, // Query params are typically optional
|
|
218
231
|
});
|
|
219
232
|
}
|
|
220
233
|
}
|
|
@@ -224,12 +237,15 @@ function extractFromRouteFile(content: string, existingParams: ParameterDefiniti
|
|
|
224
237
|
const headerRegex = /headers\.get\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
225
238
|
while ((match = headerRegex.exec(content)) !== null) {
|
|
226
239
|
const name = match[1];
|
|
227
|
-
if (
|
|
240
|
+
if (
|
|
241
|
+
!existingParams.some((p) => p.name === name) &&
|
|
242
|
+
!params.some((p) => p.name === name)
|
|
243
|
+
) {
|
|
228
244
|
params.push({
|
|
229
245
|
name,
|
|
230
|
-
location:
|
|
231
|
-
type:
|
|
232
|
-
required: false
|
|
246
|
+
location: "header",
|
|
247
|
+
type: "string",
|
|
248
|
+
required: false,
|
|
233
249
|
});
|
|
234
250
|
}
|
|
235
251
|
}
|
|
@@ -239,12 +255,15 @@ function extractFromRouteFile(content: string, existingParams: ParameterDefiniti
|
|
|
239
255
|
const pathParamRegex = /params\.(\w+)|{\s*(\w+)\s*}\s*=\s*params/g;
|
|
240
256
|
while ((match = pathParamRegex.exec(content)) !== null) {
|
|
241
257
|
const name = match[1] || match[2];
|
|
242
|
-
if (
|
|
258
|
+
if (
|
|
259
|
+
!existingParams.some((p) => p.name === name) &&
|
|
260
|
+
!params.some((p) => p.name === name)
|
|
261
|
+
) {
|
|
243
262
|
params.push({
|
|
244
263
|
name,
|
|
245
|
-
location:
|
|
246
|
-
type:
|
|
247
|
-
required: true // Path params are always required
|
|
264
|
+
location: "path",
|
|
265
|
+
type: "string",
|
|
266
|
+
required: true, // Path params are always required
|
|
248
267
|
});
|
|
249
268
|
}
|
|
250
269
|
}
|
|
@@ -268,7 +287,7 @@ function extractTestedParams(content: string): string[] {
|
|
|
268
287
|
const bodyContent = match[1];
|
|
269
288
|
const paramNames = bodyContent.match(/(\w+)\s*:/g);
|
|
270
289
|
if (paramNames) {
|
|
271
|
-
paramNames.forEach(p => testedParams.add(p.replace(
|
|
290
|
+
paramNames.forEach((p) => testedParams.add(p.replace(":", "").trim()));
|
|
272
291
|
}
|
|
273
292
|
}
|
|
274
293
|
|
|
@@ -292,7 +311,7 @@ function extractTestedParams(content: string): string[] {
|
|
|
292
311
|
// ============================================
|
|
293
312
|
|
|
294
313
|
function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
295
|
-
console.log(
|
|
314
|
+
console.log("๐ Scanning for parameter sources...");
|
|
296
315
|
|
|
297
316
|
// Find files
|
|
298
317
|
const schemaFiles = findFiles(baseDir, /schema.*\.ts$|schemas?\/.*\.ts$/);
|
|
@@ -307,7 +326,7 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
307
326
|
|
|
308
327
|
// Process schema files
|
|
309
328
|
for (const schemaFile of schemaFiles) {
|
|
310
|
-
const content = fs.readFileSync(schemaFile,
|
|
329
|
+
const content = fs.readFileSync(schemaFile, "utf-8");
|
|
311
330
|
const params = extractFromZodSchema(content);
|
|
312
331
|
|
|
313
332
|
if (params.length > 0) {
|
|
@@ -315,20 +334,25 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
315
334
|
|
|
316
335
|
// Determine endpoint from file path
|
|
317
336
|
const pathParts = relativePath.split(path.sep);
|
|
318
|
-
const apiIndex = pathParts.findIndex(p => p ===
|
|
319
|
-
let endpoint =
|
|
337
|
+
const apiIndex = pathParts.findIndex((p) => p === "api");
|
|
338
|
+
let endpoint = "/api/unknown";
|
|
320
339
|
|
|
321
340
|
if (apiIndex >= 0) {
|
|
322
|
-
endpoint =
|
|
341
|
+
endpoint =
|
|
342
|
+
"/" +
|
|
343
|
+
pathParts
|
|
344
|
+
.slice(apiIndex)
|
|
345
|
+
.join("/")
|
|
346
|
+
.replace(/\/schema.*\.ts$/, "");
|
|
323
347
|
}
|
|
324
348
|
|
|
325
349
|
const existing = endpointsMap.get(endpoint) || {
|
|
326
350
|
endpoint,
|
|
327
|
-
method:
|
|
351
|
+
method: "POST",
|
|
328
352
|
parameters: [],
|
|
329
353
|
sourceFiles: [],
|
|
330
354
|
testedParameters: [],
|
|
331
|
-
untestedParameters: []
|
|
355
|
+
untestedParameters: [],
|
|
332
356
|
};
|
|
333
357
|
|
|
334
358
|
existing.parameters.push(...params);
|
|
@@ -342,30 +366,33 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
342
366
|
|
|
343
367
|
// Process route files
|
|
344
368
|
for (const routeFile of routeFiles) {
|
|
345
|
-
const content = fs.readFileSync(routeFile,
|
|
369
|
+
const content = fs.readFileSync(routeFile, "utf-8");
|
|
346
370
|
const relativePath = path.relative(baseDir, routeFile);
|
|
347
371
|
|
|
348
372
|
// Determine endpoint
|
|
349
373
|
const pathParts = relativePath.split(path.sep);
|
|
350
|
-
const apiIndex = pathParts.findIndex(p => p ===
|
|
351
|
-
let endpoint =
|
|
374
|
+
const apiIndex = pathParts.findIndex((p) => p === "api");
|
|
375
|
+
let endpoint = "/api/unknown";
|
|
352
376
|
|
|
353
377
|
if (apiIndex >= 0) {
|
|
354
|
-
endpoint =
|
|
378
|
+
endpoint = "/" + pathParts.slice(apiIndex, -1).join("/");
|
|
355
379
|
}
|
|
356
380
|
|
|
357
381
|
const existing = endpointsMap.get(endpoint) || {
|
|
358
382
|
endpoint,
|
|
359
|
-
method:
|
|
383
|
+
method: "GET",
|
|
360
384
|
parameters: [],
|
|
361
385
|
sourceFiles: [],
|
|
362
386
|
testedParameters: [],
|
|
363
|
-
untestedParameters: []
|
|
387
|
+
untestedParameters: [],
|
|
364
388
|
};
|
|
365
389
|
|
|
366
390
|
// Determine method
|
|
367
|
-
if (
|
|
368
|
-
|
|
391
|
+
if (
|
|
392
|
+
content.includes("export async function POST") ||
|
|
393
|
+
content.includes("export const POST")
|
|
394
|
+
) {
|
|
395
|
+
existing.method = "POST";
|
|
369
396
|
}
|
|
370
397
|
|
|
371
398
|
const routeParams = extractFromRouteFile(content, existing.parameters);
|
|
@@ -384,7 +411,7 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
384
411
|
|
|
385
412
|
// Process test files to find tested parameters
|
|
386
413
|
for (const testFile of testFiles) {
|
|
387
|
-
const content = fs.readFileSync(testFile,
|
|
414
|
+
const content = fs.readFileSync(testFile, "utf-8");
|
|
388
415
|
const relativePath = path.relative(baseDir, testFile);
|
|
389
416
|
|
|
390
417
|
// Determine endpoint from test file
|
|
@@ -396,7 +423,9 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
396
423
|
|
|
397
424
|
if (existing) {
|
|
398
425
|
const testedParams = extractTestedParams(content);
|
|
399
|
-
existing.testedParameters = [
|
|
426
|
+
existing.testedParameters = [
|
|
427
|
+
...new Set([...existing.testedParameters, ...testedParams]),
|
|
428
|
+
];
|
|
400
429
|
|
|
401
430
|
console.log(`\n๐งช Test: ${relativePath}`);
|
|
402
431
|
console.log(` Found ${testedParams.length} tested parameters`);
|
|
@@ -407,9 +436,11 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
407
436
|
let totalParams = 0;
|
|
408
437
|
let testedParams = 0;
|
|
409
438
|
|
|
410
|
-
const endpoints = Array.from(endpointsMap.values()).map(ep => {
|
|
411
|
-
const allParamNames = ep.parameters.map(p => p.name);
|
|
412
|
-
ep.untestedParameters = allParamNames.filter(
|
|
439
|
+
const endpoints = Array.from(endpointsMap.values()).map((ep) => {
|
|
440
|
+
const allParamNames = ep.parameters.map((p) => p.name);
|
|
441
|
+
ep.untestedParameters = allParamNames.filter(
|
|
442
|
+
(p) => !ep.testedParameters.includes(p),
|
|
443
|
+
);
|
|
413
444
|
|
|
414
445
|
totalParams += ep.parameters.length;
|
|
415
446
|
testedParams += ep.testedParameters.length;
|
|
@@ -418,14 +449,15 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
418
449
|
});
|
|
419
450
|
|
|
420
451
|
return {
|
|
421
|
-
version:
|
|
452
|
+
version: "3.0.0",
|
|
422
453
|
generatedAt: new Date().toISOString(),
|
|
423
454
|
endpoints,
|
|
424
455
|
coverage: {
|
|
425
456
|
totalParameters: totalParams,
|
|
426
457
|
testedParameters: testedParams,
|
|
427
|
-
coveragePercent:
|
|
428
|
-
|
|
458
|
+
coveragePercent:
|
|
459
|
+
totalParams > 0 ? Math.round((testedParams / totalParams) * 100) : 0,
|
|
460
|
+
},
|
|
429
461
|
};
|
|
430
462
|
}
|
|
431
463
|
|
|
@@ -436,12 +468,18 @@ function extractAllParameters(baseDir: string): ParameterMatrix {
|
|
|
436
468
|
function main() {
|
|
437
469
|
const args = process.argv.slice(2);
|
|
438
470
|
const baseDir = args[0] || process.cwd();
|
|
439
|
-
const outputPath =
|
|
471
|
+
const outputPath =
|
|
472
|
+
args[1] ||
|
|
473
|
+
path.join(baseDir, "src", "app", "api-test", "parameter-matrix.json");
|
|
440
474
|
|
|
441
|
-
console.log(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
console.log(
|
|
475
|
+
console.log(
|
|
476
|
+
"โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ",
|
|
477
|
+
);
|
|
478
|
+
console.log(" ๐ Parameter Matrix Extractor");
|
|
479
|
+
console.log(" @hustle-together/api-dev-tools v3.0");
|
|
480
|
+
console.log(
|
|
481
|
+
"โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ",
|
|
482
|
+
);
|
|
445
483
|
console.log(`\n๐ Base directory: ${baseDir}`);
|
|
446
484
|
console.log(`๐ Output file: ${outputPath}\n`);
|
|
447
485
|
|
|
@@ -456,22 +494,26 @@ function main() {
|
|
|
456
494
|
// Write matrix
|
|
457
495
|
fs.writeFileSync(outputPath, JSON.stringify(matrix, null, 2));
|
|
458
496
|
|
|
459
|
-
console.log(
|
|
460
|
-
|
|
461
|
-
|
|
497
|
+
console.log(
|
|
498
|
+
"\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ",
|
|
499
|
+
);
|
|
500
|
+
console.log(" โ
Parameter matrix generated successfully!");
|
|
501
|
+
console.log(
|
|
502
|
+
"โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ",
|
|
503
|
+
);
|
|
462
504
|
console.log(`\n๐ Coverage Summary:`);
|
|
463
505
|
console.log(` โข Total parameters: ${matrix.coverage.totalParameters}`);
|
|
464
506
|
console.log(` โข Tested parameters: ${matrix.coverage.testedParameters}`);
|
|
465
507
|
console.log(` โข Coverage: ${matrix.coverage.coveragePercent}%`);
|
|
466
508
|
|
|
467
509
|
// List untested parameters
|
|
468
|
-
const untested = matrix.endpoints.flatMap(ep =>
|
|
469
|
-
ep.untestedParameters.map(p => `${ep.endpoint}: ${p}`)
|
|
510
|
+
const untested = matrix.endpoints.flatMap((ep) =>
|
|
511
|
+
ep.untestedParameters.map((p) => `${ep.endpoint}: ${p}`),
|
|
470
512
|
);
|
|
471
513
|
|
|
472
514
|
if (untested.length > 0) {
|
|
473
515
|
console.log(`\nโ ๏ธ Untested parameters (${untested.length}):`);
|
|
474
|
-
untested.slice(0, 10).forEach(p => console.log(` โข ${p}`));
|
|
516
|
+
untested.slice(0, 10).forEach((p) => console.log(` โข ${p}`));
|
|
475
517
|
if (untested.length > 10) {
|
|
476
518
|
console.log(` ... and ${untested.length - 10} more`);
|
|
477
519
|
}
|