apiforge-analyzer 1.3.2 → 1.3.3
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/README.md +165 -3
- package/dist/cjs/cli.js +37 -7
- package/dist/cjs/detect.js +34 -16
- package/dist/cjs/frameworks/fastify.js +1112 -0
- package/dist/cjs/index.js +29 -6
- package/dist/cjs/package.json +1 -1
- package/dist/esm/cli.js +37 -7
- package/dist/esm/detect.js +33 -16
- package/dist/esm/frameworks/fastify.js +1076 -0
- package/dist/esm/index.js +29 -7
- package/dist/types/detect.d.ts +7 -3
- package/dist/types/frameworks/fastify.d.ts +16 -0
- package/dist/types/index.d.ts +16 -1
- package/package.json +7 -4
package/dist/cjs/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* APIForge Analyzer — main entry point (dual ESM/CJS)
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.detectArchitectureType = exports.detectFramework = exports.uploadRoutes = exports.exportToJSON = exports.analyzeNestJS = exports.analyzeReactRouter = exports.analyzeVite = exports.analyzeNextApp = exports.analyzeNextPages = exports.analyzeExpress = void 0;
|
|
6
|
+
exports.detectArchitectureType = exports.detectFramework = exports.uploadRoutes = exports.exportToJSON = exports.analyzeFastify = exports.analyzeNestJS = exports.analyzeReactRouter = exports.analyzeVite = exports.analyzeNextApp = exports.analyzeNextPages = exports.analyzeExpress = void 0;
|
|
7
7
|
exports.analyze = analyze;
|
|
8
8
|
const fs_1 = require("fs");
|
|
9
9
|
const path_1 = require("path");
|
|
@@ -19,6 +19,8 @@ const react_router_js_1 = require("./frameworks/react-router.js");
|
|
|
19
19
|
Object.defineProperty(exports, "analyzeReactRouter", { enumerable: true, get: function () { return react_router_js_1.analyzeReactRouter; } });
|
|
20
20
|
const nestjs_js_1 = require("./frameworks/nestjs.js");
|
|
21
21
|
Object.defineProperty(exports, "analyzeNestJS", { enumerable: true, get: function () { return nestjs_js_1.analyzeNestJS; } });
|
|
22
|
+
const fastify_js_1 = require("./frameworks/fastify.js");
|
|
23
|
+
Object.defineProperty(exports, "analyzeFastify", { enumerable: true, get: function () { return fastify_js_1.analyzeFastify; } });
|
|
22
24
|
const detect_js_1 = require("./detect.js");
|
|
23
25
|
const export_js_1 = require("./export.js");
|
|
24
26
|
const uploader_js_1 = require("./uploader.js");
|
|
@@ -117,6 +119,15 @@ async function analyze(app, options) {
|
|
|
117
119
|
framework = (0, detect_js_1.detectFramework)(app).name;
|
|
118
120
|
console.log(`📦 Detected framework: ${framework}`);
|
|
119
121
|
}
|
|
122
|
+
// Reject loudly on an unsupported framework instead of falling through to
|
|
123
|
+
// `return []` — that return value is indistinguishable from "no routes",
|
|
124
|
+
// "no API key", "debounced", etc. A caller needs to be able to tell them apart.
|
|
125
|
+
if (!detect_js_1.SUPPORTED_FRAMEWORKS.has(framework)) {
|
|
126
|
+
const msg = `Framework "${framework}" not supported yet (${[...detect_js_1.SUPPORTED_FRAMEWORKS].join(', ')})`;
|
|
127
|
+
console.error(`❌ APIForge: ${msg}`);
|
|
128
|
+
(0, uploader_js_1.writeStatusFile)('error', { error: msg });
|
|
129
|
+
throw new Error(msg);
|
|
130
|
+
}
|
|
120
131
|
try {
|
|
121
132
|
switch (framework) {
|
|
122
133
|
case 'nestjs':
|
|
@@ -141,6 +152,20 @@ async function analyze(app, options) {
|
|
|
141
152
|
}
|
|
142
153
|
}
|
|
143
154
|
break;
|
|
155
|
+
case 'fastify':
|
|
156
|
+
if (!app) {
|
|
157
|
+
const msg = 'Fastify app instance required — pass your app object to analyze()';
|
|
158
|
+
console.error(`❌ ${msg}`);
|
|
159
|
+
(0, uploader_js_1.writeStatusFile)('error', { error: msg });
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
routes = await (0, fastify_js_1.analyzeFastify)(app, {
|
|
163
|
+
scopeCatalogPath: options.scopeCatalogPath,
|
|
164
|
+
errorCatalogPath: options.errorCatalogPath,
|
|
165
|
+
envelopes: options.envelopes,
|
|
166
|
+
annotationsPath: options.annotationsPath,
|
|
167
|
+
});
|
|
168
|
+
break;
|
|
144
169
|
case 'nextjs-pages':
|
|
145
170
|
routes = await (0, nextjs_pages_js_1.analyzeNextPages)(options.nextDir || 'pages/api');
|
|
146
171
|
break;
|
|
@@ -153,11 +178,8 @@ async function analyze(app, options) {
|
|
|
153
178
|
case 'react-router':
|
|
154
179
|
routes = await (0, react_router_js_1.analyzeReactRouter)(options.nextDir || 'src/routes.tsx');
|
|
155
180
|
break;
|
|
156
|
-
default:
|
|
157
|
-
|
|
158
|
-
console.warn(`⚠️ APIForge: ${msg}`);
|
|
159
|
-
(0, uploader_js_1.writeStatusFile)('error', { error: msg });
|
|
160
|
-
return [];
|
|
181
|
+
// No default: unsupported frameworks are rejected above, before this
|
|
182
|
+
// try block, so a thrown error here isn't silently swallowed into `[]`.
|
|
161
183
|
}
|
|
162
184
|
}
|
|
163
185
|
catch (err) {
|
|
@@ -213,6 +235,7 @@ exports.default = {
|
|
|
213
235
|
analyzeExpress: express_js_1.analyzeExpress,
|
|
214
236
|
analyzeNextPages: nextjs_pages_js_1.analyzeNextPages,
|
|
215
237
|
analyzeNextApp: nextjs_app_js_1.analyzeNextApp,
|
|
238
|
+
analyzeFastify: fastify_js_1.analyzeFastify,
|
|
216
239
|
analyzeVite: vite_js_1.analyzeVite,
|
|
217
240
|
analyzeReactRouter: react_router_js_1.analyzeReactRouter,
|
|
218
241
|
analyzeNestJS: nestjs_js_1.analyzeNestJS,
|
package/dist/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"commonjs"}
|
|
1
|
+
{"type":"commonjs"}
|
package/dist/esm/cli.js
CHANGED
|
@@ -219,11 +219,18 @@ async function main() {
|
|
|
219
219
|
if (serverResult.status === 'error') {
|
|
220
220
|
s.stop(red(`✗ ${serverResult.message}`));
|
|
221
221
|
// Give a targeted hint for common failure modes
|
|
222
|
-
if (serverResult.message.includes('API key')) {
|
|
223
|
-
log.warn(yellow('Hint:
|
|
224
|
-
log.warn(yellow('
|
|
225
|
-
log.
|
|
226
|
-
log.
|
|
222
|
+
if (serverResult.message.includes('API key') || serverResult.message.includes('Invalid API key')) {
|
|
223
|
+
log.warn(yellow('Hint: Your API key was rejected by the server. Common causes:'));
|
|
224
|
+
log.warn(yellow(''));
|
|
225
|
+
log.warn(yellow(' 1. Key not yet created — go to your dashboard and generate one:'));
|
|
226
|
+
log.info(` ${cyan('https://apiforge.brainfogagency.com/dashboard')}`);
|
|
227
|
+
log.warn(yellow(''));
|
|
228
|
+
log.warn(yellow(' 2. Key belongs to a different account — make sure you\'re logged in'));
|
|
229
|
+
log.warn(yellow(' to the same account that owns this project.'));
|
|
230
|
+
log.warn(yellow(''));
|
|
231
|
+
log.warn(yellow(' 3. Server not loading .env.local — add this at the top of your entry file:'));
|
|
232
|
+
log.info(` ${cyan("require('dotenv').config({ path: '.env.local' })")}`);
|
|
233
|
+
log.warn(yellow(' Or install dotenv: npm install dotenv'));
|
|
227
234
|
}
|
|
228
235
|
else if (serverResult.message.includes('No routes')) {
|
|
229
236
|
log.warn(yellow('Hint: Make sure all routes are registered before analyze() is called.'));
|
|
@@ -233,6 +240,14 @@ async function main() {
|
|
|
233
240
|
log.warn(yellow('Hint: Your server might be taking too long to start, or the analyzer'));
|
|
234
241
|
log.warn(yellow(' is not being called. Check your server logs for APIForge messages.'));
|
|
235
242
|
}
|
|
243
|
+
// Show captured server output to help debug the root cause
|
|
244
|
+
const capturedOutput = serverResult.serverOutput;
|
|
245
|
+
if (capturedOutput) {
|
|
246
|
+
log.info('');
|
|
247
|
+
log.info(dim('─── Server output ───────────────────────────────'));
|
|
248
|
+
capturedOutput.split('\n').slice(0, 25).forEach((line) => log.info(dim(` ${line}`)));
|
|
249
|
+
log.info(dim('─────────────────────────────────────────────────'));
|
|
250
|
+
}
|
|
236
251
|
await offerCleanup(entryFile, injected);
|
|
237
252
|
outro(red('Analysis failed.'));
|
|
238
253
|
process.exit(1);
|
|
@@ -744,11 +759,20 @@ function startAndWaitForAnalysis(command, timeoutMs) {
|
|
|
744
759
|
const statusPath = join(process.cwd(), STATUS_FILE);
|
|
745
760
|
const child = spawn(command, {
|
|
746
761
|
cwd: process.cwd(),
|
|
747
|
-
stdio: 'ignore',
|
|
762
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
748
763
|
shell: true,
|
|
749
764
|
env: { ...process.env },
|
|
750
765
|
detached: false,
|
|
751
766
|
});
|
|
767
|
+
// Capture server output (head-capped) so we can show it on failure
|
|
768
|
+
let serverOutput = '';
|
|
769
|
+
const MAX_OUTPUT = 4000;
|
|
770
|
+
const captureOutput = (chunk) => {
|
|
771
|
+
if (serverOutput.length < MAX_OUTPUT)
|
|
772
|
+
serverOutput += chunk.toString();
|
|
773
|
+
};
|
|
774
|
+
child.stdout?.on('data', captureOutput);
|
|
775
|
+
child.stderr?.on('data', captureOutput);
|
|
752
776
|
let resolved = false;
|
|
753
777
|
function finish(result) {
|
|
754
778
|
if (resolved)
|
|
@@ -760,7 +784,7 @@ function startAndWaitForAnalysis(command, timeoutMs) {
|
|
|
760
784
|
child.kill('SIGTERM');
|
|
761
785
|
}
|
|
762
786
|
catch { }
|
|
763
|
-
resolve(result);
|
|
787
|
+
resolve({ ...result, serverOutput: serverOutput.trim() });
|
|
764
788
|
}
|
|
765
789
|
// Poll for the status file every 500 ms
|
|
766
790
|
const poller = setInterval(() => {
|
|
@@ -1244,6 +1268,12 @@ function detectAppVariable(content) {
|
|
|
1244
1268
|
/(?:const|let|var)\s+(\w+)\s*=\s*(?:fastify|Fastify)\s*\(/,
|
|
1245
1269
|
/(?:const|let|var)\s+(\w+)\s*=\s*new\s+Koa\s*\(/,
|
|
1246
1270
|
/(?:const|let|var)\s+(\w+)\s*=\s*(?:Router|createApp|createServer)\s*\(/,
|
|
1271
|
+
// Inline `require('fastify')(...)` / `require('express')(...)` — the app
|
|
1272
|
+
// factory is never bound to a local name, only its return value is. This
|
|
1273
|
+
// is at least as common as the named-import style above (it's the pattern
|
|
1274
|
+
// in Fastify's own project generator output), and the patterns above never
|
|
1275
|
+
// match it because there's no `fastify`/`express` identifier in the source.
|
|
1276
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"](?:fastify|express)['"]\s*\)\s*(?:\.default\s*)?\(/,
|
|
1247
1277
|
];
|
|
1248
1278
|
for (const re of patterns) {
|
|
1249
1279
|
const m = content.match(re);
|
package/dist/esm/detect.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Frameworks analyze() can actually dispatch to. Detection can identify more
|
|
5
|
+
* frameworks than this (koa, hapi) — those are named so the detector is honest
|
|
6
|
+
* about what it saw, but `supported: false` tells callers not to expect output.
|
|
7
|
+
*/
|
|
8
|
+
export const SUPPORTED_FRAMEWORKS = new Set([
|
|
9
|
+
'express',
|
|
10
|
+
'fastify',
|
|
11
|
+
'nestjs',
|
|
12
|
+
'nextjs-pages',
|
|
13
|
+
'nextjs-app',
|
|
14
|
+
'vite',
|
|
15
|
+
'react-router',
|
|
16
|
+
]);
|
|
3
17
|
const cwd = () => process.cwd();
|
|
4
18
|
function fileExists(rel) {
|
|
5
19
|
return existsSync(join(cwd(), rel));
|
|
@@ -18,16 +32,19 @@ function tryResolve(mod) {
|
|
|
18
32
|
* Detect the web framework from the live app instance + filesystem hints.
|
|
19
33
|
* Confidence is used to pick the best match when multiple signals fire.
|
|
20
34
|
*/
|
|
35
|
+
function mk(name, confidence) {
|
|
36
|
+
return { name, confidence, supported: SUPPORTED_FRAMEWORKS.has(name) };
|
|
37
|
+
}
|
|
21
38
|
export function detectFramework(app) {
|
|
22
39
|
// ── Instance-based detection (highest confidence) ─────────────────────────
|
|
23
40
|
if (app && typeof app === 'function') {
|
|
24
41
|
// Express: _router or lazyrouter
|
|
25
42
|
if (app._router || (app.lazyrouter && typeof app.lazyrouter === 'function')) {
|
|
26
|
-
return
|
|
43
|
+
return mk('express', 1.0);
|
|
27
44
|
}
|
|
28
45
|
// Koa: middleware array
|
|
29
46
|
if (Array.isArray(app.middleware) && typeof app.use === 'function' && app.context) {
|
|
30
|
-
return
|
|
47
|
+
return mk('koa', 0.95);
|
|
31
48
|
}
|
|
32
49
|
}
|
|
33
50
|
// Fastify: inject method and route method
|
|
@@ -35,7 +52,7 @@ export function detectFramework(app) {
|
|
|
35
52
|
typeof app === 'object' &&
|
|
36
53
|
typeof app.inject === 'function' &&
|
|
37
54
|
typeof app.route === 'function') {
|
|
38
|
-
return
|
|
55
|
+
return mk('fastify', 1.0);
|
|
39
56
|
}
|
|
40
57
|
// Hapi: server with route + start methods
|
|
41
58
|
if (app &&
|
|
@@ -43,43 +60,43 @@ export function detectFramework(app) {
|
|
|
43
60
|
typeof app.route === 'function' &&
|
|
44
61
|
typeof app.start === 'function' &&
|
|
45
62
|
typeof app.stop === 'function') {
|
|
46
|
-
return
|
|
63
|
+
return mk('hapi', 0.95);
|
|
47
64
|
}
|
|
48
65
|
// NestJS application
|
|
49
66
|
if (app &&
|
|
50
67
|
typeof app === 'object' &&
|
|
51
68
|
typeof app.getHttpServer === 'function' &&
|
|
52
69
|
typeof app.listen === 'function') {
|
|
53
|
-
return
|
|
70
|
+
return mk('nestjs', 0.95);
|
|
54
71
|
}
|
|
55
72
|
// ── Filesystem detection ──────────────────────────────────────────────────
|
|
56
73
|
// Next.js App Router
|
|
57
74
|
if (fileExists('app') && (fileExists('app/layout.tsx') || fileExists('app/layout.js'))) {
|
|
58
|
-
return
|
|
75
|
+
return mk('nextjs-app', 0.92);
|
|
59
76
|
}
|
|
60
77
|
// Next.js Pages Router
|
|
61
78
|
if (fileExists('pages/api') || fileExists('src/pages/api')) {
|
|
62
|
-
return
|
|
79
|
+
return mk('nextjs-pages', 0.90);
|
|
63
80
|
}
|
|
64
81
|
if (fileExists('next.config.js') ||
|
|
65
82
|
fileExists('next.config.mjs') ||
|
|
66
83
|
fileExists('next.config.ts')) {
|
|
67
|
-
return
|
|
84
|
+
return mk('nextjs-pages', 0.75);
|
|
68
85
|
}
|
|
69
86
|
// ── Dependency-based detection (lowest confidence) ────────────────────────
|
|
70
87
|
if (tryResolve('express'))
|
|
71
|
-
return
|
|
88
|
+
return mk('express', 0.70);
|
|
72
89
|
if (tryResolve('fastify'))
|
|
73
|
-
return
|
|
90
|
+
return mk('fastify', 0.70);
|
|
74
91
|
if (tryResolve('koa'))
|
|
75
|
-
return
|
|
92
|
+
return mk('koa', 0.70);
|
|
76
93
|
if (tryResolve('@hapi/hapi') || tryResolve('hapi'))
|
|
77
|
-
return
|
|
94
|
+
return mk('hapi', 0.70);
|
|
78
95
|
if (tryResolve('@nestjs/core'))
|
|
79
|
-
return
|
|
96
|
+
return mk('nestjs', 0.70);
|
|
80
97
|
if (tryResolve('vite'))
|
|
81
|
-
return
|
|
98
|
+
return mk('vite', 0.60);
|
|
82
99
|
if (tryResolve('react-router-dom'))
|
|
83
|
-
return
|
|
84
|
-
return
|
|
100
|
+
return mk('react-router', 0.55);
|
|
101
|
+
return mk('unknown', 0);
|
|
85
102
|
}
|