@mdocs/server 0.1.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.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @mdocs/server
2
+
3
+ Local HTTP server for mDocs. Clones public GitHub repositories, scans them for markdown files, and exposes their content over a REST API.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @mdocs/server
9
+ ```
10
+
11
+ ## Requirements
12
+
13
+ - Node.js >= 18
14
+ - Git (must be available in `PATH` for clone/sync operations)
15
+
16
+ ## Programmatic usage
17
+
18
+ ### `startServer(overrides?)`
19
+
20
+ Starts the server and returns a Node.js `http.Server`.
21
+
22
+ ```ts
23
+ import { startServer } from '@mdocs/server';
24
+
25
+ const server = await startServer({
26
+ port: 4873,
27
+ host: '127.0.0.1',
28
+ dataDir: '/path/to/project',
29
+ origins: ['http://localhost:3000'],
30
+ });
31
+
32
+ console.log('Server started');
33
+ ```
34
+
35
+ ### `createApp(config)`
36
+
37
+ Creates and returns an Express app without starting it — useful for testing or custom server setups.
38
+
39
+ ```ts
40
+ import { createApp, parseConfig } from '@mdocs/server';
41
+
42
+ const config = parseConfig({ port: 4873 });
43
+ const app = createApp(config);
44
+
45
+ app.listen(config.port, config.host);
46
+ ```
47
+
48
+ ### `parseConfig(overrides?)`
49
+
50
+ Merges overrides with environment variables and built-in defaults.
51
+
52
+ ```ts
53
+ import { parseConfig } from '@mdocs/server';
54
+
55
+ const config = parseConfig({ port: 5000 });
56
+ // { port: 5000, host: '127.0.0.1', dataDir: process.cwd(), origins: [...] }
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ | Option | Env var | Default |
62
+ |---|---|---|
63
+ | `port` | `PORT` | `4873` |
64
+ | `host` | `HOST` | `127.0.0.1` |
65
+ | `dataDir` | `DATA_DIR` | `process.cwd()` |
66
+ | `origins` | — | `['http://localhost:3000', 'http://127.0.0.1:3000', 'https://mdocs.vercel.app']` |
67
+
68
+ ## Standalone usage
69
+
70
+ Run the server directly without the CLI:
71
+
72
+ ```sh
73
+ node node_modules/@mdocs/server/dist/index.js
74
+ ```
75
+
76
+ Or with environment variables:
77
+
78
+ ```sh
79
+ PORT=5000 DATA_DIR=/my/project node node_modules/@mdocs/server/dist/index.js
80
+ ```
81
+
82
+ ## REST API
83
+
84
+ Base URL: `http://127.0.0.1:4873`
85
+
86
+ ---
87
+
88
+ ### Health
89
+
90
+ #### `GET /health`
91
+
92
+ Returns server status.
93
+
94
+ ```jsonc
95
+ // 200 OK
96
+ { "ok": true, "name": "mdocs-server", "version": "0.1.0" }
97
+ ```
98
+
99
+ ---
100
+
101
+ ### Repositories
102
+
103
+ #### `GET /api/repos`
104
+
105
+ Lists all tracked repositories.
106
+
107
+ ```jsonc
108
+ // 200 OK
109
+ [
110
+ {
111
+ "id": "550e8400-e29b-41d4-a716-446655440000",
112
+ "name": "owner/repo",
113
+ "url": "https://github.com/owner/repo",
114
+ "branch": "main",
115
+ "clonedAt": "2026-05-09T10:00:00.000Z",
116
+ "lastSyncedAt": null,
117
+ "currentCommit": "abc1234...",
118
+ "fileCount": 12
119
+ }
120
+ ]
121
+ ```
122
+
123
+ ---
124
+
125
+ #### `POST /api/repos/clone`
126
+
127
+ Clones a public GitHub repository and starts tracking it.
128
+
129
+ **Request body:**
130
+
131
+ | Field | Type | Required | Description |
132
+ |---|---|---|---|
133
+ | `url` | `string` | Yes | Public GitHub HTTPS URL (`https://github.com/owner/repo`) |
134
+ | `branch` | `string` | No | Branch to clone (defaults to the repo's default branch) |
135
+
136
+ ```sh
137
+ curl -X POST http://127.0.0.1:4873/api/repos/clone \
138
+ -H "Content-Type: application/json" \
139
+ -d '{"url": "https://github.com/owner/repo", "branch": "main"}'
140
+ ```
141
+
142
+ ```jsonc
143
+ // 201 Created
144
+ {
145
+ "id": "550e8400-e29b-41d4-a716-446655440000",
146
+ "name": "owner/repo",
147
+ "url": "https://github.com/owner/repo",
148
+ "branch": "main",
149
+ "clonedAt": "2026-05-09T10:00:00.000Z",
150
+ "lastSyncedAt": null,
151
+ "currentCommit": "abc1234...",
152
+ "fileCount": 12
153
+ }
154
+ ```
155
+
156
+ > Only public `https://github.com/owner/repo` URLs are accepted. SSH URLs and non-GitHub hosts are rejected.
157
+
158
+ ---
159
+
160
+ #### `POST /api/repos/:repoId/sync`
161
+
162
+ Pulls the latest changes for a tracked repository (`git pull --ff-only`).
163
+
164
+ ```sh
165
+ curl -X POST http://127.0.0.1:4873/api/repos/550e8400-e29b-41d4-a716-446655440000/sync
166
+ ```
167
+
168
+ Returns the updated `RepoMeta` object.
169
+
170
+ ---
171
+
172
+ #### `DELETE /api/repos/:repoId`
173
+
174
+ Removes a repository from tracking and deletes its local clone.
175
+
176
+ ```sh
177
+ curl -X DELETE http://127.0.0.1:4873/api/repos/550e8400-e29b-41d4-a716-446655440000
178
+ ```
179
+
180
+ ```
181
+ 204 No Content
182
+ ```
183
+
184
+ ---
185
+
186
+ ### Files
187
+
188
+ #### `GET /api/repos/:repoId/files`
189
+
190
+ Lists all markdown files in the repository (`.md`, `.mdx`, `.markdown`). Files larger than 1 MB and common non-doc directories (`node_modules`, `dist`, `.git`, etc.) are excluded.
191
+
192
+ ```jsonc
193
+ // 200 OK
194
+ [
195
+ {
196
+ "id": "docs/intro.md",
197
+ "repoId": "550e8400-e29b-41d4-a716-446655440000",
198
+ "name": "intro.md",
199
+ "relPath": "docs/intro.md",
200
+ "size": 1024,
201
+ "lastModified": 1746784800000
202
+ }
203
+ ]
204
+ ```
205
+
206
+ ---
207
+
208
+ #### `GET /api/repos/:repoId/files/:path`
209
+
210
+ Returns the content of a specific file.
211
+
212
+ ```sh
213
+ curl http://127.0.0.1:4873/api/repos/550e8400-e29b-41d4-a716-446655440000/files/docs/intro.md
214
+ ```
215
+
216
+ ```jsonc
217
+ // 200 OK
218
+ {
219
+ "id": "docs/intro.md",
220
+ "repoId": "550e8400-e29b-41d4-a716-446655440000",
221
+ "name": "intro.md",
222
+ "relPath": "docs/intro.md",
223
+ "size": 1024,
224
+ "lastModified": 1746784800000,
225
+ "content": "# Introduction\n\nWelcome to the docs..."
226
+ }
227
+ ```
228
+
229
+ ---
230
+
231
+ ## TypeScript types
232
+
233
+ All public types are exported:
234
+
235
+ ```ts
236
+ import type { Config, RepoMeta, FileRef, FileContent } from '@mdocs/server';
237
+ ```
238
+
239
+ ## Security
240
+
241
+ - CORS is enforced — only origins listed in `config.origins` are allowed.
242
+ - Only `https://github.com` URLs are accepted for cloning.
243
+ - File reads are path-traversal-safe — resolved paths are checked to stay within the clone directory.
244
+ - Repository IDs are validated against path traversal before any file operation.
245
+
246
+ ## Related packages
247
+
248
+ - [`@mdocs/cli`](https://www.npmjs.com/package/@mdocs/cli) — CLI wrapper around this server
@@ -0,0 +1,42 @@
1
+ import { Server } from 'node:http';
2
+ import { Express } from 'express';
3
+
4
+ interface RepoMeta {
5
+ id: string;
6
+ name: string;
7
+ url: string;
8
+ branch: string;
9
+ clonedAt: string;
10
+ lastSyncedAt: string | null;
11
+ currentCommit: string | null;
12
+ fileCount: number;
13
+ }
14
+ interface FileRef {
15
+ id: string;
16
+ repoId: string;
17
+ name: string;
18
+ relPath: string;
19
+ size: number;
20
+ lastModified: number;
21
+ }
22
+ interface FileContent extends FileRef {
23
+ content: string;
24
+ }
25
+ interface Config {
26
+ port: number;
27
+ host: string;
28
+ dataDir: string;
29
+ origins: string[];
30
+ }
31
+
32
+ declare function createApp(config: Config): Express;
33
+
34
+ declare const DEFAULT_PORT = 4873;
35
+ declare const DEFAULT_HOST = "127.0.0.1";
36
+ declare const DEFAULT_ORIGINS: string[];
37
+ declare function parseConfig(overrides?: Partial<Config>): Config;
38
+ declare function reposDir(dataDir: string): string;
39
+
40
+ declare function startServer(overrides?: Partial<Config>): Promise<Server>;
41
+
42
+ export { type Config, DEFAULT_HOST, DEFAULT_ORIGINS, DEFAULT_PORT, type FileContent, type FileRef, type RepoMeta, createApp, parseConfig, reposDir, startServer };
package/dist/index.js ADDED
@@ -0,0 +1,377 @@
1
+ // src/index.ts
2
+ import { fileURLToPath } from "url";
3
+ import { resolve as resolve3 } from "path";
4
+
5
+ // src/app.ts
6
+ import express from "express";
7
+
8
+ // src/security/cors.ts
9
+ import cors from "cors";
10
+ function createCorsMiddleware(config) {
11
+ return cors({
12
+ origin: (origin, cb) => {
13
+ if (!origin) return cb(null, true);
14
+ if (config.origins.includes(origin)) return cb(null, true);
15
+ cb(new Error(`Origin ${origin} is not allowed`));
16
+ },
17
+ methods: ["GET", "POST", "DELETE", "OPTIONS"],
18
+ allowedHeaders: ["Content-Type"]
19
+ });
20
+ }
21
+
22
+ // src/routes/health.ts
23
+ import { Router } from "express";
24
+ function healthRouter() {
25
+ const router = Router();
26
+ router.get("/", (_req, res) => {
27
+ res.json({ ok: true, name: "mdocs-server", version: "0.1.0" });
28
+ });
29
+ return router;
30
+ }
31
+
32
+ // src/routes/repos.ts
33
+ import { Router as Router3 } from "express";
34
+ import { randomUUID } from "crypto";
35
+ import { join as join4 } from "path";
36
+
37
+ // src/config.ts
38
+ import { resolve } from "path";
39
+ var DEFAULT_PORT = 4873;
40
+ var DEFAULT_HOST = "127.0.0.1";
41
+ var MDOCS_DIR = ".mdocs";
42
+ var REPOS_SUBDIR = "repos";
43
+ var DEFAULT_ORIGINS = [
44
+ "http://localhost:3000",
45
+ "http://127.0.0.1:3000",
46
+ "https://mdocs.vercel.app"
47
+ ];
48
+ function parseConfig(overrides = {}) {
49
+ return {
50
+ port: overrides.port ?? parseInt(process.env["PORT"] ?? String(DEFAULT_PORT), 10),
51
+ host: overrides.host ?? process.env["HOST"] ?? DEFAULT_HOST,
52
+ dataDir: overrides.dataDir ?? process.env["DATA_DIR"] ?? process.cwd(),
53
+ origins: overrides.origins ?? DEFAULT_ORIGINS
54
+ };
55
+ }
56
+ function reposDir(dataDir) {
57
+ return resolve(dataDir, MDOCS_DIR, REPOS_SUBDIR);
58
+ }
59
+
60
+ // src/security/github-url.ts
61
+ function validateGitHubUrl(url) {
62
+ try {
63
+ const parsed = new URL(url);
64
+ if (parsed.protocol !== "https:") return null;
65
+ if (parsed.hostname !== "github.com") return null;
66
+ const parts = parsed.pathname.replace(/^\//, "").replace(/\.git$/, "").split("/");
67
+ if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
68
+ return { owner: parts[0], repo: parts[1] };
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ // src/services/git.ts
75
+ import { spawn } from "child_process";
76
+ function run(args, cwd) {
77
+ return new Promise((resolve4, reject) => {
78
+ const proc = spawn("git", args, {
79
+ cwd,
80
+ stdio: ["ignore", "pipe", "pipe"]
81
+ });
82
+ let stdout = "";
83
+ let stderr = "";
84
+ proc.stdout.on("data", (chunk) => stdout += chunk.toString());
85
+ proc.stderr.on("data", (chunk) => stderr += chunk.toString());
86
+ proc.on("close", (code) => {
87
+ if (code === 0) resolve4(stdout.trim());
88
+ else reject(new Error(stderr.trim() || `git exited with code ${code}`));
89
+ });
90
+ proc.on("error", reject);
91
+ });
92
+ }
93
+ async function cloneRepo(url, dest, branch) {
94
+ const args = ["clone", url, dest, "--single-branch"];
95
+ if (branch) args.push("--branch", branch);
96
+ await run(args);
97
+ }
98
+ async function pullRepo(repoPath) {
99
+ await run(["pull", "--ff-only"], repoPath);
100
+ }
101
+ async function getHeadCommit(repoPath) {
102
+ return run(["rev-parse", "HEAD"], repoPath);
103
+ }
104
+ async function getDefaultBranch(repoPath) {
105
+ try {
106
+ return await run(["symbolic-ref", "--short", "HEAD"], repoPath);
107
+ } catch {
108
+ return "main";
109
+ }
110
+ }
111
+
112
+ // src/services/scanner.ts
113
+ import { readdirSync, statSync } from "fs";
114
+ import { join, relative, basename, extname } from "path";
115
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
116
+ ".git",
117
+ "node_modules",
118
+ ".next",
119
+ "dist",
120
+ "build",
121
+ ".turbo",
122
+ "vendor",
123
+ ".cache"
124
+ ]);
125
+ var MD_EXTS = /* @__PURE__ */ new Set([".md", ".markdown", ".mdx"]);
126
+ var MAX_FILE_SIZE = 1024 * 1024;
127
+ function scanMarkdownFiles(repoPath, repoId) {
128
+ const results = [];
129
+ function walk(dir) {
130
+ let entries;
131
+ try {
132
+ entries = readdirSync(dir);
133
+ } catch {
134
+ return;
135
+ }
136
+ for (const entry of entries) {
137
+ if (SKIP_DIRS.has(entry)) continue;
138
+ const fullPath = join(dir, entry);
139
+ let stat;
140
+ try {
141
+ stat = statSync(fullPath);
142
+ } catch {
143
+ continue;
144
+ }
145
+ if (stat.isDirectory()) {
146
+ walk(fullPath);
147
+ continue;
148
+ }
149
+ if (!stat.isFile()) continue;
150
+ if (!MD_EXTS.has(extname(entry).toLowerCase())) continue;
151
+ if (stat.size > MAX_FILE_SIZE) continue;
152
+ const relPath = relative(repoPath, fullPath).replace(/\\/g, "/");
153
+ results.push({
154
+ id: relPath,
155
+ repoId,
156
+ name: basename(entry),
157
+ relPath,
158
+ size: stat.size,
159
+ lastModified: stat.mtimeMs
160
+ });
161
+ }
162
+ }
163
+ walk(repoPath);
164
+ return results;
165
+ }
166
+
167
+ // src/services/repo-store.ts
168
+ import { readFileSync, writeFileSync, existsSync, readdirSync as readdirSync2, unlinkSync, rmSync } from "fs";
169
+ import { join as join2 } from "path";
170
+ function listRepos(reposDir2) {
171
+ if (!existsSync(reposDir2)) return [];
172
+ return readdirSync2(reposDir2).filter((f) => f.endsWith(".json")).flatMap((f) => {
173
+ try {
174
+ return [JSON.parse(readFileSync(join2(reposDir2, f), "utf8"))];
175
+ } catch {
176
+ return [];
177
+ }
178
+ });
179
+ }
180
+ function getRepo(reposDir2, id) {
181
+ if (id.includes("/") || id.includes("\\") || id.includes("..")) return null;
182
+ const file = join2(reposDir2, `${id}.json`);
183
+ if (!existsSync(file)) return null;
184
+ try {
185
+ return JSON.parse(readFileSync(file, "utf8"));
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+ function saveRepo(reposDir2, meta) {
191
+ writeFileSync(join2(reposDir2, `${meta.id}.json`), JSON.stringify(meta, null, 2), "utf8");
192
+ }
193
+ function deleteRepo(reposDir2, id) {
194
+ if (id.includes("/") || id.includes("\\") || id.includes("..")) return;
195
+ const metaFile = join2(reposDir2, `${id}.json`);
196
+ const cloneDir = join2(reposDir2, id);
197
+ if (existsSync(metaFile)) unlinkSync(metaFile);
198
+ if (existsSync(cloneDir)) rmSync(cloneDir, { recursive: true, force: true });
199
+ }
200
+
201
+ // src/routes/files.ts
202
+ import { Router as Router2 } from "express";
203
+ import { readFileSync as readFileSync2, statSync as statSync2, existsSync as existsSync2 } from "fs";
204
+ import { join as join3 } from "path";
205
+
206
+ // src/security/paths.ts
207
+ import { resolve as resolve2 } from "path";
208
+ function safeResolve(base, ...parts) {
209
+ const resolved = resolve2(base, ...parts);
210
+ return resolved.startsWith(base) ? resolved : null;
211
+ }
212
+
213
+ // src/routes/files.ts
214
+ function filesRouter(config) {
215
+ const router = Router2({ mergeParams: true });
216
+ const reposDir2 = reposDir(config.dataDir);
217
+ router.get("*", (req, res) => {
218
+ const { repoId } = req.params;
219
+ if (!getRepo(reposDir2, repoId)) {
220
+ res.status(404).json({ error: "Repository not found" });
221
+ return;
222
+ }
223
+ const cloneDir = join3(reposDir2, repoId);
224
+ const relPath = req.path === "/" ? "" : decodeURIComponent(req.path.slice(1));
225
+ if (!relPath) {
226
+ res.json(scanMarkdownFiles(cloneDir, repoId));
227
+ return;
228
+ }
229
+ const absPath = safeResolve(cloneDir, relPath);
230
+ if (!absPath) {
231
+ res.status(400).json({ error: "Invalid file path" });
232
+ return;
233
+ }
234
+ if (!existsSync2(absPath)) {
235
+ res.status(404).json({ error: "File not found" });
236
+ return;
237
+ }
238
+ try {
239
+ const stat = statSync2(absPath);
240
+ const content = readFileSync2(absPath, "utf8");
241
+ const file = {
242
+ id: relPath,
243
+ repoId,
244
+ name: relPath.split("/").pop() ?? relPath,
245
+ relPath,
246
+ size: stat.size,
247
+ lastModified: stat.mtimeMs,
248
+ content
249
+ };
250
+ res.json(file);
251
+ } catch {
252
+ res.status(500).json({ error: "Failed to read file" });
253
+ }
254
+ });
255
+ return router;
256
+ }
257
+
258
+ // src/routes/repos.ts
259
+ function reposRouter(config) {
260
+ const router = Router3();
261
+ const reposDir2 = reposDir(config.dataDir);
262
+ router.use("/:repoId/files", filesRouter(config));
263
+ router.get("/", (_req, res) => {
264
+ res.json(listRepos(reposDir2));
265
+ });
266
+ router.post("/clone", async (req, res) => {
267
+ const { url, branch } = req.body;
268
+ if (!url || typeof url !== "string") {
269
+ res.status(400).json({ error: '"url" is required' });
270
+ return;
271
+ }
272
+ const parsed = validateGitHubUrl(url);
273
+ if (!parsed) {
274
+ res.status(400).json({ error: "Only public GitHub HTTPS URLs are supported (https://github.com/owner/repo)" });
275
+ return;
276
+ }
277
+ const id = randomUUID();
278
+ const cloneDir = join4(reposDir2, id);
279
+ try {
280
+ await cloneRepo(url, cloneDir, branch);
281
+ const detectedBranch = branch ?? await getDefaultBranch(cloneDir);
282
+ const currentCommit = await getHeadCommit(cloneDir);
283
+ const files = scanMarkdownFiles(cloneDir, id);
284
+ const meta = {
285
+ id,
286
+ name: `${parsed.owner}/${parsed.repo}`,
287
+ url,
288
+ branch: detectedBranch,
289
+ clonedAt: (/* @__PURE__ */ new Date()).toISOString(),
290
+ lastSyncedAt: null,
291
+ currentCommit,
292
+ fileCount: files.length
293
+ };
294
+ saveRepo(reposDir2, meta);
295
+ res.status(201).json(meta);
296
+ } catch (err) {
297
+ res.status(500).json({ error: err.message });
298
+ }
299
+ });
300
+ router.post("/:repoId/sync", async (req, res) => {
301
+ const { repoId } = req.params;
302
+ const meta = getRepo(reposDir2, repoId);
303
+ if (!meta) {
304
+ res.status(404).json({ error: "Repository not found" });
305
+ return;
306
+ }
307
+ const cloneDir = join4(reposDir2, repoId);
308
+ try {
309
+ await pullRepo(cloneDir);
310
+ const currentCommit = await getHeadCommit(cloneDir);
311
+ const files = scanMarkdownFiles(cloneDir, repoId);
312
+ const updated = {
313
+ ...meta,
314
+ currentCommit,
315
+ fileCount: files.length,
316
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
317
+ };
318
+ saveRepo(reposDir2, updated);
319
+ res.json(updated);
320
+ } catch (err) {
321
+ res.status(500).json({ error: err.message });
322
+ }
323
+ });
324
+ router.delete("/:repoId", (req, res) => {
325
+ const { repoId } = req.params;
326
+ if (!getRepo(reposDir2, repoId)) {
327
+ res.status(404).json({ error: "Repository not found" });
328
+ return;
329
+ }
330
+ deleteRepo(reposDir2, repoId);
331
+ res.status(204).send();
332
+ });
333
+ return router;
334
+ }
335
+
336
+ // src/app.ts
337
+ function createApp(config) {
338
+ const app = express();
339
+ app.use(createCorsMiddleware(config));
340
+ app.use(express.json());
341
+ app.use("/health", healthRouter());
342
+ app.use("/api/repos", reposRouter(config));
343
+ app.use((_req, res) => {
344
+ res.status(404).json({ error: "Not found" });
345
+ });
346
+ return app;
347
+ }
348
+
349
+ // src/index.ts
350
+ async function startServer(overrides = {}) {
351
+ const config = parseConfig(overrides);
352
+ const app = createApp(config);
353
+ return new Promise((resolve_, reject) => {
354
+ const server = app.listen(config.port, config.host, () => resolve_(server));
355
+ server.on("error", reject);
356
+ });
357
+ }
358
+ var __filename2 = fileURLToPath(import.meta.url);
359
+ var isMain = process.argv[1] !== void 0 && resolve3(process.argv[1]) === resolve3(__filename2);
360
+ if (isMain) {
361
+ const config = parseConfig();
362
+ startServer(config).then(() => {
363
+ console.log(`mDocs server listening at http://${config.host}:${config.port}`);
364
+ }).catch((err) => {
365
+ console.error("Failed to start server:", err.message);
366
+ process.exit(1);
367
+ });
368
+ }
369
+ export {
370
+ DEFAULT_HOST,
371
+ DEFAULT_ORIGINS,
372
+ DEFAULT_PORT,
373
+ createApp,
374
+ parseConfig,
375
+ reposDir,
376
+ startServer
377
+ };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@mdocs/server",
3
+ "version": "0.1.0",
4
+ "description": "mDocs local HTTP server",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "types": "./dist/index.d.ts",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "cors": "^2.8.5",
19
+ "express": "^4.19.2"
20
+ },
21
+ "devDependencies": {
22
+ "@types/cors": "^2.8.17",
23
+ "@types/express": "^4.17.21",
24
+ "@types/node": "^20.14.0",
25
+ "tsup": "^8.1.0",
26
+ "tsx": "^4.15.0",
27
+ "typescript": "^5.4.5"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "dev": "tsx src/index.ts",
32
+ "typecheck": "tsc --noEmit"
33
+ }
34
+ }