@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 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
+ }