@doccov/api 0.3.5 → 0.3.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccov/api",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "DocCov API - Badge endpoint and coverage services",
5
5
  "keywords": [
6
6
  "doccov",
@@ -19,12 +19,11 @@
19
19
  "author": "Ryan Waits",
20
20
  "type": "module",
21
21
  "scripts": {
22
- "build": "bunup",
23
22
  "dev": "bun run --hot src/index.ts",
24
23
  "start": "bun run src/index.ts",
25
- "lint": "biome check src/ functions/ lib/",
26
- "lint:fix": "biome check --write src/ functions/ lib/",
27
- "format": "biome format --write src/ functions/ lib/"
24
+ "lint": "biome check src/ api/",
25
+ "lint:fix": "biome check --write src/ api/",
26
+ "format": "biome format --write src/ api/"
28
27
  },
29
28
  "dependencies": {
30
29
  "@ai-sdk/anthropic": "^2.0.55",
@@ -32,7 +31,6 @@
32
31
  "@openpkg-ts/spec": "^0.9.0",
33
32
  "@vercel/sandbox": "^1.0.3",
34
33
  "ai": "^5.0.111",
35
- "hono": "^4.0.0",
36
34
  "ms": "^2.1.3",
37
35
  "zod": "^3.25.0"
38
36
  },
@@ -41,7 +39,6 @@
41
39
  "@types/ms": "^0.7.34",
42
40
  "@types/node": "^20.0.0",
43
41
  "@vercel/node": "^3.0.0",
44
- "bunup": "latest",
45
42
  "typescript": "^5.0.0"
46
43
  }
47
44
  }
@@ -34,10 +34,13 @@ planRoute.post('/', async (c) => {
34
34
 
35
35
  // Check for private repos
36
36
  if (context.metadata.isPrivate) {
37
- return c.json({
38
- error: 'Private repositories are not supported',
39
- hint: 'Use a public repository or run doccov locally',
40
- }, 403);
37
+ return c.json(
38
+ {
39
+ error: 'Private repositories are not supported',
40
+ hint: 'Use a public repository or run doccov locally',
41
+ },
42
+ 403,
43
+ );
41
44
  }
42
45
 
43
46
  // Generate build plan using AI
@@ -67,9 +70,12 @@ planRoute.post('/', async (c) => {
67
70
  }
68
71
  }
69
72
 
70
- return c.json({
71
- error: 'Failed to generate build plan',
72
- message: error instanceof Error ? error.message : 'Unknown error',
73
- }, 500);
73
+ return c.json(
74
+ {
75
+ error: 'Failed to generate build plan',
76
+ message: error instanceof Error ? error.message : 'Unknown error',
77
+ },
78
+ 500,
79
+ );
74
80
  }
75
81
  });
package/vercel.json CHANGED
@@ -1,12 +1,8 @@
1
1
  {
2
+ "framework": null,
2
3
  "installCommand": "bun install",
3
- "buildCommand": "bun run build",
4
+ "outputDirectory": ".",
4
5
  "rewrites": [
5
- { "source": "/badge/:path*", "destination": "/api" },
6
- { "source": "/spec/:path*", "destination": "/api" },
7
- { "source": "/plan", "destination": "/api/plan" },
8
- { "source": "/execute", "destination": "/api/execute" },
9
- { "source": "/execute-stream", "destination": "/api/execute-stream" },
10
6
  { "source": "/(.*)", "destination": "/api" }
11
7
  ]
12
8
  }
package/api/[...path].ts DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * Catch-all router for Vercel serverless functions.
3
- * Routes requests to the appropriate bundled function handler.
4
- */
5
- import type { VercelRequest, VercelResponse } from '@vercel/node';
6
-
7
- // Import bundled handlers (built by bunup)
8
- import executeHandler from '../dist/functions/execute';
9
- import executeStreamHandler from '../dist/functions/execute-stream';
10
- import planHandler from '../dist/functions/plan';
11
-
12
- export const config = {
13
- runtime: 'nodejs',
14
- maxDuration: 300,
15
- };
16
-
17
- type Handler = (req: VercelRequest, res: VercelResponse) => Promise<void | VercelResponse>;
18
-
19
- const handlers: Record<string, Handler> = {
20
- execute: executeHandler,
21
- 'execute-stream': executeStreamHandler,
22
- plan: planHandler,
23
- };
24
-
25
- export default async function handler(req: VercelRequest, res: VercelResponse) {
26
- // Extract path from catch-all
27
- const pathSegments = req.query.path;
28
- const route = Array.isArray(pathSegments) ? pathSegments[0] : pathSegments;
29
-
30
- if (!route || !handlers[route]) {
31
- return res.status(404).json({ error: 'Not found', availableRoutes: Object.keys(handlers) });
32
- }
33
-
34
- return handlers[route](req, res);
35
- }
package/bunup.config.ts DELETED
@@ -1,16 +0,0 @@
1
- import { defineConfig } from 'bunup';
2
-
3
- export default defineConfig({
4
- entry: ['functions/execute.ts', 'functions/execute-stream.ts', 'functions/plan.ts'],
5
- dts: false,
6
- clean: true,
7
- splitting: false,
8
- format: ['esm'],
9
- outDir: 'dist/functions',
10
- external: [
11
- '@vercel/node',
12
- '@vercel/sandbox',
13
- '@doccov/sdk',
14
- '@openpkg-ts/spec',
15
- ],
16
- });
@@ -1,273 +0,0 @@
1
- /**
2
- * GET /execute-stream - Execute a build plan with SSE streaming progress
3
- *
4
- * Query params:
5
- * - plan: Base64-encoded BuildPlan JSON (required)
6
- *
7
- * SSE Events:
8
- * - step-start: { stepId, name }
9
- * - step-progress: { stepId, output }
10
- * - step-complete: { stepId, success, duration, error? }
11
- * - complete: { success, spec?, totalDuration, error? }
12
- * - error: { message }
13
- */
14
-
15
- import type { BuildPlan, BuildPlanExecutionResult, BuildPlanStepResult } from '@doccov/sdk';
16
- import type { OpenPkg } from '@openpkg-ts/spec';
17
- import { Sandbox } from '@vercel/sandbox';
18
- import type { VercelRequest, VercelResponse } from '@vercel/node';
19
- import ms from 'ms';
20
-
21
- export const config = {
22
- runtime: 'nodejs',
23
- maxDuration: 300, // 5 minutes for full execution
24
- };
25
-
26
- /**
27
- * SSE event types for execute streaming
28
- */
29
- type ExecuteEvent =
30
- | { type: 'step-start'; stepId: string; name: string }
31
- | { type: 'step-progress'; stepId: string; output: string }
32
- | { type: 'step-complete'; stepId: string; success: boolean; duration: number; error?: string }
33
- | { type: 'complete'; success: boolean; spec?: OpenPkg; totalDuration: number; error?: string }
34
- | { type: 'error'; message: string };
35
-
36
- /**
37
- * Map runtime to Vercel sandbox runtime
38
- */
39
- function getSandboxRuntime(runtime: string): 'node22' | 'node24' {
40
- if (runtime === 'node24') return 'node24';
41
- return 'node22'; // Default to node22 for everything else
42
- }
43
-
44
- export default async function handler(req: VercelRequest, res: VercelResponse) {
45
- // CORS
46
- res.setHeader('Access-Control-Allow-Origin', '*');
47
- res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
48
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
49
-
50
- if (req.method === 'OPTIONS') {
51
- return res.status(200).end();
52
- }
53
-
54
- if (req.method !== 'GET') {
55
- return res.status(405).json({ error: 'Method not allowed' });
56
- }
57
-
58
- // Get plan from query string (base64 encoded)
59
- const planBase64 = req.query.plan as string;
60
-
61
- if (!planBase64) {
62
- return res.status(400).json({ error: 'plan query param is required (base64 encoded JSON)' });
63
- }
64
-
65
- let plan: BuildPlan;
66
- try {
67
- const planJson = Buffer.from(planBase64, 'base64').toString('utf-8');
68
- plan = JSON.parse(planJson) as BuildPlan;
69
- } catch {
70
- return res.status(400).json({ error: 'Invalid plan: must be valid base64-encoded JSON' });
71
- }
72
-
73
- // Validate plan structure
74
- if (!plan.target?.repoUrl || !plan.steps) {
75
- return res.status(400).json({ error: 'Invalid plan structure' });
76
- }
77
-
78
- // Set SSE headers
79
- res.setHeader('Content-Type', 'text/event-stream');
80
- res.setHeader('Cache-Control', 'no-cache');
81
- res.setHeader('Connection', 'keep-alive');
82
-
83
- // Send initial comment
84
- res.write(':ok\n\n');
85
-
86
- // Helper to send SSE event
87
- const sendEvent = (event: ExecuteEvent) => {
88
- const data = JSON.stringify(event);
89
- res.write(`data: ${data}\n\n`);
90
- };
91
-
92
- // Run execution with streaming progress
93
- await runExecuteWithProgress(plan, sendEvent);
94
-
95
- res.end();
96
- }
97
-
98
- async function runExecuteWithProgress(
99
- plan: BuildPlan,
100
- sendEvent: (event: ExecuteEvent) => void,
101
- ): Promise<void> {
102
- const startTime = Date.now();
103
- const stepResults: BuildPlanStepResult[] = [];
104
- let sandbox: Awaited<ReturnType<typeof Sandbox.create>> | null = null;
105
-
106
- try {
107
- // Create sandbox with git source
108
- sandbox = await Sandbox.create({
109
- source: {
110
- url: plan.target.repoUrl,
111
- type: 'git',
112
- },
113
- resources: { vcpus: 4 },
114
- timeout: ms('5m'),
115
- runtime: getSandboxRuntime(plan.environment.runtime),
116
- });
117
-
118
- // Execute each step
119
- for (const step of plan.steps) {
120
- const stepStart = Date.now();
121
-
122
- sendEvent({
123
- type: 'step-start',
124
- stepId: step.id,
125
- name: step.name,
126
- });
127
-
128
- try {
129
- const result = await sandbox.runCommand({
130
- cmd: step.command,
131
- args: step.args,
132
- cwd: step.cwd,
133
- timeout: step.timeout ?? 60000,
134
- });
135
-
136
- // stdout/stderr are async functions in Vercel Sandbox
137
- const stdout = result.stdout ? await result.stdout() : '';
138
- const stderr = result.stderr ? await result.stderr() : '';
139
-
140
- const success = result.exitCode === 0;
141
- const duration = Date.now() - stepStart;
142
-
143
- stepResults.push({
144
- stepId: step.id,
145
- success,
146
- duration,
147
- output: stdout.slice(0, 5000),
148
- error: !success ? stderr.slice(0, 2000) : undefined,
149
- });
150
-
151
- sendEvent({
152
- type: 'step-complete',
153
- stepId: step.id,
154
- success,
155
- duration,
156
- error: !success ? stderr.slice(0, 500) : undefined,
157
- });
158
-
159
- // Stop on non-optional failures
160
- if (!success && !step.optional) {
161
- throw new Error(`Step '${step.name}' failed: ${stderr.slice(0, 500)}`);
162
- }
163
- } catch (stepError) {
164
- const duration = Date.now() - stepStart;
165
- const errorMsg = stepError instanceof Error ? stepError.message : 'Unknown error';
166
-
167
- stepResults.push({
168
- stepId: step.id,
169
- success: false,
170
- duration,
171
- error: errorMsg,
172
- });
173
-
174
- sendEvent({
175
- type: 'step-complete',
176
- stepId: step.id,
177
- success: false,
178
- duration,
179
- error: errorMsg.slice(0, 500),
180
- });
181
-
182
- if (!step.optional) {
183
- throw stepError;
184
- }
185
- }
186
- }
187
-
188
- // Run doccov spec to generate the OpenPkg spec
189
- const analyzeStep = {
190
- id: 'analyze',
191
- name: 'Generate OpenPkg spec',
192
- };
193
-
194
- sendEvent({
195
- type: 'step-start',
196
- stepId: analyzeStep.id,
197
- name: analyzeStep.name,
198
- });
199
-
200
- const specStart = Date.now();
201
- const entryPoint = plan.target.entryPoints[0] ?? 'src/index.ts';
202
-
203
- // Install doccov CLI first
204
- await sandbox.runCommand({
205
- cmd: 'npm',
206
- args: ['install', '-g', '@doccov/cli@latest'],
207
- });
208
-
209
- // Run spec generation
210
- const specResult = await sandbox.runCommand({
211
- cmd: 'doccov',
212
- args: ['spec', entryPoint, '--output', 'openpkg.json'],
213
- cwd: plan.target.rootPath,
214
- });
215
-
216
- const specStdout = specResult.stdout ? await specResult.stdout() : '';
217
- const specStderr = specResult.stderr ? await specResult.stderr() : '';
218
- const specDuration = Date.now() - specStart;
219
-
220
- stepResults.push({
221
- stepId: 'analyze',
222
- success: specResult.exitCode === 0,
223
- duration: specDuration,
224
- output: specStdout.slice(0, 2000),
225
- error: specResult.exitCode !== 0 ? specStderr.slice(0, 2000) : undefined,
226
- });
227
-
228
- sendEvent({
229
- type: 'step-complete',
230
- stepId: 'analyze',
231
- success: specResult.exitCode === 0,
232
- duration: specDuration,
233
- error: specResult.exitCode !== 0 ? specStderr.slice(0, 500) : undefined,
234
- });
235
-
236
- if (specResult.exitCode !== 0) {
237
- throw new Error(`Spec generation failed: ${specStderr.slice(0, 500)}`);
238
- }
239
-
240
- // Read the generated spec - readFile returns a readable stream
241
- const specStream = await sandbox.readFile({ path: 'openpkg.json' });
242
- const chunks: Buffer[] = [];
243
- for await (const chunk of specStream as AsyncIterable<Buffer>) {
244
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
245
- }
246
- const specContent = Buffer.concat(chunks).toString('utf-8');
247
- const spec = JSON.parse(specContent) as OpenPkg;
248
-
249
- sendEvent({
250
- type: 'complete',
251
- success: true,
252
- spec,
253
- totalDuration: Date.now() - startTime,
254
- });
255
- } catch (error) {
256
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
257
-
258
- sendEvent({
259
- type: 'complete',
260
- success: false,
261
- totalDuration: Date.now() - startTime,
262
- error: errorMsg,
263
- });
264
- } finally {
265
- if (sandbox) {
266
- try {
267
- await sandbox.stop();
268
- } catch {
269
- // Ignore cleanup errors
270
- }
271
- }
272
- }
273
- }
@@ -1,204 +0,0 @@
1
- /**
2
- * POST /execute - Execute a build plan in a Vercel Sandbox
3
- *
4
- * Request body:
5
- * - plan: BuildPlan object (required)
6
- *
7
- * Response:
8
- * - BuildPlanExecutionResult with spec and step results
9
- */
10
-
11
- import type { BuildPlan, BuildPlanExecutionResult, BuildPlanStepResult } from '@doccov/sdk';
12
- import type { OpenPkg } from '@openpkg-ts/spec';
13
- import { Sandbox } from '@vercel/sandbox';
14
- import type { VercelRequest, VercelResponse } from '@vercel/node';
15
- import ms from 'ms';
16
-
17
- export const config = {
18
- runtime: 'nodejs',
19
- maxDuration: 300, // 5 minutes for full execution
20
- };
21
-
22
- interface ExecuteRequestBody {
23
- plan: BuildPlan;
24
- }
25
-
26
- /**
27
- * Map package manager to install command
28
- */
29
- function getInstallCommand(pm: string): { cmd: string; args: string[] } {
30
- switch (pm) {
31
- case 'bun':
32
- return { cmd: 'bun', args: ['install', '--ignore-scripts'] };
33
- case 'pnpm':
34
- return { cmd: 'pnpm', args: ['install', '--ignore-scripts'] };
35
- case 'yarn':
36
- return { cmd: 'yarn', args: ['install', '--ignore-scripts'] };
37
- default:
38
- return { cmd: 'npm', args: ['install', '--ignore-scripts', '--legacy-peer-deps'] };
39
- }
40
- }
41
-
42
- /**
43
- * Map runtime to Vercel sandbox runtime
44
- */
45
- function getSandboxRuntime(runtime: string): 'node22' | 'node24' {
46
- if (runtime === 'node24') return 'node24';
47
- return 'node22'; // Default to node22 for everything else
48
- }
49
-
50
- export default async function handler(req: VercelRequest, res: VercelResponse) {
51
- // CORS
52
- res.setHeader('Access-Control-Allow-Origin', '*');
53
- res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
54
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
55
-
56
- if (req.method === 'OPTIONS') {
57
- return res.status(200).end();
58
- }
59
-
60
- if (req.method !== 'POST') {
61
- return res.status(405).json({ error: 'Method not allowed' });
62
- }
63
-
64
- const body = req.body as ExecuteRequestBody;
65
-
66
- if (!body.plan) {
67
- return res.status(400).json({ error: 'plan is required' });
68
- }
69
-
70
- const { plan } = body;
71
-
72
- // Validate plan structure
73
- if (!plan.target?.repoUrl || !plan.steps) {
74
- return res.status(400).json({ error: 'Invalid plan structure' });
75
- }
76
-
77
- const startTime = Date.now();
78
- const stepResults: BuildPlanStepResult[] = [];
79
- let sandbox: Awaited<ReturnType<typeof Sandbox.create>> | null = null;
80
-
81
- try {
82
- // Create sandbox with git source
83
- sandbox = await Sandbox.create({
84
- source: {
85
- url: plan.target.repoUrl,
86
- type: 'git',
87
- },
88
- resources: { vcpus: 4 },
89
- timeout: ms('5m'),
90
- runtime: getSandboxRuntime(plan.environment.runtime),
91
- });
92
-
93
- // Execute each step
94
- for (const step of plan.steps) {
95
- const stepStart = Date.now();
96
-
97
- try {
98
- const result = await sandbox.runCommand({
99
- cmd: step.command,
100
- args: step.args,
101
- cwd: step.cwd,
102
- timeout: step.timeout ?? 60000,
103
- });
104
-
105
- // stdout/stderr are async functions in Vercel Sandbox
106
- const stdout = result.stdout ? await result.stdout() : '';
107
- const stderr = result.stderr ? await result.stderr() : '';
108
-
109
- stepResults.push({
110
- stepId: step.id,
111
- success: result.exitCode === 0,
112
- duration: Date.now() - stepStart,
113
- output: stdout.slice(0, 5000), // Truncate output
114
- error: result.exitCode !== 0 ? stderr.slice(0, 2000) : undefined,
115
- });
116
-
117
- // Stop on non-optional failures
118
- if (result.exitCode !== 0 && !step.optional) {
119
- throw new Error(`Step '${step.name}' failed: ${stderr}`);
120
- }
121
- } catch (stepError) {
122
- stepResults.push({
123
- stepId: step.id,
124
- success: false,
125
- duration: Date.now() - stepStart,
126
- error: stepError instanceof Error ? stepError.message : 'Unknown error',
127
- });
128
-
129
- if (!step.optional) {
130
- throw stepError;
131
- }
132
- }
133
- }
134
-
135
- // Run doccov spec to generate the OpenPkg spec
136
- const specStart = Date.now();
137
- const entryPoint = plan.target.entryPoints[0] ?? 'src/index.ts';
138
-
139
- // Install doccov CLI first
140
- await sandbox.runCommand({
141
- cmd: 'npm',
142
- args: ['install', '-g', '@doccov/cli@latest'],
143
- });
144
-
145
- // Run spec generation
146
- const specResult = await sandbox.runCommand({
147
- cmd: 'doccov',
148
- args: ['spec', entryPoint, '--output', 'openpkg.json'],
149
- cwd: plan.target.rootPath,
150
- });
151
-
152
- const specStdout = specResult.stdout ? await specResult.stdout() : '';
153
- const specStderr = specResult.stderr ? await specResult.stderr() : '';
154
-
155
- stepResults.push({
156
- stepId: 'analyze',
157
- success: specResult.exitCode === 0,
158
- duration: Date.now() - specStart,
159
- output: specStdout.slice(0, 2000),
160
- error: specResult.exitCode !== 0 ? specStderr.slice(0, 2000) : undefined,
161
- });
162
-
163
- if (specResult.exitCode !== 0) {
164
- throw new Error(`Spec generation failed: ${specStderr}`);
165
- }
166
-
167
- // Read the generated spec - readFile returns a readable stream
168
- const specStream = await sandbox.readFile({ path: 'openpkg.json' });
169
- const chunks: Buffer[] = [];
170
- for await (const chunk of specStream as AsyncIterable<Buffer>) {
171
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
172
- }
173
- const specContent = Buffer.concat(chunks).toString('utf-8');
174
- const spec = JSON.parse(specContent) as OpenPkg;
175
-
176
- const result: BuildPlanExecutionResult = {
177
- success: true,
178
- spec,
179
- stepResults,
180
- totalDuration: Date.now() - startTime,
181
- };
182
-
183
- return res.status(200).json(result);
184
- } catch (error) {
185
- console.error('Execution error:', error);
186
-
187
- const result: BuildPlanExecutionResult = {
188
- success: false,
189
- stepResults,
190
- totalDuration: Date.now() - startTime,
191
- error: error instanceof Error ? error.message : 'Unknown error',
192
- };
193
-
194
- return res.status(200).json(result); // Return 200 with success: false
195
- } finally {
196
- if (sandbox) {
197
- try {
198
- await sandbox.stop();
199
- } catch {
200
- // Ignore cleanup errors
201
- }
202
- }
203
- }
204
- }