@hustle-together/api-dev-tools 3.10.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 +331 -0
- package/.claude/commands/README.md +196 -0
- package/.claude/commands/add-command.md +212 -0
- package/.claude/commands/api-create.md +510 -0
- package/.claude/commands/api-env.md +51 -0
- package/.claude/commands/api-interview.md +344 -0
- package/.claude/commands/api-research.md +357 -0
- package/.claude/commands/api-status.md +279 -0
- package/.claude/commands/api-verify.md +232 -0
- package/.claude/commands/beepboop.md +96 -0
- package/.claude/commands/busycommit.md +111 -0
- package/.claude/commands/commit.md +82 -0
- package/.claude/commands/cycle.md +137 -0
- package/.claude/commands/gap.md +85 -0
- package/.claude/commands/green.md +137 -0
- package/.claude/commands/issue.md +187 -0
- package/.claude/commands/ntfy-setup.md +91 -0
- package/.claude/commands/ntfy-test.md +74 -0
- package/.claude/commands/plan.md +167 -0
- package/.claude/commands/pr.md +121 -0
- package/.claude/commands/publish.md +40 -0
- package/.claude/commands/red.md +137 -0
- package/.claude/commands/refactor.md +137 -0
- package/.claude/commands/spike.md +137 -0
- package/.claude/commands/summarize.md +93 -0
- package/.claude/commands/tdd.md +139 -0
- package/.claude/commands/worktree-add.md +307 -0
- package/.claude/commands/worktree-cleanup.md +275 -0
- package/.claude/hooks/api-workflow-check.py +227 -0
- package/.claude/hooks/enforce-deep-research.py +185 -0
- package/.claude/hooks/enforce-disambiguation.py +155 -0
- package/.claude/hooks/enforce-documentation.py +192 -0
- package/.claude/hooks/enforce-environment.py +253 -0
- package/.claude/hooks/enforce-external-research.py +328 -0
- package/.claude/hooks/enforce-interview.py +421 -0
- package/.claude/hooks/enforce-refactor.py +189 -0
- package/.claude/hooks/enforce-research.py +159 -0
- package/.claude/hooks/enforce-schema.py +186 -0
- package/.claude/hooks/enforce-scope.py +160 -0
- package/.claude/hooks/enforce-tdd-red.py +250 -0
- package/.claude/hooks/enforce-verify.py +186 -0
- package/.claude/hooks/periodic-reground.py +154 -0
- package/.claude/hooks/session-startup.py +151 -0
- package/.claude/hooks/track-tool-use.py +626 -0
- package/.claude/hooks/verify-after-green.py +282 -0
- package/.claude/hooks/verify-implementation.py +225 -0
- package/.claude/research/index.json +6 -0
- package/.claude/settings.json +144 -0
- package/.claude/settings.local.json +12 -0
- package/.claude-plugin/marketplace.json +103 -0
- package/.skills/README.md +293 -0
- package/.skills/_shared/convert-commands.py +192 -0
- package/.skills/_shared/hooks/api-workflow-check.py +227 -0
- package/.skills/_shared/hooks/enforce-deep-research.py +185 -0
- package/.skills/_shared/hooks/enforce-disambiguation.py +155 -0
- package/.skills/_shared/hooks/enforce-documentation.py +192 -0
- package/.skills/_shared/hooks/enforce-environment.py +253 -0
- package/.skills/_shared/hooks/enforce-external-research.py +328 -0
- package/.skills/_shared/hooks/enforce-interview.py +421 -0
- package/.skills/_shared/hooks/enforce-refactor.py +189 -0
- package/.skills/_shared/hooks/enforce-research.py +159 -0
- package/.skills/_shared/hooks/enforce-schema.py +186 -0
- package/.skills/_shared/hooks/enforce-scope.py +160 -0
- package/.skills/_shared/hooks/enforce-tdd-red.py +250 -0
- package/.skills/_shared/hooks/enforce-verify.py +186 -0
- package/.skills/_shared/hooks/periodic-reground.py +154 -0
- package/.skills/_shared/hooks/session-startup.py +151 -0
- package/.skills/_shared/hooks/track-tool-use.py +626 -0
- package/.skills/_shared/hooks/verify-after-green.py +282 -0
- package/.skills/_shared/hooks/verify-implementation.py +225 -0
- package/.skills/_shared/install.sh +114 -0
- package/.skills/_shared/settings.json +93 -0
- package/.skills/add-command/SKILL.md +227 -0
- package/.skills/api-create/SKILL.md +623 -0
- package/.skills/api-env/SKILL.md +64 -0
- package/.skills/api-interview/SKILL.md +357 -0
- package/.skills/api-research/SKILL.md +370 -0
- package/.skills/api-status/SKILL.md +292 -0
- package/.skills/api-verify/SKILL.md +245 -0
- package/.skills/beepboop/SKILL.md +111 -0
- package/.skills/busycommit/SKILL.md +126 -0
- package/.skills/commit/SKILL.md +97 -0
- package/.skills/cycle/SKILL.md +152 -0
- package/.skills/gap/SKILL.md +100 -0
- package/.skills/green/SKILL.md +152 -0
- package/.skills/issue/SKILL.md +202 -0
- package/.skills/plan/SKILL.md +182 -0
- package/.skills/pr/SKILL.md +136 -0
- package/.skills/publish/SKILL.md +160 -0
- package/.skills/red/SKILL.md +152 -0
- package/.skills/refactor/SKILL.md +152 -0
- package/.skills/spike/SKILL.md +152 -0
- package/.skills/summarize/SKILL.md +108 -0
- package/.skills/tdd/SKILL.md +154 -0
- package/.skills/update-todos/SKILL.md +250 -0
- package/.skills/worktree-add/SKILL.md +322 -0
- package/.skills/worktree-cleanup/SKILL.md +290 -0
- package/CHANGELOG.md +115 -0
- package/README.md +161 -7101
- 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 +33 -12
- 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
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* @generated by @hustle-together/api-dev-tools v3.0
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import fs from
|
|
18
|
-
import path from
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
19
|
|
|
20
20
|
// ============================================
|
|
21
21
|
// Types
|
|
@@ -80,7 +80,11 @@ interface ManifestOutput {
|
|
|
80
80
|
// File Discovery
|
|
81
81
|
// ============================================
|
|
82
82
|
|
|
83
|
-
function findFiles(
|
|
83
|
+
function findFiles(
|
|
84
|
+
baseDir: string,
|
|
85
|
+
pattern: RegExp,
|
|
86
|
+
exclude: string[] = ["node_modules", ".git", "dist"],
|
|
87
|
+
): string[] {
|
|
84
88
|
const files: string[] = [];
|
|
85
89
|
|
|
86
90
|
function walk(dir: string) {
|
|
@@ -109,9 +113,13 @@ function findFiles(baseDir: string, pattern: RegExp, exclude: string[] = ['node_
|
|
|
109
113
|
// Test File Parser
|
|
110
114
|
// ============================================
|
|
111
115
|
|
|
112
|
-
function parseTestFile(filePath: string): {
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
function parseTestFile(filePath: string): {
|
|
117
|
+
groups: TestGroup[];
|
|
118
|
+
endpoint?: string;
|
|
119
|
+
method?: string;
|
|
120
|
+
} {
|
|
121
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
122
|
+
const lines = content.split("\n");
|
|
115
123
|
|
|
116
124
|
const rootGroups: TestGroup[] = [];
|
|
117
125
|
const groupStack: TestGroup[] = [];
|
|
@@ -130,7 +138,9 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
|
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
// Look for HTTP method
|
|
133
|
-
const methodMatch = content.match(
|
|
141
|
+
const methodMatch = content.match(
|
|
142
|
+
/(?:GET|POST|PUT|DELETE|PATCH)\s*(?:request|endpoint)?/i,
|
|
143
|
+
);
|
|
134
144
|
if (methodMatch) {
|
|
135
145
|
method = methodMatch[0].split(/\s/)[0].toUpperCase();
|
|
136
146
|
}
|
|
@@ -145,7 +155,7 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
|
|
|
145
155
|
const group: TestGroup = {
|
|
146
156
|
name: describeMatch[1],
|
|
147
157
|
tests: [],
|
|
148
|
-
groups: []
|
|
158
|
+
groups: [],
|
|
149
159
|
};
|
|
150
160
|
|
|
151
161
|
if (groupStack.length > 0) {
|
|
@@ -163,11 +173,11 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
|
|
|
163
173
|
if (testMatch && groupStack.length > 0) {
|
|
164
174
|
const testCase: TestCase = {
|
|
165
175
|
name: testMatch[1],
|
|
166
|
-
line: lineNum
|
|
176
|
+
line: lineNum,
|
|
167
177
|
};
|
|
168
178
|
|
|
169
179
|
// Extract description from test name
|
|
170
|
-
if (testCase.name.includes(
|
|
180
|
+
if (testCase.name.includes("should")) {
|
|
171
181
|
testCase.description = testCase.name;
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -196,7 +206,7 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
|
|
|
196
206
|
// ============================================
|
|
197
207
|
|
|
198
208
|
function parseZodSchema(filePath: string): ParameterInfo[] {
|
|
199
|
-
const content = fs.readFileSync(filePath,
|
|
209
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
200
210
|
const parameters: ParameterInfo[] = [];
|
|
201
211
|
|
|
202
212
|
// Match z.object({ ... }) blocks
|
|
@@ -215,11 +225,15 @@ function parseZodSchema(filePath: string): ParameterInfo[] {
|
|
|
215
225
|
const param: ParameterInfo = {
|
|
216
226
|
name,
|
|
217
227
|
type: mapZodType(type),
|
|
218
|
-
required:
|
|
228
|
+
required:
|
|
229
|
+
!modifiers.includes(".optional()") &&
|
|
230
|
+
!modifiers.includes(".nullable()"),
|
|
219
231
|
};
|
|
220
232
|
|
|
221
233
|
// Extract description from .describe()
|
|
222
|
-
const descMatch = modifiers.match(
|
|
234
|
+
const descMatch = modifiers.match(
|
|
235
|
+
/\.describe\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/,
|
|
236
|
+
);
|
|
223
237
|
if (descMatch) {
|
|
224
238
|
param.description = descMatch[1];
|
|
225
239
|
}
|
|
@@ -235,12 +249,12 @@ function parseZodSchema(filePath: string): ParameterInfo[] {
|
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
// Extract enum values for z.enum()
|
|
238
|
-
if (type ===
|
|
252
|
+
if (type === "enum" && typeArgs) {
|
|
239
253
|
const enumMatch = typeArgs.match(/\[([^\]]+)\]/);
|
|
240
254
|
if (enumMatch) {
|
|
241
255
|
param.enum = enumMatch[1]
|
|
242
|
-
.split(
|
|
243
|
-
.map(s => s.trim().replace(/['"`]/g,
|
|
256
|
+
.split(",")
|
|
257
|
+
.map((s) => s.trim().replace(/['"`]/g, ""));
|
|
244
258
|
}
|
|
245
259
|
}
|
|
246
260
|
|
|
@@ -253,23 +267,23 @@ function parseZodSchema(filePath: string): ParameterInfo[] {
|
|
|
253
267
|
|
|
254
268
|
function mapZodType(zodType: string): string {
|
|
255
269
|
const typeMap: Record<string, string> = {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
string: "string",
|
|
271
|
+
number: "number",
|
|
272
|
+
boolean: "boolean",
|
|
273
|
+
array: "array",
|
|
274
|
+
object: "object",
|
|
275
|
+
enum: "enum",
|
|
276
|
+
literal: "literal",
|
|
277
|
+
union: "union",
|
|
278
|
+
date: "date",
|
|
279
|
+
any: "any",
|
|
280
|
+
unknown: "unknown",
|
|
281
|
+
null: "null",
|
|
282
|
+
undefined: "undefined",
|
|
283
|
+
void: "void",
|
|
284
|
+
never: "never",
|
|
285
|
+
bigint: "bigint",
|
|
286
|
+
symbol: "symbol",
|
|
273
287
|
};
|
|
274
288
|
return typeMap[zodType] || zodType;
|
|
275
289
|
}
|
|
@@ -278,14 +292,17 @@ function mapZodType(zodType: string): string {
|
|
|
278
292
|
// Route File Parser
|
|
279
293
|
// ============================================
|
|
280
294
|
|
|
281
|
-
function parseRouteFile(filePath: string): {
|
|
282
|
-
|
|
295
|
+
function parseRouteFile(filePath: string): {
|
|
296
|
+
methods: string[];
|
|
297
|
+
description?: string;
|
|
298
|
+
} {
|
|
299
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
283
300
|
const methods: string[] = [];
|
|
284
301
|
|
|
285
302
|
// Find exported HTTP method handlers
|
|
286
303
|
const methodPatterns = [
|
|
287
304
|
/export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/g,
|
|
288
|
-
/export\s+const\s+(GET|POST|PUT|DELETE|PATCH)\s*=/g
|
|
305
|
+
/export\s+const\s+(GET|POST|PUT|DELETE|PATCH)\s*=/g,
|
|
289
306
|
];
|
|
290
307
|
|
|
291
308
|
for (const pattern of methodPatterns) {
|
|
@@ -314,8 +331,10 @@ interface InterviewState {
|
|
|
314
331
|
phase: number;
|
|
315
332
|
}
|
|
316
333
|
|
|
317
|
-
function readInterviewState(
|
|
318
|
-
|
|
334
|
+
function readInterviewState(
|
|
335
|
+
baseDir: string,
|
|
336
|
+
): Map<string, Record<string, string>> {
|
|
337
|
+
const stateFile = path.join(baseDir, ".claude", "api-dev-state.json");
|
|
319
338
|
const decisions = new Map<string, Record<string, string>>();
|
|
320
339
|
|
|
321
340
|
if (!fs.existsSync(stateFile)) {
|
|
@@ -323,7 +342,7 @@ function readInterviewState(baseDir: string): Map<string, Record<string, string>
|
|
|
323
342
|
}
|
|
324
343
|
|
|
325
344
|
try {
|
|
326
|
-
const content = fs.readFileSync(stateFile,
|
|
345
|
+
const content = fs.readFileSync(stateFile, "utf-8");
|
|
327
346
|
const state = JSON.parse(content);
|
|
328
347
|
|
|
329
348
|
if (Array.isArray(state.endpoints)) {
|
|
@@ -345,7 +364,7 @@ function readInterviewState(baseDir: string): Map<string, Record<string, string>
|
|
|
345
364
|
// ============================================
|
|
346
365
|
|
|
347
366
|
function generateManifest(baseDir: string): ManifestOutput {
|
|
348
|
-
console.log(
|
|
367
|
+
console.log("🔍 Scanning for test files...");
|
|
349
368
|
|
|
350
369
|
// Find all test files
|
|
351
370
|
const testFiles = findFiles(baseDir, /\.(test|spec)\.(ts|tsx)$/);
|
|
@@ -393,9 +412,9 @@ function generateManifest(baseDir: string): ManifestOutput {
|
|
|
393
412
|
|
|
394
413
|
// Determine category from path
|
|
395
414
|
const pathParts = relativePath.split(path.sep);
|
|
396
|
-
let category =
|
|
397
|
-
if (pathParts.includes(
|
|
398
|
-
const apiIndex = pathParts.indexOf(
|
|
415
|
+
let category = "General";
|
|
416
|
+
if (pathParts.includes("api")) {
|
|
417
|
+
const apiIndex = pathParts.indexOf("api");
|
|
399
418
|
if (apiIndex + 1 < pathParts.length) {
|
|
400
419
|
category = pathParts[apiIndex + 1];
|
|
401
420
|
}
|
|
@@ -403,14 +422,20 @@ function generateManifest(baseDir: string): ManifestOutput {
|
|
|
403
422
|
categories.add(category);
|
|
404
423
|
|
|
405
424
|
// Find matching route file
|
|
406
|
-
const routeDir = path
|
|
425
|
+
const routeDir = path
|
|
426
|
+
.dirname(testFile)
|
|
427
|
+
.replace("__tests__", "")
|
|
428
|
+
.replace(".test", "");
|
|
407
429
|
const possibleRoutes = [
|
|
408
|
-
path.join(routeDir,
|
|
409
|
-
path.join(routeDir,
|
|
410
|
-
testFile.replace(/\.(test|spec)\.(ts|tsx)$/,
|
|
430
|
+
path.join(routeDir, "route.ts"),
|
|
431
|
+
path.join(routeDir, "route.tsx"),
|
|
432
|
+
testFile.replace(/\.(test|spec)\.(ts|tsx)$/, ".ts"),
|
|
411
433
|
];
|
|
412
434
|
|
|
413
|
-
let routeInfo = {
|
|
435
|
+
let routeInfo = {
|
|
436
|
+
methods: [method || "GET"],
|
|
437
|
+
description: undefined as string | undefined,
|
|
438
|
+
};
|
|
414
439
|
for (const routePath of possibleRoutes) {
|
|
415
440
|
if (fs.existsSync(routePath)) {
|
|
416
441
|
routeInfo = parseRouteFile(routePath);
|
|
@@ -419,9 +444,11 @@ function generateManifest(baseDir: string): ManifestOutput {
|
|
|
419
444
|
}
|
|
420
445
|
|
|
421
446
|
// Find matching schema file
|
|
422
|
-
const schemaBaseName = path
|
|
423
|
-
|
|
424
|
-
|
|
447
|
+
const schemaBaseName = path
|
|
448
|
+
.basename(testFile)
|
|
449
|
+
.replace(/\.(test|spec)\.(ts|tsx)$/, "");
|
|
450
|
+
const matchingSchemas = schemaFiles.filter(
|
|
451
|
+
(s) => s.includes(schemaBaseName) || s.includes(category.toLowerCase()),
|
|
425
452
|
);
|
|
426
453
|
|
|
427
454
|
let parameters: ParameterInfo[] = [];
|
|
@@ -434,49 +461,53 @@ function generateManifest(baseDir: string): ManifestOutput {
|
|
|
434
461
|
|
|
435
462
|
// Generate endpoint ID
|
|
436
463
|
const endpointId = endpoint
|
|
437
|
-
.replace(/^\/api\//,
|
|
438
|
-
.replace(/\//g,
|
|
439
|
-
.replace(/[^a-z0-9-]/gi,
|
|
464
|
+
.replace(/^\/api\//, "")
|
|
465
|
+
.replace(/\//g, "-")
|
|
466
|
+
.replace(/[^a-z0-9-]/gi, "");
|
|
440
467
|
|
|
441
468
|
const manifest: EndpointManifest = {
|
|
442
469
|
id: endpointId,
|
|
443
470
|
name: groups[0]?.name || endpointId,
|
|
444
471
|
endpoint,
|
|
445
|
-
method: routeInfo.methods[0] || method ||
|
|
472
|
+
method: routeInfo.methods[0] || method || "GET",
|
|
446
473
|
description: routeInfo.description || `API endpoint: ${endpoint}`,
|
|
447
474
|
category,
|
|
448
475
|
parameters: {
|
|
449
|
-
query: parameters.filter(p => !p.name.startsWith(
|
|
450
|
-
body: parameters.filter(
|
|
451
|
-
|
|
476
|
+
query: parameters.filter((p) => !p.name.startsWith("body")),
|
|
477
|
+
body: parameters.filter(
|
|
478
|
+
(p) => p.name.startsWith("body") || p.name === "data",
|
|
479
|
+
),
|
|
480
|
+
headers: [],
|
|
452
481
|
},
|
|
453
482
|
responses: {
|
|
454
|
-
success: { status: 200, description:
|
|
483
|
+
success: { status: 200, description: "Successful response" },
|
|
455
484
|
error: [
|
|
456
|
-
{ status: 400, description:
|
|
457
|
-
{ status: 500, description:
|
|
458
|
-
]
|
|
485
|
+
{ status: 400, description: "Bad request" },
|
|
486
|
+
{ status: 500, description: "Internal server error" },
|
|
487
|
+
],
|
|
459
488
|
},
|
|
460
489
|
testFile: relativePath,
|
|
461
490
|
testCount,
|
|
462
491
|
testCases,
|
|
463
492
|
interviewDecisions: decisions,
|
|
464
|
-
generatedAt: new Date().toISOString()
|
|
493
|
+
generatedAt: new Date().toISOString(),
|
|
465
494
|
};
|
|
466
495
|
|
|
467
496
|
endpoints.push(manifest);
|
|
468
|
-
console.log(
|
|
497
|
+
console.log(
|
|
498
|
+
` ✅ Generated manifest for ${endpoint} (${testCount} tests)`,
|
|
499
|
+
);
|
|
469
500
|
}
|
|
470
501
|
|
|
471
502
|
return {
|
|
472
|
-
version:
|
|
503
|
+
version: "3.0.0",
|
|
473
504
|
generatedAt: new Date().toISOString(),
|
|
474
505
|
endpoints,
|
|
475
506
|
summary: {
|
|
476
507
|
totalEndpoints: endpoints.length,
|
|
477
508
|
totalTests,
|
|
478
|
-
categories: Array.from(categories)
|
|
479
|
-
}
|
|
509
|
+
categories: Array.from(categories),
|
|
510
|
+
},
|
|
480
511
|
};
|
|
481
512
|
}
|
|
482
513
|
|
|
@@ -487,12 +518,18 @@ function generateManifest(baseDir: string): ManifestOutput {
|
|
|
487
518
|
function main() {
|
|
488
519
|
const args = process.argv.slice(2);
|
|
489
520
|
const baseDir = args[0] || process.cwd();
|
|
490
|
-
const outputPath =
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
console.log(
|
|
495
|
-
|
|
521
|
+
const outputPath =
|
|
522
|
+
args[1] ||
|
|
523
|
+
path.join(baseDir, "src", "app", "api-test", "api-tests-manifest.json");
|
|
524
|
+
|
|
525
|
+
console.log(
|
|
526
|
+
"═══════════════════════════════════════════════════════════════",
|
|
527
|
+
);
|
|
528
|
+
console.log(" 📋 API Test Manifest Generator");
|
|
529
|
+
console.log(" @hustle-together/api-dev-tools v3.0");
|
|
530
|
+
console.log(
|
|
531
|
+
"═══════════════════════════════════════════════════════════════",
|
|
532
|
+
);
|
|
496
533
|
console.log(`\n📁 Base directory: ${baseDir}`);
|
|
497
534
|
console.log(`📄 Output file: ${outputPath}\n`);
|
|
498
535
|
|
|
@@ -507,13 +544,17 @@ function main() {
|
|
|
507
544
|
// Write manifest
|
|
508
545
|
fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
509
546
|
|
|
510
|
-
console.log(
|
|
511
|
-
|
|
512
|
-
|
|
547
|
+
console.log(
|
|
548
|
+
"\n═══════════════════════════════════════════════════════════════",
|
|
549
|
+
);
|
|
550
|
+
console.log(" ✅ Manifest generated successfully!");
|
|
551
|
+
console.log(
|
|
552
|
+
"═══════════════════════════════════════════════════════════════",
|
|
553
|
+
);
|
|
513
554
|
console.log(`\n📊 Summary:`);
|
|
514
555
|
console.log(` • Endpoints: ${manifest.summary.totalEndpoints}`);
|
|
515
556
|
console.log(` • Tests: ${manifest.summary.totalTests}`);
|
|
516
|
-
console.log(` • Categories: ${manifest.summary.categories.join(
|
|
557
|
+
console.log(` • Categories: ${manifest.summary.categories.join(", ")}`);
|
|
517
558
|
console.log(`\n📄 Output: ${outputPath}\n`);
|
|
518
559
|
}
|
|
519
560
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# API Dev Tools - Environment Configuration
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Copy this file to .env in your project root and fill in your values.
|
|
5
|
+
# Never commit .env files with real credentials to git!
|
|
6
|
+
# =============================================================================
|
|
7
|
+
|
|
8
|
+
# -----------------------------------------------------------------------------
|
|
9
|
+
# NTFY Notifications (Optional)
|
|
10
|
+
# -----------------------------------------------------------------------------
|
|
11
|
+
# Get a free topic at https://ntfy.sh or self-host
|
|
12
|
+
NTFY_ENABLED=false
|
|
13
|
+
NTFY_SERVER=https://ntfy.sh
|
|
14
|
+
NTFY_TOPIC=your-unique-topic-name
|
|
15
|
+
|
|
16
|
+
# -----------------------------------------------------------------------------
|
|
17
|
+
# GitHub Integration
|
|
18
|
+
# -----------------------------------------------------------------------------
|
|
19
|
+
# Create at: https://github.com/settings/tokens
|
|
20
|
+
# Required scopes: repo, read:org
|
|
21
|
+
GITHUB_PERSONAL_ACCESS_TOKEN=
|
|
22
|
+
|
|
23
|
+
# -----------------------------------------------------------------------------
|
|
24
|
+
# Common API Keys (Add as needed for your integrations)
|
|
25
|
+
# -----------------------------------------------------------------------------
|
|
26
|
+
# These are examples - add keys for the APIs you integrate
|
|
27
|
+
|
|
28
|
+
# OpenAI
|
|
29
|
+
OPENAI_API_KEY=
|
|
30
|
+
|
|
31
|
+
# Anthropic
|
|
32
|
+
ANTHROPIC_API_KEY=
|
|
33
|
+
|
|
34
|
+
# Vercel AI SDK
|
|
35
|
+
# (Uses OPENAI_API_KEY or ANTHROPIC_API_KEY automatically)
|
|
36
|
+
|
|
37
|
+
# -----------------------------------------------------------------------------
|
|
38
|
+
# Development Settings
|
|
39
|
+
# -----------------------------------------------------------------------------
|
|
40
|
+
# Storybook URL for visual testing
|
|
41
|
+
STORYBOOK_URL=http://localhost:6006
|
|
42
|
+
|
|
43
|
+
# API base URL for testing
|
|
44
|
+
API_BASE_URL=http://localhost:3000
|
|
45
|
+
|
|
46
|
+
# -----------------------------------------------------------------------------
|
|
47
|
+
# Token Usage Tracking
|
|
48
|
+
# -----------------------------------------------------------------------------
|
|
49
|
+
# Track Claude Code token usage per session
|
|
50
|
+
TRACK_TOKEN_USAGE=true
|
|
51
|
+
|
|
52
|
+
# -----------------------------------------------------------------------------
|
|
53
|
+
# TypeDoc Documentation
|
|
54
|
+
# -----------------------------------------------------------------------------
|
|
55
|
+
# Auto-generate docs after Phase 12
|
|
56
|
+
TYPEDOC_AUTO_GENERATE=true
|
|
57
|
+
TYPEDOC_OUTPUT_DIR=docs/api
|