@easydocs/cli 0.1.4
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/LICENSE.md +21 -0
- package/README.md +62 -0
- package/dist/index.js +193 -0
- package/package.json +28 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 EasyDocs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @easydocs/cli
|
|
2
|
+
|
|
3
|
+
Zero-install proxy and spec export for [EasyDocs](https://github.com/RubenGlez/easydocs).
|
|
4
|
+
|
|
5
|
+
## Proxy mode — zero code changes
|
|
6
|
+
|
|
7
|
+
Route your requests through the EasyDocs proxy and it captures traffic automatically. No middleware to install, no code to change.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @easydocs/cli proxy --project=my-api --port=3999
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then send requests through the proxy:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
http://localhost:3999?target=https://api.example.com/users
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Every request is captured and an OpenAPI spec is generated in the background.
|
|
20
|
+
|
|
21
|
+
## Dashboard
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @easydocs/cli dashboard
|
|
25
|
+
# requires @easydocs/dashboard to be installed
|
|
26
|
+
npm install -D @easydocs/dashboard
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Export spec
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# JSON (default)
|
|
33
|
+
npx @easydocs/cli export
|
|
34
|
+
|
|
35
|
+
# YAML
|
|
36
|
+
npx @easydocs/cli export --yaml
|
|
37
|
+
|
|
38
|
+
# Scoped to a project
|
|
39
|
+
npx @easydocs/cli export --project=my-api
|
|
40
|
+
|
|
41
|
+
# Pipe to a file
|
|
42
|
+
npx @easydocs/cli export > openapi.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Flags
|
|
46
|
+
|
|
47
|
+
| Flag | Command | Default | Description |
|
|
48
|
+
|------|---------|---------|-------------|
|
|
49
|
+
| `--port=<n>` | proxy | `3999` | Port for the proxy server |
|
|
50
|
+
| `--port=<n>` | dashboard | `4999` | Port for the dashboard |
|
|
51
|
+
| `--project=<slug>` | proxy, export | `default` | Scope to a project |
|
|
52
|
+
| `--yaml` | export | — | Output YAML instead of JSON |
|
|
53
|
+
| `--prod` | dashboard | — | Run `next start` instead of `next dev` |
|
|
54
|
+
|
|
55
|
+
## Environment variables
|
|
56
|
+
|
|
57
|
+
| Variable | Description |
|
|
58
|
+
|----------|-------------|
|
|
59
|
+
| `EASYDOCS_DB_URL` | Database URL (default: `~/.easydocs/db.sqlite`) |
|
|
60
|
+
| `OPENAI_API_KEY` | OpenAI API key |
|
|
61
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
|
62
|
+
| `EASYDOCS_DASHBOARD_PATH` | Path to a custom dashboard directory |
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { createDB, getAllEndpoints, getEndpointsByProject, findOrCreateProject } from "@easydocs/core";
|
|
5
|
+
import { capture } from "@easydocs/core";
|
|
6
|
+
import { createServer } from "http";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
import { spawn } from "child_process";
|
|
11
|
+
import yaml from "js-yaml";
|
|
12
|
+
var [, , command, ...args] = process.argv;
|
|
13
|
+
switch (command) {
|
|
14
|
+
case "dashboard":
|
|
15
|
+
await runDashboard(args);
|
|
16
|
+
break;
|
|
17
|
+
case "export":
|
|
18
|
+
await runExport(args);
|
|
19
|
+
break;
|
|
20
|
+
case "proxy":
|
|
21
|
+
case void 0:
|
|
22
|
+
await runProxy(args);
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
console.error(
|
|
26
|
+
`Unknown command: ${command}
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
easydocs [proxy] Start proxy server
|
|
30
|
+
easydocs dashboard Start the docs dashboard
|
|
31
|
+
easydocs export Export spec to stdout
|
|
32
|
+
|
|
33
|
+
Flags:
|
|
34
|
+
--project=<slug> Scope to a project (default: all)
|
|
35
|
+
--port=<n> Port for proxy (default: 3999) or dashboard (default: 4999)
|
|
36
|
+
--yaml Export as YAML instead of JSON
|
|
37
|
+
--prod Dashboard: run next start instead of next dev`
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
function getFlag(args2, name) {
|
|
42
|
+
const entry = args2.find((a) => a.startsWith(`--${name}=`));
|
|
43
|
+
return entry?.split("=").slice(1).join("=");
|
|
44
|
+
}
|
|
45
|
+
function findDashboardDir() {
|
|
46
|
+
if (process.env.EASYDOCS_DASHBOARD_PATH) {
|
|
47
|
+
return process.env.EASYDOCS_DASHBOARD_PATH;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const req = createRequire(join(process.cwd(), "package.json"));
|
|
51
|
+
const pkgPath = req.resolve("@easydocs/dashboard/package.json");
|
|
52
|
+
return dirname(pkgPath);
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
let dir = process.cwd();
|
|
56
|
+
for (let i = 0; i < 8; i++) {
|
|
57
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) {
|
|
58
|
+
const candidate = join(dir, "apps", "dashboard");
|
|
59
|
+
if (existsSync(join(candidate, "package.json"))) return candidate;
|
|
60
|
+
}
|
|
61
|
+
const parent = dirname(dir);
|
|
62
|
+
if (parent === dir) break;
|
|
63
|
+
dir = parent;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
async function runDashboard(args2) {
|
|
68
|
+
const port = parseInt(getFlag(args2, "port") ?? "4999", 10);
|
|
69
|
+
const prod = args2.includes("--prod");
|
|
70
|
+
const dashboardDir = findDashboardDir();
|
|
71
|
+
if (!dashboardDir) {
|
|
72
|
+
console.error("[EasyDocs] Dashboard package not found.\n");
|
|
73
|
+
console.error("Install it:");
|
|
74
|
+
console.error(" npm install -D @easydocs/dashboard");
|
|
75
|
+
console.error(" pnpm add -D @easydocs/dashboard\n");
|
|
76
|
+
console.error("Then run:");
|
|
77
|
+
console.error(" npx easydocs dashboard");
|
|
78
|
+
console.error("\nOr set EASYDOCS_DASHBOARD_PATH to your dashboard directory.");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
const nextBin = join(dashboardDir, "node_modules", ".bin", "next");
|
|
82
|
+
const cmd = existsSync(nextBin) ? nextBin : "next";
|
|
83
|
+
const mode = prod ? ["start", "--port", String(port)] : ["dev", "--port", String(port)];
|
|
84
|
+
console.log(`[EasyDocs] Starting dashboard (${prod ? "production" : "dev"}) \u2192 http://localhost:${port}`);
|
|
85
|
+
const child = spawn(cmd, mode, {
|
|
86
|
+
cwd: dashboardDir,
|
|
87
|
+
stdio: "inherit",
|
|
88
|
+
shell: process.platform === "win32",
|
|
89
|
+
env: { ...process.env, EASYDOCS_DB_URL: process.env.EASYDOCS_DB_URL }
|
|
90
|
+
});
|
|
91
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
92
|
+
}
|
|
93
|
+
async function runExport(args2) {
|
|
94
|
+
const format = args2.includes("--yaml") ? "yaml" : "json";
|
|
95
|
+
const projectSlug = getFlag(args2, "project");
|
|
96
|
+
const db = createDB(process.env.EASYDOCS_DB_URL);
|
|
97
|
+
let endpoints;
|
|
98
|
+
if (projectSlug) {
|
|
99
|
+
const projectId = await findOrCreateProject(db, projectSlug);
|
|
100
|
+
endpoints = await getEndpointsByProject(db, projectId);
|
|
101
|
+
} else {
|
|
102
|
+
endpoints = await getAllEndpoints(db);
|
|
103
|
+
}
|
|
104
|
+
const spec = {
|
|
105
|
+
openapi: "3.0.3",
|
|
106
|
+
info: { title: projectSlug ?? "API Documentation", version: "1.0.0" },
|
|
107
|
+
paths: endpoints.reduce((acc, e) => {
|
|
108
|
+
if (!e.path || !e.method) return acc;
|
|
109
|
+
const activeSpec = e.isManuallyEdited && e.manualSpec ? e.manualSpec : e.spec;
|
|
110
|
+
if (!activeSpec) return acc;
|
|
111
|
+
if (!acc[e.path]) acc[e.path] = {};
|
|
112
|
+
acc[e.path][e.method.toLowerCase()] = activeSpec;
|
|
113
|
+
return acc;
|
|
114
|
+
}, {})
|
|
115
|
+
};
|
|
116
|
+
process.stdout.write(format === "yaml" ? yaml.dump(spec) : JSON.stringify(spec, null, 2));
|
|
117
|
+
}
|
|
118
|
+
async function runProxy(args2) {
|
|
119
|
+
const port = parseInt(getFlag(args2, "port") ?? "3999", 10);
|
|
120
|
+
const projectSlug = getFlag(args2, "project") ?? "default";
|
|
121
|
+
const server = createServer(async (req, res) => {
|
|
122
|
+
const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
123
|
+
const targetParam = reqUrl.searchParams.get("target");
|
|
124
|
+
if (!targetParam) {
|
|
125
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
126
|
+
res.end(JSON.stringify({ error: "Missing ?target=<url> query parameter" }));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let targetUrl;
|
|
130
|
+
try {
|
|
131
|
+
targetUrl = new URL(decodeURIComponent(targetParam));
|
|
132
|
+
} catch {
|
|
133
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
134
|
+
res.end(JSON.stringify({ error: "Invalid target URL" }));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const method = req.method ?? "GET";
|
|
138
|
+
const startedAt = Date.now();
|
|
139
|
+
let requestBody;
|
|
140
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
141
|
+
requestBody = await new Promise((resolve, reject) => {
|
|
142
|
+
const chunks = [];
|
|
143
|
+
req.on("data", (c) => chunks.push(c));
|
|
144
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
145
|
+
req.on("error", reject);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const upstream = await fetch(targetUrl.toString(), {
|
|
149
|
+
method,
|
|
150
|
+
headers: Object.fromEntries(
|
|
151
|
+
Object.entries(req.headers).filter(([k]) => k !== "host").map(([k, v]) => [k, String(v)])
|
|
152
|
+
),
|
|
153
|
+
body: requestBody
|
|
154
|
+
});
|
|
155
|
+
const responseText = await upstream.text();
|
|
156
|
+
let responseBody;
|
|
157
|
+
try {
|
|
158
|
+
responseBody = JSON.parse(responseText);
|
|
159
|
+
} catch {
|
|
160
|
+
responseBody = responseText;
|
|
161
|
+
}
|
|
162
|
+
let parsedRequestBody = null;
|
|
163
|
+
if (requestBody?.length) {
|
|
164
|
+
try {
|
|
165
|
+
parsedRequestBody = JSON.parse(requestBody.toString());
|
|
166
|
+
} catch {
|
|
167
|
+
parsedRequestBody = requestBody.toString();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
capture(
|
|
171
|
+
{
|
|
172
|
+
method,
|
|
173
|
+
path: targetUrl.pathname,
|
|
174
|
+
query: Object.fromEntries(targetUrl.searchParams.entries()),
|
|
175
|
+
params: {},
|
|
176
|
+
body: parsedRequestBody,
|
|
177
|
+
response: responseBody,
|
|
178
|
+
status: upstream.status,
|
|
179
|
+
requestHeaders: req.headers,
|
|
180
|
+
responseHeaders: Object.fromEntries(upstream.headers.entries()),
|
|
181
|
+
durationMs: Date.now() - startedAt
|
|
182
|
+
},
|
|
183
|
+
{ project: projectSlug }
|
|
184
|
+
);
|
|
185
|
+
res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
|
|
186
|
+
res.end(responseText);
|
|
187
|
+
});
|
|
188
|
+
server.listen(port, () => {
|
|
189
|
+
console.log(`[EasyDocs] Proxy \u2192 http://localhost:${port} (project: ${projectSlug})`);
|
|
190
|
+
console.log(`[EasyDocs] Usage: http://localhost:${port}?target=https://api.example.com/users`);
|
|
191
|
+
console.log(`[EasyDocs] Dashboard: npx easydocs dashboard`);
|
|
192
|
+
});
|
|
193
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@easydocs/cli",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist",
|
|
6
|
+
"README.md"
|
|
7
|
+
],
|
|
8
|
+
"description": "EasyDocs CLI — proxy mode and spec export",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"easydocs": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"js-yaml": "^4.1.0",
|
|
15
|
+
"@easydocs/core": "0.1.4"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/js-yaml": "^4.0.9",
|
|
19
|
+
"@types/node": "^20",
|
|
20
|
+
"tsup": "^8.3.0",
|
|
21
|
+
"typescript": "^5"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
}
|
|
28
|
+
}
|