zero-doc 1.0.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.
@@ -0,0 +1,5 @@
1
+ export { RouteParser } from './parser';
2
+ export { APIInventoryGenerator } from './generator';
3
+ export { AIEnricher } from './ai-enricher';
4
+ export { GeminiEnricher } from './gemini-enricher';
5
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.GeminiEnricher = exports.AIEnricher = exports.APIInventoryGenerator = exports.RouteParser = void 0;
18
+ var parser_1 = require("./parser");
19
+ Object.defineProperty(exports, "RouteParser", { enumerable: true, get: function () { return parser_1.RouteParser; } });
20
+ var generator_1 = require("./generator");
21
+ Object.defineProperty(exports, "APIInventoryGenerator", { enumerable: true, get: function () { return generator_1.APIInventoryGenerator; } });
22
+ var ai_enricher_1 = require("./ai-enricher");
23
+ Object.defineProperty(exports, "AIEnricher", { enumerable: true, get: function () { return ai_enricher_1.AIEnricher; } });
24
+ var gemini_enricher_1 = require("./gemini-enricher");
25
+ Object.defineProperty(exports, "GeminiEnricher", { enumerable: true, get: function () { return gemini_enricher_1.GeminiEnricher; } });
26
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,14 @@
1
+ import { RouteEndpoint } from './types';
2
+ export declare class RouteParser {
3
+ private project;
4
+ private routes;
5
+ constructor(tsConfigPath?: string);
6
+ addSourceFiles(pattern: string): void;
7
+ parse(): RouteEndpoint[];
8
+ private extractRoute;
9
+ private extractGroup;
10
+ private extractPath;
11
+ private extractHandler;
12
+ private extractParameters;
13
+ getRoutes(): RouteEndpoint[];
14
+ }
package/dist/parser.js ADDED
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RouteParser = void 0;
4
+ const ts_morph_1 = require("ts-morph");
5
+ const nanoid_1 = require("nanoid");
6
+ class RouteParser {
7
+ constructor(tsConfigPath) {
8
+ this.routes = [];
9
+ this.project = new ts_morph_1.Project({
10
+ tsConfigFilePath: tsConfigPath,
11
+ skipAddingFilesFromTsConfig: true,
12
+ });
13
+ }
14
+ addSourceFiles(pattern) {
15
+ this.project.addSourceFilesAtPaths(pattern);
16
+ }
17
+ parse() {
18
+ this.routes = [];
19
+ this.project.getSourceFiles().forEach(sourceFile => {
20
+ const filePath = sourceFile.getFilePath();
21
+ sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression).forEach(call => {
22
+ const route = this.extractRoute(call, filePath);
23
+ if (route) {
24
+ this.routes.push(route);
25
+ }
26
+ });
27
+ });
28
+ return this.routes;
29
+ }
30
+ extractRoute(call, filePath) {
31
+ const expression = call.getExpression();
32
+ const expressionText = expression.getText();
33
+ // Supports: app.get, router.post, fastify.put, etc.
34
+ const methodMatch = expressionText.match(/\.(get|post|put|delete|patch)$/i);
35
+ if (!methodMatch)
36
+ return null;
37
+ const method = methodMatch[1].toUpperCase();
38
+ const args = call.getArguments();
39
+ if (args.length < 2)
40
+ return null; // Needs at least (path, handler)
41
+ // Extract path (first argument)
42
+ const pathArg = args[0];
43
+ const path = this.extractPath(pathArg);
44
+ if (!path)
45
+ return null;
46
+ // Extract handler (usually last argument)
47
+ const handlerArg = args[args.length - 1];
48
+ const handler = this.extractHandler(handlerArg);
49
+ // Extract parameters from req.body and req.query
50
+ const parameters = this.extractParameters(handlerArg);
51
+ // Extract group from path (first segment after /)
52
+ const group = this.extractGroup(path);
53
+ return {
54
+ id: (0, nanoid_1.nanoid)(10),
55
+ method,
56
+ path,
57
+ group,
58
+ file: filePath,
59
+ line: call.getStartLineNumber(),
60
+ handler,
61
+ parameters,
62
+ metadata: {},
63
+ };
64
+ }
65
+ extractGroup(path) {
66
+ const segments = path.split('/').filter(s => s && !s.startsWith(':'));
67
+ if (segments.length === 0) {
68
+ return 'Root';
69
+ }
70
+ const firstSegment = segments[0];
71
+ const capitalized = firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1);
72
+ return capitalized;
73
+ }
74
+ extractPath(node) {
75
+ const text = node.getText();
76
+ // Remove quotes and template literals
77
+ const cleaned = text.replace(/^['"`]|['"`]$/g, '');
78
+ // Validate if it looks like a path
79
+ if (cleaned.startsWith('/')) {
80
+ return cleaned;
81
+ }
82
+ return null;
83
+ }
84
+ extractHandler(node) {
85
+ const isAsync = node.getText().includes('async');
86
+ // Try to extract function name if it's a named function
87
+ let name;
88
+ if (ts_morph_1.Node.isIdentifier(node)) {
89
+ name = node.getText();
90
+ }
91
+ // Extract function parameters (req, res, next, etc.)
92
+ const parameters = [];
93
+ if (ts_morph_1.Node.isArrowFunction(node) || ts_morph_1.Node.isFunctionExpression(node)) {
94
+ node.getParameters().forEach(param => {
95
+ parameters.push(param.getName());
96
+ });
97
+ }
98
+ return {
99
+ name,
100
+ isAsync,
101
+ parameters,
102
+ };
103
+ }
104
+ extractParameters(handlerNode) {
105
+ const parameters = [];
106
+ // Look for accesses to req.body, req.query, req.params
107
+ if (ts_morph_1.Node.isArrowFunction(handlerNode) || ts_morph_1.Node.isFunctionExpression(handlerNode)) {
108
+ const body = handlerNode.getBody();
109
+ if (body) {
110
+ // Look for PropertyAccessExpression: req.body.email, req.query.page, etc.
111
+ body.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertyAccessExpression).forEach(prop => {
112
+ const fullText = prop.getText();
113
+ // req.body.fieldName
114
+ const bodyMatch = fullText.match(/req\.body\.(\w+)/);
115
+ if (bodyMatch) {
116
+ parameters.push({
117
+ name: bodyMatch[1],
118
+ in: 'body',
119
+ required: true, // Can be refined later
120
+ });
121
+ }
122
+ // req.query.fieldName
123
+ const queryMatch = fullText.match(/req\.query\.(\w+)/);
124
+ if (queryMatch) {
125
+ parameters.push({
126
+ name: queryMatch[1],
127
+ in: 'query',
128
+ });
129
+ }
130
+ // req.params.fieldName
131
+ const paramsMatch = fullText.match(/req\.params\.(\w+)/);
132
+ if (paramsMatch) {
133
+ parameters.push({
134
+ name: paramsMatch[1],
135
+ in: 'path',
136
+ required: true,
137
+ });
138
+ }
139
+ });
140
+ // Look for destructuring: const { email, password } = req.body
141
+ body.getDescendantsOfKind(ts_morph_1.SyntaxKind.VariableDeclaration).forEach(varDecl => {
142
+ const initializer = varDecl.getInitializer();
143
+ if (initializer?.getText().startsWith('req.')) {
144
+ const sourceText = initializer.getText();
145
+ const name = varDecl.getName();
146
+ // If it's destructuring
147
+ if (name.startsWith('{')) {
148
+ const fields = name
149
+ .replace(/[{}]/g, '')
150
+ .split(',')
151
+ .map(f => f.trim());
152
+ let location = 'body';
153
+ if (sourceText.includes('req.query'))
154
+ location = 'query';
155
+ if (sourceText.includes('req.params'))
156
+ location = 'path';
157
+ fields.forEach(field => {
158
+ parameters.push({
159
+ name: field,
160
+ in: location,
161
+ required: location !== 'query',
162
+ });
163
+ });
164
+ }
165
+ }
166
+ });
167
+ }
168
+ }
169
+ // Remove duplicates
170
+ return parameters.filter((param, index, self) => index === self.findIndex(p => p.name === param.name && p.in === param.in));
171
+ }
172
+ getRoutes() {
173
+ return this.routes;
174
+ }
175
+ }
176
+ exports.RouteParser = RouteParser;
@@ -0,0 +1,51 @@
1
+ export interface RouteParameter {
2
+ name: string;
3
+ in: 'path' | 'query' | 'body' | 'header';
4
+ type?: string;
5
+ required?: boolean;
6
+ description?: string;
7
+ inferredType?: 'string' | 'number' | 'boolean' | 'object' | 'array';
8
+ }
9
+ export interface RouteEndpoint {
10
+ id: string;
11
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
12
+ path: string;
13
+ group: string;
14
+ file: string;
15
+ line: number;
16
+ handler: {
17
+ name?: string;
18
+ isAsync: boolean;
19
+ parameters: string[];
20
+ };
21
+ parameters: RouteParameter[];
22
+ requestBody?: {
23
+ required: boolean;
24
+ fields: RouteParameter[];
25
+ };
26
+ response?: {
27
+ statusCode: number;
28
+ type?: string;
29
+ };
30
+ metadata?: {
31
+ tags?: string[];
32
+ summary?: string;
33
+ description?: string;
34
+ deprecated?: boolean;
35
+ title?: string;
36
+ aiGenerated?: boolean;
37
+ };
38
+ }
39
+ export interface APIInventory {
40
+ version: string;
41
+ generatedAt: string;
42
+ project: {
43
+ name?: string;
44
+ framework: 'express' | 'fastify' | 'unknown';
45
+ };
46
+ endpoints: RouteEndpoint[];
47
+ stats: {
48
+ totalEndpoints: number;
49
+ byMethod: Record<string, number>;
50
+ };
51
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "zero-doc",
3
+ "version": "1.0.0",
4
+ "description": "Zero-Config API Documentation Generator - Generate beautiful API docs from your code automatically",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "zero-doc": "./dist/cli.js"
8
+ },
9
+ "keywords": [
10
+ "api",
11
+ "documentation",
12
+ "docs",
13
+ "openapi",
14
+ "swagger",
15
+ "express",
16
+ "fastify",
17
+ "typescript",
18
+ "static-analysis",
19
+ "ai",
20
+ "zero-config"
21
+ ],
22
+ "author": "Lucas Sens",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": ""
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "viewer",
31
+ "scripts"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsc && npm run copy-viewer",
35
+ "copy-viewer": "node scripts/copy-viewer.js",
36
+ "dev": "ts-node src/cli.ts",
37
+ "generate": "ts-node src/cli.ts generate"
38
+ },
39
+ "dependencies": {
40
+ "@google/generative-ai": "^0.21.0",
41
+ "commander": "^11.0.0",
42
+ "dotenv": "^16.3.1",
43
+ "fs-extra": "^11.2.0",
44
+ "nanoid": "^3.3.7",
45
+ "openai": "^4.20.0",
46
+ "tmp": "^0.2.1",
47
+ "ts-morph": "^21.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/fs-extra": "^11.0.4",
51
+ "@types/node": "^20.0.0",
52
+ "@types/tmp": "^0.2.6",
53
+ "ts-node": "^10.9.0",
54
+ "typescript": "^5.0.0"
55
+ }
56
+ }
57
+
@@ -0,0 +1,30 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const fse = require('fs-extra');
4
+
5
+ // Get the monorepo root (go up from packages/cli/scripts to root)
6
+ const monorepoRoot = path.join(__dirname, '../../../');
7
+ const viewerSource = path.join(monorepoRoot, 'apps/viewer');
8
+ const viewerDest = path.join(__dirname, '../viewer');
9
+
10
+ if (!fs.existsSync(viewerSource)) {
11
+ console.error('❌ Viewer source not found at:', viewerSource);
12
+ process.exit(1);
13
+ }
14
+
15
+ if (fs.existsSync(viewerDest)) {
16
+ fse.removeSync(viewerDest);
17
+ }
18
+
19
+ fse.copySync(viewerSource, viewerDest, {
20
+ filter: (src) => {
21
+ const relativePath = path.relative(viewerSource, src);
22
+ return !relativePath.includes('node_modules') &&
23
+ !relativePath.includes('dist') &&
24
+ !relativePath.startsWith('.git') &&
25
+ !relativePath.includes('package-lock.json');
26
+ }
27
+ });
28
+
29
+ console.log('✅ Viewer copied to packages/cli/viewer');
30
+
@@ -0,0 +1,41 @@
1
+ # Zero-Doc Viewer
2
+
3
+ Beautiful API documentation viewer built with React + Vite + Tailwind.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ cd apps/viewer
9
+ npm install
10
+ npm run dev
11
+ ```
12
+
13
+ The viewer will automatically load `api-inventory.json` from the `public` folder.
14
+
15
+ ## Features
16
+
17
+ - ✅ Stripe-style documentation layout
18
+ - ✅ Sidebar navigation grouped by HTTP method
19
+ - ✅ Color-coded method badges (GET=green, POST=blue, etc.)
20
+ - ✅ Automatic parameter tables
21
+ - ✅ Code snippets (fetch & axios)
22
+ - ✅ Dark mode code blocks
23
+ - ✅ Responsive design
24
+
25
+ ## Structure
26
+
27
+ ```
28
+ apps/viewer/
29
+ ├── src/
30
+ │ ├── components/
31
+ │ │ ├── Sidebar.tsx # Navigation sidebar
32
+ │ │ ├── EndpointView.tsx # Main endpoint documentation
33
+ │ │ ├── ParameterTable.tsx # Auto-generated parameter tables
34
+ │ │ └── CodeSnippet.tsx # fetch/axios code examples
35
+ │ ├── App.tsx # Main app component
36
+ │ ├── types.ts # TypeScript types
37
+ │ └── main.tsx # Entry point
38
+ └── public/
39
+ └── api-inventory.json # Generated API inventory
40
+ ```
41
+
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>API Documentation - Zero Doc</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
14
+
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@zero-doc/viewer",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^18.2.0",
13
+ "react-dom": "^18.2.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/react": "^18.2.43",
17
+ "@types/react-dom": "^18.2.17",
18
+ "@vitejs/plugin-react": "^4.2.1",
19
+ "autoprefixer": "^10.4.16",
20
+ "postcss": "^8.4.32",
21
+ "tailwindcss": "^3.3.6",
22
+ "typescript": "^5.2.2",
23
+ "vite": "^5.0.8"
24
+ }
25
+ }
26
+
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,151 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "generatedAt": "2025-12-28T06:58:49.389Z",
4
+ "project": {
5
+ "framework": "express"
6
+ },
7
+ "endpoints": [
8
+ {
9
+ "id": "-C8ltf6rc1",
10
+ "method": "GET",
11
+ "path": "/users",
12
+ "group": "Users",
13
+ "file": "C:/trampos/Projetos/Zero-Config API Reference/packages/cli/examples/express-routes.ts",
14
+ "line": 7,
15
+ "handler": {
16
+ "isAsync": false,
17
+ "parameters": [
18
+ "req",
19
+ "res"
20
+ ]
21
+ },
22
+ "parameters": [
23
+ {
24
+ "name": "page",
25
+ "in": "query"
26
+ },
27
+ {
28
+ "name": "limit",
29
+ "in": "query"
30
+ }
31
+ ],
32
+ "metadata": {}
33
+ },
34
+ {
35
+ "id": "ksWVmn7IFn",
36
+ "method": "GET",
37
+ "path": "/users/:id",
38
+ "group": "Users",
39
+ "file": "C:/trampos/Projetos/Zero-Config API Reference/packages/cli/examples/express-routes.ts",
40
+ "line": 14,
41
+ "handler": {
42
+ "isAsync": true,
43
+ "parameters": [
44
+ "req",
45
+ "res"
46
+ ]
47
+ },
48
+ "parameters": [
49
+ {
50
+ "name": "id",
51
+ "in": "path",
52
+ "required": true
53
+ }
54
+ ],
55
+ "metadata": {}
56
+ },
57
+ {
58
+ "id": "-nScJ4hpeC",
59
+ "method": "POST",
60
+ "path": "/users",
61
+ "group": "Users",
62
+ "file": "C:/trampos/Projetos/Zero-Config API Reference/packages/cli/examples/express-routes.ts",
63
+ "line": 20,
64
+ "handler": {
65
+ "isAsync": true,
66
+ "parameters": [
67
+ "req",
68
+ "res"
69
+ ]
70
+ },
71
+ "parameters": [
72
+ {
73
+ "name": "email",
74
+ "in": "body",
75
+ "required": true
76
+ },
77
+ {
78
+ "name": "password",
79
+ "in": "body",
80
+ "required": true
81
+ },
82
+ {
83
+ "name": "name",
84
+ "in": "body",
85
+ "required": true
86
+ }
87
+ ],
88
+ "metadata": {}
89
+ },
90
+ {
91
+ "id": "J2WEY9IHsB",
92
+ "method": "PUT",
93
+ "path": "/users/:id",
94
+ "group": "Users",
95
+ "file": "C:/trampos/Projetos/Zero-Config API Reference/packages/cli/examples/express-routes.ts",
96
+ "line": 26,
97
+ "handler": {
98
+ "isAsync": false,
99
+ "parameters": [
100
+ "req",
101
+ "res"
102
+ ]
103
+ },
104
+ "parameters": [
105
+ {
106
+ "name": "id",
107
+ "in": "path",
108
+ "required": true
109
+ },
110
+ {
111
+ "name": "email",
112
+ "in": "body",
113
+ "required": true
114
+ }
115
+ ],
116
+ "metadata": {}
117
+ },
118
+ {
119
+ "id": "Nam618R71j",
120
+ "method": "DELETE",
121
+ "path": "/users/:id",
122
+ "group": "Users",
123
+ "file": "C:/trampos/Projetos/Zero-Config API Reference/packages/cli/examples/express-routes.ts",
124
+ "line": 33,
125
+ "handler": {
126
+ "isAsync": true,
127
+ "parameters": [
128
+ "req",
129
+ "res"
130
+ ]
131
+ },
132
+ "parameters": [
133
+ {
134
+ "name": "id",
135
+ "in": "path",
136
+ "required": true
137
+ }
138
+ ],
139
+ "metadata": {}
140
+ }
141
+ ],
142
+ "stats": {
143
+ "totalEndpoints": 5,
144
+ "byMethod": {
145
+ "GET": 2,
146
+ "POST": 1,
147
+ "PUT": 1,
148
+ "DELETE": 1
149
+ }
150
+ }
151
+ }