@doccov/api 0.3.4 → 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/.vercelignore +2 -0
- package/CHANGELOG.md +28 -0
- package/api/index.ts +894 -83
- package/package.json +8 -7
- package/src/index.ts +9 -7
- package/src/routes/plan.ts +81 -0
- package/tsconfig.json +2 -2
- package/vercel.json +3 -5
- package/api/scan/detect.ts +0 -118
- package/api/scan-stream.ts +0 -460
- package/api/scan.ts +0 -63
- package/src/routes/scan.ts +0 -240
- package/src/sandbox-runner.ts +0 -82
- package/src/scan-worker.ts +0 -122
- package/src/stores/job-store.interface.ts +0 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doccov/api",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "DocCov API - Badge endpoint and coverage services",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"doccov",
|
|
@@ -18,18 +18,19 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"author": "Ryan Waits",
|
|
20
20
|
"type": "module",
|
|
21
|
-
"main": "./src/index.ts",
|
|
22
21
|
"scripts": {
|
|
23
22
|
"dev": "bun run --hot src/index.ts",
|
|
24
23
|
"start": "bun run src/index.ts",
|
|
25
|
-
"lint": "biome check src/",
|
|
26
|
-
"lint:fix": "biome check --write src/",
|
|
27
|
-
"format": "biome format --write src/"
|
|
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",
|
|
30
|
+
"@doccov/sdk": "^0.13.0",
|
|
31
|
+
"@openpkg-ts/spec": "^0.9.0",
|
|
31
32
|
"@vercel/sandbox": "^1.0.3",
|
|
32
|
-
"
|
|
33
|
+
"ai": "^5.0.111",
|
|
33
34
|
"ms": "^2.1.3",
|
|
34
35
|
"zod": "^3.25.0"
|
|
35
36
|
},
|
package/src/index.ts
CHANGED
|
@@ -2,20 +2,20 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { cors } from 'hono/cors';
|
|
3
3
|
import { rateLimit } from './middleware/rate-limit';
|
|
4
4
|
import { badgeRoute } from './routes/badge';
|
|
5
|
-
import {
|
|
5
|
+
import { planRoute } from './routes/plan';
|
|
6
6
|
|
|
7
7
|
const app = new Hono();
|
|
8
8
|
|
|
9
9
|
// Middleware
|
|
10
10
|
app.use('*', cors());
|
|
11
11
|
|
|
12
|
-
// Rate limit /
|
|
12
|
+
// Rate limit /plan endpoint: 10 requests per minute per IP
|
|
13
13
|
app.use(
|
|
14
|
-
'/
|
|
14
|
+
'/plan',
|
|
15
15
|
rateLimit({
|
|
16
16
|
windowMs: 60 * 1000,
|
|
17
17
|
max: 10,
|
|
18
|
-
message: 'Too many
|
|
18
|
+
message: 'Too many plan requests. Please try again in a minute.',
|
|
19
19
|
}),
|
|
20
20
|
);
|
|
21
21
|
|
|
@@ -23,10 +23,12 @@ app.use(
|
|
|
23
23
|
app.get('/', (c) => {
|
|
24
24
|
return c.json({
|
|
25
25
|
name: 'DocCov API',
|
|
26
|
-
version: '0.
|
|
26
|
+
version: '0.4.0',
|
|
27
27
|
endpoints: {
|
|
28
28
|
badge: '/badge/:owner/:repo',
|
|
29
|
-
|
|
29
|
+
plan: '/plan',
|
|
30
|
+
execute: '/execute',
|
|
31
|
+
'execute-stream': '/execute-stream',
|
|
30
32
|
health: '/health',
|
|
31
33
|
},
|
|
32
34
|
});
|
|
@@ -38,7 +40,7 @@ app.get('/health', (c) => {
|
|
|
38
40
|
|
|
39
41
|
// Routes
|
|
40
42
|
app.route('/badge', badgeRoute);
|
|
41
|
-
app.route('/
|
|
43
|
+
app.route('/plan', planRoute);
|
|
42
44
|
|
|
43
45
|
// Vercel serverless handler + Bun auto-serves this export
|
|
44
46
|
export default app;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan route for local development (mirrors api/plan.ts)
|
|
3
|
+
* Does NOT use Vercel Sandbox - only GitHub API + Anthropic Claude
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { fetchGitHubContext, parseScanGitHubUrl } from '@doccov/sdk';
|
|
7
|
+
import { Hono } from 'hono';
|
|
8
|
+
import { generateBuildPlan } from '../../lib/plan-agent';
|
|
9
|
+
|
|
10
|
+
export const planRoute = new Hono();
|
|
11
|
+
|
|
12
|
+
planRoute.post('/', async (c) => {
|
|
13
|
+
const body = await c.req.json<{ url: string; ref?: string; package?: string }>();
|
|
14
|
+
|
|
15
|
+
if (!body.url) {
|
|
16
|
+
return c.json({ error: 'url is required' }, 400);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Validate URL format
|
|
20
|
+
let repoUrl: string;
|
|
21
|
+
try {
|
|
22
|
+
const parsed = parseScanGitHubUrl(body.url);
|
|
23
|
+
if (!parsed) {
|
|
24
|
+
return c.json({ error: 'Invalid GitHub URL' }, 400);
|
|
25
|
+
}
|
|
26
|
+
repoUrl = `https://github.com/${parsed.owner}/${parsed.repo}`;
|
|
27
|
+
} catch {
|
|
28
|
+
return c.json({ error: 'Invalid GitHub URL' }, 400);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Fetch project context from GitHub
|
|
33
|
+
const context = await fetchGitHubContext(repoUrl, body.ref);
|
|
34
|
+
|
|
35
|
+
// Check for private repos
|
|
36
|
+
if (context.metadata.isPrivate) {
|
|
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
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Generate build plan using AI
|
|
47
|
+
const plan = await generateBuildPlan(context, {
|
|
48
|
+
targetPackage: body.package,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return c.json({
|
|
52
|
+
plan,
|
|
53
|
+
context: {
|
|
54
|
+
owner: context.metadata.owner,
|
|
55
|
+
repo: context.metadata.repo,
|
|
56
|
+
ref: context.ref,
|
|
57
|
+
packageManager: context.packageManager,
|
|
58
|
+
isMonorepo: context.workspace.isMonorepo,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Plan generation error:', error);
|
|
63
|
+
|
|
64
|
+
if (error instanceof Error) {
|
|
65
|
+
if (error.message.includes('404') || error.message.includes('not found')) {
|
|
66
|
+
return c.json({ error: 'Repository not found' }, 404);
|
|
67
|
+
}
|
|
68
|
+
if (error.message.includes('rate limit')) {
|
|
69
|
+
return c.json({ error: 'GitHub API rate limit exceeded' }, 429);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
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
|
+
);
|
|
80
|
+
}
|
|
81
|
+
});
|
package/tsconfig.json
CHANGED
package/vercel.json
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
+
"framework": null,
|
|
3
|
+
"installCommand": "bun install",
|
|
4
|
+
"outputDirectory": ".",
|
|
2
5
|
"rewrites": [
|
|
3
|
-
{ "source": "/badge/:path*", "destination": "/api" },
|
|
4
|
-
{ "source": "/spec/:path*", "destination": "/api" },
|
|
5
|
-
{ "source": "/scan-stream", "destination": "/api/scan-stream" },
|
|
6
|
-
{ "source": "/scan/detect", "destination": "/api/scan/detect" },
|
|
7
|
-
{ "source": "/scan", "destination": "/api/scan" },
|
|
8
6
|
{ "source": "/(.*)", "destination": "/api" }
|
|
9
7
|
]
|
|
10
8
|
}
|
package/api/scan/detect.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Detect endpoint - uses SDK detection via SandboxFileSystem.
|
|
3
|
-
* Detects monorepo structure and package manager for a GitHub repository.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
detectPackageManager,
|
|
8
|
-
SandboxFileSystem,
|
|
9
|
-
detectMonorepo as sdkDetectMonorepo,
|
|
10
|
-
} from '@doccov/sdk';
|
|
11
|
-
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
|
12
|
-
import { Sandbox } from '@vercel/sandbox';
|
|
13
|
-
|
|
14
|
-
export const config = {
|
|
15
|
-
runtime: 'nodejs',
|
|
16
|
-
maxDuration: 60, // Quick detection, 1 minute max
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
interface DetectRequestBody {
|
|
20
|
-
url: string;
|
|
21
|
-
ref?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface PackageInfo {
|
|
25
|
-
name: string;
|
|
26
|
-
path: string;
|
|
27
|
-
description?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface DetectResponse {
|
|
31
|
-
isMonorepo: boolean;
|
|
32
|
-
packageManager: 'npm' | 'pnpm' | 'bun' | 'yarn';
|
|
33
|
-
packages?: PackageInfo[];
|
|
34
|
-
defaultPackage?: string;
|
|
35
|
-
error?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|
39
|
-
// CORS
|
|
40
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
41
|
-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
42
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
43
|
-
|
|
44
|
-
if (req.method === 'OPTIONS') {
|
|
45
|
-
return res.status(200).end();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (req.method !== 'POST') {
|
|
49
|
-
return res.status(405).json({ error: 'Method not allowed' });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const body = req.body as DetectRequestBody;
|
|
53
|
-
|
|
54
|
-
if (!body.url) {
|
|
55
|
-
return res.status(400).json({ error: 'url is required' });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const result = await detectRepoStructure(body.url);
|
|
60
|
-
return res.status(200).json(result);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
-
return res.status(500).json({
|
|
64
|
-
isMonorepo: false,
|
|
65
|
-
packageManager: 'npm',
|
|
66
|
-
error: message,
|
|
67
|
-
} as DetectResponse);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Detect repository structure using SDK utilities via SandboxFileSystem.
|
|
73
|
-
*/
|
|
74
|
-
async function detectRepoStructure(url: string): Promise<DetectResponse> {
|
|
75
|
-
const sandbox = await Sandbox.create({
|
|
76
|
-
source: {
|
|
77
|
-
url,
|
|
78
|
-
type: 'git',
|
|
79
|
-
},
|
|
80
|
-
resources: { vcpus: 2 },
|
|
81
|
-
timeout: 60 * 1000, // 1 minute
|
|
82
|
-
runtime: 'node22',
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
// Create SDK FileSystem abstraction for sandbox
|
|
87
|
-
const fs = new SandboxFileSystem(sandbox);
|
|
88
|
-
|
|
89
|
-
// Use SDK detection functions
|
|
90
|
-
const [monoInfo, pmInfo] = await Promise.all([sdkDetectMonorepo(fs), detectPackageManager(fs)]);
|
|
91
|
-
|
|
92
|
-
if (!monoInfo.isMonorepo) {
|
|
93
|
-
return {
|
|
94
|
-
isMonorepo: false,
|
|
95
|
-
packageManager: pmInfo.name,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Map SDK package info to API response format
|
|
100
|
-
const packages: PackageInfo[] = monoInfo.packages
|
|
101
|
-
.filter((p) => !p.private)
|
|
102
|
-
.map((p) => ({
|
|
103
|
-
name: p.name,
|
|
104
|
-
path: p.path,
|
|
105
|
-
description: p.description,
|
|
106
|
-
}))
|
|
107
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
isMonorepo: true,
|
|
111
|
-
packageManager: pmInfo.name,
|
|
112
|
-
packages,
|
|
113
|
-
defaultPackage: packages[0]?.name,
|
|
114
|
-
};
|
|
115
|
-
} finally {
|
|
116
|
-
await sandbox.stop();
|
|
117
|
-
}
|
|
118
|
-
}
|