@doccov/api 0.4.0 → 0.6.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/CHANGELOG.md +19 -0
- package/api/index.ts +105 -26
- package/migrations/005_coverage_sdk_field_names.ts +41 -0
- package/package.json +4 -4
- package/src/index.ts +36 -2
- package/src/middleware/anonymous-rate-limit.ts +131 -0
- package/src/routes/ai.ts +353 -0
- package/src/routes/badge.ts +122 -32
- package/src/routes/billing.ts +65 -0
- package/src/routes/coverage.ts +53 -48
- package/src/routes/demo.ts +606 -0
- package/src/routes/github-app.ts +368 -0
- package/src/routes/invites.ts +90 -0
- package/src/routes/orgs.ts +249 -0
- package/src/routes/spec-v1.ts +165 -0
- package/src/routes/spec.ts +186 -0
- package/src/utils/github-app.ts +196 -0
- package/src/utils/github-checks.ts +498 -0
- package/src/utils/remote-analyzer.ts +251 -0
- package/src/utils/spec-cache.ts +131 -0
- package/src/utils/spec-diff-core.ts +406 -0
- package/src/utils/github.ts +0 -5
package/src/routes/coverage.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getPlanLimits, type Plan } from '@doccov/db';
|
|
1
2
|
import { Hono } from 'hono';
|
|
2
3
|
import { nanoid } from 'nanoid';
|
|
3
4
|
import { auth } from '../auth/config';
|
|
@@ -29,33 +30,50 @@ coverageRoute.get('/projects/:projectId/history', async (c) => {
|
|
|
29
30
|
const { projectId } = c.req.param();
|
|
30
31
|
const { range = '30d', limit = '50' } = c.req.query();
|
|
31
32
|
|
|
32
|
-
// Verify user has access to project
|
|
33
|
-
const
|
|
33
|
+
// Verify user has access to project and get org plan
|
|
34
|
+
const projectWithOrg = await db
|
|
34
35
|
.selectFrom('projects')
|
|
35
36
|
.innerJoin('org_members', 'org_members.orgId', 'projects.orgId')
|
|
37
|
+
.innerJoin('organizations', 'organizations.id', 'projects.orgId')
|
|
36
38
|
.where('projects.id', '=', projectId)
|
|
37
39
|
.where('org_members.userId', '=', session.user.id)
|
|
38
|
-
.select(['projects.id', 'projects.name'])
|
|
40
|
+
.select(['projects.id', 'projects.name', 'organizations.plan'])
|
|
39
41
|
.executeTakeFirst();
|
|
40
42
|
|
|
41
|
-
if (!
|
|
43
|
+
if (!projectWithOrg) {
|
|
42
44
|
return c.json({ error: 'Project not found' }, 404);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
//
|
|
47
|
+
// Check plan limits for trends access
|
|
48
|
+
const planLimits = getPlanLimits(projectWithOrg.plan as Plan);
|
|
49
|
+
if (planLimits.historyDays === 0) {
|
|
50
|
+
return c.json(
|
|
51
|
+
{
|
|
52
|
+
error: 'Coverage trends require Team plan or higher',
|
|
53
|
+
upgrade: 'https://doccov.com/pricing',
|
|
54
|
+
},
|
|
55
|
+
403,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Calculate date filter based on range (capped by plan limit)
|
|
46
60
|
let dateFilter: Date | null = null;
|
|
47
61
|
const now = new Date();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
const maxDays = planLimits.historyDays;
|
|
63
|
+
|
|
64
|
+
// Map range to days, capped by plan limit
|
|
65
|
+
const rangeDays: Record<string, number> = {
|
|
66
|
+
'7d': Math.min(7, maxDays),
|
|
67
|
+
'30d': Math.min(30, maxDays),
|
|
68
|
+
'90d': Math.min(90, maxDays),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (range in rangeDays) {
|
|
72
|
+
const days = rangeDays[range];
|
|
73
|
+
dateFilter = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
|
74
|
+
} else if (range === 'all' || range === 'versions') {
|
|
75
|
+
// Still cap by plan limit
|
|
76
|
+
dateFilter = new Date(now.getTime() - maxDays * 24 * 60 * 60 * 1000);
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
let query = db
|
|
@@ -74,13 +92,9 @@ coverageRoute.get('/projects/:projectId/history', async (c) => {
|
|
|
74
92
|
'version',
|
|
75
93
|
'branch',
|
|
76
94
|
'commitSha',
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'descriptionCount',
|
|
81
|
-
'paramsCount',
|
|
82
|
-
'returnsCount',
|
|
83
|
-
'examplesCount',
|
|
95
|
+
'coverageScore',
|
|
96
|
+
'documentedExports',
|
|
97
|
+
'totalExports',
|
|
84
98
|
'driftCount',
|
|
85
99
|
'source',
|
|
86
100
|
'createdAt',
|
|
@@ -111,13 +125,9 @@ coverageRoute.post('/projects/:projectId/snapshots', async (c) => {
|
|
|
111
125
|
version?: string;
|
|
112
126
|
branch?: string;
|
|
113
127
|
commitSha?: string;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
descriptionCount?: number;
|
|
118
|
-
paramsCount?: number;
|
|
119
|
-
returnsCount?: number;
|
|
120
|
-
examplesCount?: number;
|
|
128
|
+
coverageScore: number;
|
|
129
|
+
documentedExports: number;
|
|
130
|
+
totalExports: number;
|
|
121
131
|
driftCount?: number;
|
|
122
132
|
source?: 'ci' | 'manual' | 'scheduled';
|
|
123
133
|
}>();
|
|
@@ -144,13 +154,9 @@ coverageRoute.post('/projects/:projectId/snapshots', async (c) => {
|
|
|
144
154
|
version: body.version || null,
|
|
145
155
|
branch: body.branch || null,
|
|
146
156
|
commitSha: body.commitSha || null,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
descriptionCount: body.descriptionCount || null,
|
|
151
|
-
paramsCount: body.paramsCount || null,
|
|
152
|
-
returnsCount: body.returnsCount || null,
|
|
153
|
-
examplesCount: body.examplesCount || null,
|
|
157
|
+
coverageScore: body.coverageScore,
|
|
158
|
+
documentedExports: body.documentedExports,
|
|
159
|
+
totalExports: body.totalExports,
|
|
154
160
|
driftCount: body.driftCount || 0,
|
|
155
161
|
source: body.source || 'manual',
|
|
156
162
|
})
|
|
@@ -161,7 +167,7 @@ coverageRoute.post('/projects/:projectId/snapshots', async (c) => {
|
|
|
161
167
|
await db
|
|
162
168
|
.updateTable('projects')
|
|
163
169
|
.set({
|
|
164
|
-
coverageScore: body.
|
|
170
|
+
coverageScore: body.coverageScore,
|
|
165
171
|
driftCount: body.driftCount || 0,
|
|
166
172
|
lastAnalyzedAt: new Date(),
|
|
167
173
|
})
|
|
@@ -174,9 +180,9 @@ coverageRoute.post('/projects/:projectId/snapshots', async (c) => {
|
|
|
174
180
|
// Helper: Generate insights from coverage data
|
|
175
181
|
interface Snapshot {
|
|
176
182
|
version: string | null;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
coverageScore: number;
|
|
184
|
+
documentedExports: number;
|
|
185
|
+
totalExports: number;
|
|
180
186
|
driftCount: number;
|
|
181
187
|
}
|
|
182
188
|
|
|
@@ -192,7 +198,7 @@ function generateInsights(snapshots: Snapshot[]): Insight[] {
|
|
|
192
198
|
|
|
193
199
|
const first = snapshots[0];
|
|
194
200
|
const last = snapshots[snapshots.length - 1];
|
|
195
|
-
const diff = last.
|
|
201
|
+
const diff = last.coverageScore - first.coverageScore;
|
|
196
202
|
|
|
197
203
|
// Overall improvement/regression
|
|
198
204
|
if (diff > 0) {
|
|
@@ -210,8 +216,8 @@ function generateInsights(snapshots: Snapshot[]): Insight[] {
|
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
// Predict time to 100%
|
|
213
|
-
if (diff > 0 && last.
|
|
214
|
-
const remaining = 100 - last.
|
|
219
|
+
if (diff > 0 && last.coverageScore < 100) {
|
|
220
|
+
const remaining = 100 - last.coverageScore;
|
|
215
221
|
const avgGainPerSnapshot = diff / (snapshots.length - 1);
|
|
216
222
|
if (avgGainPerSnapshot > 0) {
|
|
217
223
|
const snapshotsToComplete = Math.ceil(remaining / avgGainPerSnapshot);
|
|
@@ -227,8 +233,7 @@ function generateInsights(snapshots: Snapshot[]): Insight[] {
|
|
|
227
233
|
const milestones = [50, 75, 90, 100];
|
|
228
234
|
for (const milestone of milestones) {
|
|
229
235
|
const crossedAt = snapshots.findIndex(
|
|
230
|
-
(s, i) =>
|
|
231
|
-
i > 0 && s.coveragePercent >= milestone && snapshots[i - 1].coveragePercent < milestone,
|
|
236
|
+
(s, i) => i > 0 && s.coverageScore >= milestone && snapshots[i - 1].coverageScore < milestone,
|
|
232
237
|
);
|
|
233
238
|
if (crossedAt > 0) {
|
|
234
239
|
insights.push({
|
|
@@ -253,7 +258,7 @@ function detectRegression(
|
|
|
253
258
|
for (let i = 1; i < recent.length; i++) {
|
|
254
259
|
const prev = recent[i - 1];
|
|
255
260
|
const curr = recent[i];
|
|
256
|
-
const drop = prev.
|
|
261
|
+
const drop = prev.coverageScore - curr.coverageScore;
|
|
257
262
|
|
|
258
263
|
if (drop >= 3) {
|
|
259
264
|
// 3% or more drop
|
|
@@ -261,7 +266,7 @@ function detectRegression(
|
|
|
261
266
|
fromVersion: prev.version || `v${i}`,
|
|
262
267
|
toVersion: curr.version || `v${i + 1}`,
|
|
263
268
|
coverageDrop: Math.round(drop),
|
|
264
|
-
exportsLost: prev.
|
|
269
|
+
exportsLost: prev.documentedExports - curr.documentedExports,
|
|
265
270
|
};
|
|
266
271
|
}
|
|
267
272
|
}
|