git-daemon 0.1.9 → 0.1.11
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 +31 -0
- package/config.schema.json +24 -0
- package/design.md +156 -2
- package/dist/app.js +221 -4
- package/dist/approvals.js +4 -1
- package/dist/config.js +29 -1
- package/dist/context.js +3 -0
- package/dist/git.js +117 -8
- package/dist/healthchecks.js +425 -0
- package/dist/jobs.js +1 -0
- package/dist/validation.js +20 -1
- package/healthchecks/example-suite/README.md +10 -0
- package/healthchecks/example-suite/about-description/healthcheck.json +17 -0
- package/healthchecks/example-suite/about-description/run.js +241 -0
- package/healthchecks/example-suite/package-manager/healthcheck.json +17 -0
- package/healthchecks/example-suite/package-manager/run.js +106 -0
- package/healthchecks/example-suite/suite.json +6 -0
- package/openapi.yaml +277 -0
- package/package.json +3 -1
- package/src/app.ts +111 -0
- package/src/config.ts +35 -1
- package/src/context.ts +3 -0
- package/src/daemon.ts +4 -0
- package/src/healthchecks.ts +620 -0
- package/src/jobs.ts +2 -0
- package/src/types.ts +6 -1
- package/src/validation.ts +14 -0
- package/tests/app.test.ts +177 -20
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const start = Date.now();
|
|
8
|
+
|
|
9
|
+
const emit = ({
|
|
10
|
+
status,
|
|
11
|
+
summary,
|
|
12
|
+
explanation,
|
|
13
|
+
details = [],
|
|
14
|
+
metrics = {},
|
|
15
|
+
artifacts = [],
|
|
16
|
+
}) => {
|
|
17
|
+
const payload = {
|
|
18
|
+
status,
|
|
19
|
+
summary,
|
|
20
|
+
explanation,
|
|
21
|
+
details,
|
|
22
|
+
metrics,
|
|
23
|
+
artifacts,
|
|
24
|
+
durationMs: Date.now() - start,
|
|
25
|
+
};
|
|
26
|
+
process.stdout.write(JSON.stringify(payload));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const parseJsonEnv = (name) => {
|
|
30
|
+
const raw = process.env[name];
|
|
31
|
+
if (!raw) {
|
|
32
|
+
return { value: null };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return { value: JSON.parse(raw) };
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return { error: err instanceof Error ? err.message : "Invalid JSON" };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const findReadme = (repoPath) => {
|
|
42
|
+
const candidates = [
|
|
43
|
+
"README.md",
|
|
44
|
+
"README.MD",
|
|
45
|
+
"README.markdown",
|
|
46
|
+
"README.txt",
|
|
47
|
+
];
|
|
48
|
+
for (const name of candidates) {
|
|
49
|
+
const target = path.join(repoPath, name);
|
|
50
|
+
if (fs.existsSync(target)) {
|
|
51
|
+
return target;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const isBadgeLine = (line) =>
|
|
58
|
+
/^\s*\[!\[.*\]\(.*\)\]\(.*\)\s*$/.test(line) ||
|
|
59
|
+
/^\s*!\[.*\]\(.*\)\s*$/.test(line) ||
|
|
60
|
+
/^\s*<img\b/i.test(line);
|
|
61
|
+
|
|
62
|
+
const stripMarkdown = (text) =>
|
|
63
|
+
text
|
|
64
|
+
.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1")
|
|
65
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
66
|
+
.replace(/`([^`]*)`/g, "$1")
|
|
67
|
+
.replace(/[*_~]/g, "")
|
|
68
|
+
.replace(/<[^>]+>/g, "");
|
|
69
|
+
|
|
70
|
+
const normalize = (text) =>
|
|
71
|
+
stripMarkdown(text)
|
|
72
|
+
.replace(/\s+/g, " ")
|
|
73
|
+
.trim()
|
|
74
|
+
.replace(/[.!?]+$/, "")
|
|
75
|
+
.toLowerCase();
|
|
76
|
+
|
|
77
|
+
const extractFirstSentence = (readme) => {
|
|
78
|
+
const lines = readme.split(/\r?\n/);
|
|
79
|
+
const paragraph = [];
|
|
80
|
+
let inCodeBlock = false;
|
|
81
|
+
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
if (trimmed.startsWith("```")) {
|
|
85
|
+
inCodeBlock = !inCodeBlock;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (inCodeBlock) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!trimmed) {
|
|
92
|
+
if (paragraph.length > 0) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (trimmed.startsWith("#") || trimmed.startsWith(">")) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (isBadgeLine(trimmed)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (trimmed.startsWith("<!--")) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
paragraph.push(trimmed);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (paragraph.length === 0) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const text = paragraph.join(" ");
|
|
113
|
+
const match = text.match(/^(.+?[.!?])(?:\s|$)/);
|
|
114
|
+
return match ? match[1] : text;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const repoPath = process.env.REPO_PATH;
|
|
118
|
+
if (!repoPath) {
|
|
119
|
+
emit({
|
|
120
|
+
status: "failed",
|
|
121
|
+
summary: "Missing repo path",
|
|
122
|
+
explanation: "REPO_PATH environment variable is required.",
|
|
123
|
+
details: [
|
|
124
|
+
{
|
|
125
|
+
code: "MISSING_REPO_PATH",
|
|
126
|
+
severity: "high",
|
|
127
|
+
message: "REPO_PATH environment variable was not provided.",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const repoInfo = parseJsonEnv("REPO_INFO");
|
|
135
|
+
if (repoInfo.error) {
|
|
136
|
+
emit({
|
|
137
|
+
status: "failed",
|
|
138
|
+
summary: "Invalid repo info",
|
|
139
|
+
explanation: "REPO_INFO must be valid JSON.",
|
|
140
|
+
details: [
|
|
141
|
+
{
|
|
142
|
+
code: "INVALID_REPO_INFO",
|
|
143
|
+
severity: "high",
|
|
144
|
+
message: repoInfo.error,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const description =
|
|
152
|
+
repoInfo.value && typeof repoInfo.value.description === "string"
|
|
153
|
+
? repoInfo.value.description.trim()
|
|
154
|
+
: "";
|
|
155
|
+
|
|
156
|
+
if (!description) {
|
|
157
|
+
emit({
|
|
158
|
+
status: "na",
|
|
159
|
+
summary: "Missing repo description",
|
|
160
|
+
explanation:
|
|
161
|
+
"REPO_INFO.description is missing, so there is nothing to compare.",
|
|
162
|
+
});
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const readmePath = findReadme(repoPath);
|
|
167
|
+
if (!readmePath) {
|
|
168
|
+
emit({
|
|
169
|
+
status: "na",
|
|
170
|
+
summary: "README not found",
|
|
171
|
+
explanation: "No README file was found in the repo root.",
|
|
172
|
+
});
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let readmeText = "";
|
|
177
|
+
try {
|
|
178
|
+
readmeText = fs.readFileSync(readmePath, "utf8");
|
|
179
|
+
} catch (err) {
|
|
180
|
+
emit({
|
|
181
|
+
status: "failed",
|
|
182
|
+
summary: "Failed to read README",
|
|
183
|
+
explanation: "Unable to read README file.",
|
|
184
|
+
details: [
|
|
185
|
+
{
|
|
186
|
+
code: "README_READ_FAILED",
|
|
187
|
+
severity: "high",
|
|
188
|
+
message: err instanceof Error ? err.message : "Read failed.",
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const firstSentence = extractFirstSentence(readmeText);
|
|
196
|
+
if (!firstSentence) {
|
|
197
|
+
emit({
|
|
198
|
+
status: "na",
|
|
199
|
+
summary: "README has no readable paragraph",
|
|
200
|
+
explanation:
|
|
201
|
+
"Could not locate non-title text to compare against the description.",
|
|
202
|
+
});
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const normalizedDescription = normalize(description);
|
|
207
|
+
const normalizedSentence = normalize(firstSentence);
|
|
208
|
+
|
|
209
|
+
if (normalizedDescription === normalizedSentence) {
|
|
210
|
+
emit({
|
|
211
|
+
status: "pass-full",
|
|
212
|
+
summary: "Description matches README",
|
|
213
|
+
explanation:
|
|
214
|
+
"The GitHub repo description matches the first README sentence.",
|
|
215
|
+
});
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
emit({
|
|
220
|
+
status: "failed",
|
|
221
|
+
summary: "Description does not match README",
|
|
222
|
+
explanation:
|
|
223
|
+
"The GitHub repo description does not match the first README sentence.",
|
|
224
|
+
details: [
|
|
225
|
+
{
|
|
226
|
+
code: "ABOUT_MISMATCH",
|
|
227
|
+
severity: "medium",
|
|
228
|
+
message: `README sentence: "${firstSentence}"`,
|
|
229
|
+
remediation: `Update the GitHub description to match: "${firstSentence}"`,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
code: "ABOUT_MISMATCH",
|
|
233
|
+
severity: "medium",
|
|
234
|
+
message: `Repo description: "${description}"`,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
metrics: {
|
|
238
|
+
readmeSentence: firstSentence,
|
|
239
|
+
description,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "package-manager",
|
|
3
|
+
"name": "packageManager is defined",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Ensures package.json declares a package manager.",
|
|
6
|
+
"entrypoint": "./run.js",
|
|
7
|
+
"args": [],
|
|
8
|
+
"timeoutSeconds": 30,
|
|
9
|
+
"permissions": {
|
|
10
|
+
"repoRead": true,
|
|
11
|
+
"network": false,
|
|
12
|
+
"secrets": []
|
|
13
|
+
},
|
|
14
|
+
"cacheable": true,
|
|
15
|
+
"cacheMaxAgeSeconds": 3600,
|
|
16
|
+
"outputSchemaVersion": 1
|
|
17
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const start = Date.now();
|
|
8
|
+
|
|
9
|
+
const emit = ({
|
|
10
|
+
status,
|
|
11
|
+
summary,
|
|
12
|
+
explanation,
|
|
13
|
+
details = [],
|
|
14
|
+
metrics = {},
|
|
15
|
+
artifacts = [],
|
|
16
|
+
}) => {
|
|
17
|
+
const payload = {
|
|
18
|
+
status,
|
|
19
|
+
summary,
|
|
20
|
+
explanation,
|
|
21
|
+
details,
|
|
22
|
+
metrics,
|
|
23
|
+
artifacts,
|
|
24
|
+
durationMs: Date.now() - start,
|
|
25
|
+
};
|
|
26
|
+
process.stdout.write(JSON.stringify(payload));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const repoPath = process.env.REPO_PATH;
|
|
30
|
+
if (!repoPath) {
|
|
31
|
+
emit({
|
|
32
|
+
status: "failed",
|
|
33
|
+
summary: "Missing repo path",
|
|
34
|
+
explanation: "REPO_PATH environment variable is required.",
|
|
35
|
+
details: [
|
|
36
|
+
{
|
|
37
|
+
code: "MISSING_REPO_PATH",
|
|
38
|
+
severity: "high",
|
|
39
|
+
message: "REPO_PATH environment variable was not provided.",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const packageJsonPath = path.join(repoPath, "package.json");
|
|
47
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
48
|
+
emit({
|
|
49
|
+
status: "na",
|
|
50
|
+
summary: "No package.json",
|
|
51
|
+
explanation: "This repository does not use package.json.",
|
|
52
|
+
});
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
const raw = fs.readFileSync(packageJsonPath, "utf8");
|
|
59
|
+
parsed = JSON.parse(raw);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
emit({
|
|
62
|
+
status: "failed",
|
|
63
|
+
summary: "Invalid package.json",
|
|
64
|
+
explanation: "package.json could not be parsed.",
|
|
65
|
+
details: [
|
|
66
|
+
{
|
|
67
|
+
code: "PACKAGE_JSON_INVALID",
|
|
68
|
+
severity: "high",
|
|
69
|
+
message: err instanceof Error ? err.message : "Parse failed.",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const packageManager =
|
|
77
|
+
parsed && typeof parsed.packageManager === "string"
|
|
78
|
+
? parsed.packageManager.trim()
|
|
79
|
+
: "";
|
|
80
|
+
|
|
81
|
+
if (!packageManager) {
|
|
82
|
+
emit({
|
|
83
|
+
status: "failed",
|
|
84
|
+
summary: "packageManager missing",
|
|
85
|
+
explanation:
|
|
86
|
+
"Define packageManager in package.json to lock the package manager toolchain.",
|
|
87
|
+
details: [
|
|
88
|
+
{
|
|
89
|
+
code: "PACKAGE_MANAGER_MISSING",
|
|
90
|
+
severity: "medium",
|
|
91
|
+
message:
|
|
92
|
+
"Set packageManager (example: \"pnpm@8.15.4\") to enable corepack.",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
emit({
|
|
100
|
+
status: "pass-full",
|
|
101
|
+
summary: "packageManager is set",
|
|
102
|
+
explanation: `package.json declares "${packageManager}".`,
|
|
103
|
+
metrics: {
|
|
104
|
+
packageManager,
|
|
105
|
+
},
|
|
106
|
+
});
|
package/openapi.yaml
CHANGED
|
@@ -93,6 +93,8 @@ paths:
|
|
|
93
93
|
data: {"type":"log","stream":"stdout","line":"Cloning into..."}
|
|
94
94
|
|
|
95
95
|
data: {"type":"progress","kind":"git","percent":42,"detail":"receiving objects"}
|
|
96
|
+
|
|
97
|
+
data: {"type":"progress","kind":"healthcheck","detail":"lint: running"}
|
|
96
98
|
'401':
|
|
97
99
|
$ref: '#/components/responses/Unauthorized'
|
|
98
100
|
'403':
|
|
@@ -314,6 +316,77 @@ paths:
|
|
|
314
316
|
$ref: '#/components/responses/UnprocessableEntity'
|
|
315
317
|
'500':
|
|
316
318
|
$ref: '#/components/responses/InternalError'
|
|
319
|
+
/v1/healthchecks:
|
|
320
|
+
get:
|
|
321
|
+
summary: List available healthchecks
|
|
322
|
+
parameters:
|
|
323
|
+
- $ref: '#/components/parameters/OriginHeader'
|
|
324
|
+
responses:
|
|
325
|
+
'200':
|
|
326
|
+
description: Healthcheck catalog
|
|
327
|
+
content:
|
|
328
|
+
application/json:
|
|
329
|
+
schema:
|
|
330
|
+
$ref: '#/components/schemas/HealthcheckCatalogResponse'
|
|
331
|
+
'401':
|
|
332
|
+
$ref: '#/components/responses/Unauthorized'
|
|
333
|
+
'403':
|
|
334
|
+
$ref: '#/components/responses/Forbidden'
|
|
335
|
+
'500':
|
|
336
|
+
$ref: '#/components/responses/InternalError'
|
|
337
|
+
/v1/healthchecks/run:
|
|
338
|
+
post:
|
|
339
|
+
summary: Run healthchecks for a repository
|
|
340
|
+
parameters:
|
|
341
|
+
- $ref: '#/components/parameters/OriginHeader'
|
|
342
|
+
requestBody:
|
|
343
|
+
required: true
|
|
344
|
+
content:
|
|
345
|
+
application/json:
|
|
346
|
+
schema:
|
|
347
|
+
$ref: '#/components/schemas/HealthcheckRunRequest'
|
|
348
|
+
responses:
|
|
349
|
+
'202':
|
|
350
|
+
description: Job created
|
|
351
|
+
content:
|
|
352
|
+
application/json:
|
|
353
|
+
schema:
|
|
354
|
+
$ref: '#/components/schemas/JobResponse'
|
|
355
|
+
'401':
|
|
356
|
+
$ref: '#/components/responses/Unauthorized'
|
|
357
|
+
'403':
|
|
358
|
+
$ref: '#/components/responses/Forbidden'
|
|
359
|
+
'404':
|
|
360
|
+
$ref: '#/components/responses/NotFound'
|
|
361
|
+
'409':
|
|
362
|
+
$ref: '#/components/responses/Conflict'
|
|
363
|
+
'422':
|
|
364
|
+
$ref: '#/components/responses/UnprocessableEntity'
|
|
365
|
+
'500':
|
|
366
|
+
$ref: '#/components/responses/InternalError'
|
|
367
|
+
/v1/healthchecks/jobs/{id}/result:
|
|
368
|
+
get:
|
|
369
|
+
summary: Get healthcheck results for a job
|
|
370
|
+
parameters:
|
|
371
|
+
- $ref: '#/components/parameters/OriginHeader'
|
|
372
|
+
- $ref: '#/components/parameters/JobId'
|
|
373
|
+
responses:
|
|
374
|
+
'200':
|
|
375
|
+
description: Healthcheck results
|
|
376
|
+
content:
|
|
377
|
+
application/json:
|
|
378
|
+
schema:
|
|
379
|
+
$ref: '#/components/schemas/HealthcheckRunResult'
|
|
380
|
+
'401':
|
|
381
|
+
$ref: '#/components/responses/Unauthorized'
|
|
382
|
+
'403':
|
|
383
|
+
$ref: '#/components/responses/Forbidden'
|
|
384
|
+
'404':
|
|
385
|
+
$ref: '#/components/responses/NotFound'
|
|
386
|
+
'409':
|
|
387
|
+
$ref: '#/components/responses/Conflict'
|
|
388
|
+
'500':
|
|
389
|
+
$ref: '#/components/responses/InternalError'
|
|
317
390
|
/v1/diagnostics:
|
|
318
391
|
get:
|
|
319
392
|
summary: Get diagnostics bundle metadata
|
|
@@ -708,6 +781,210 @@ components:
|
|
|
708
781
|
safer:
|
|
709
782
|
type: boolean
|
|
710
783
|
default: true
|
|
784
|
+
HealthcheckCatalogResponse:
|
|
785
|
+
type: object
|
|
786
|
+
required:
|
|
787
|
+
- suites
|
|
788
|
+
properties:
|
|
789
|
+
suites:
|
|
790
|
+
type: array
|
|
791
|
+
items:
|
|
792
|
+
$ref: '#/components/schemas/HealthcheckSuite'
|
|
793
|
+
HealthcheckSuite:
|
|
794
|
+
type: object
|
|
795
|
+
required:
|
|
796
|
+
- id
|
|
797
|
+
- name
|
|
798
|
+
- checks
|
|
799
|
+
properties:
|
|
800
|
+
id:
|
|
801
|
+
type: string
|
|
802
|
+
name:
|
|
803
|
+
type: string
|
|
804
|
+
version:
|
|
805
|
+
type: string
|
|
806
|
+
description:
|
|
807
|
+
type: string
|
|
808
|
+
checks:
|
|
809
|
+
type: array
|
|
810
|
+
items:
|
|
811
|
+
$ref: '#/components/schemas/HealthcheckDefinition'
|
|
812
|
+
HealthcheckDefinition:
|
|
813
|
+
type: object
|
|
814
|
+
required:
|
|
815
|
+
- id
|
|
816
|
+
- name
|
|
817
|
+
- version
|
|
818
|
+
- description
|
|
819
|
+
- timeoutSeconds
|
|
820
|
+
- cacheable
|
|
821
|
+
properties:
|
|
822
|
+
id:
|
|
823
|
+
type: string
|
|
824
|
+
name:
|
|
825
|
+
type: string
|
|
826
|
+
version:
|
|
827
|
+
type: string
|
|
828
|
+
description:
|
|
829
|
+
type: string
|
|
830
|
+
timeoutSeconds:
|
|
831
|
+
type: integer
|
|
832
|
+
minimum: 1
|
|
833
|
+
cacheable:
|
|
834
|
+
type: boolean
|
|
835
|
+
cacheMaxAgeSeconds:
|
|
836
|
+
type: integer
|
|
837
|
+
minimum: 1
|
|
838
|
+
permissions:
|
|
839
|
+
$ref: '#/components/schemas/HealthcheckPermissions'
|
|
840
|
+
configSchema:
|
|
841
|
+
type: object
|
|
842
|
+
description: JSON schema for check config.
|
|
843
|
+
additionalProperties: true
|
|
844
|
+
outputSchemaVersion:
|
|
845
|
+
type: integer
|
|
846
|
+
HealthcheckPermissions:
|
|
847
|
+
type: object
|
|
848
|
+
properties:
|
|
849
|
+
repoRead:
|
|
850
|
+
type: boolean
|
|
851
|
+
network:
|
|
852
|
+
type: boolean
|
|
853
|
+
secrets:
|
|
854
|
+
type: array
|
|
855
|
+
items:
|
|
856
|
+
type: string
|
|
857
|
+
HealthcheckRunRequest:
|
|
858
|
+
type: object
|
|
859
|
+
required:
|
|
860
|
+
- repoPath
|
|
861
|
+
properties:
|
|
862
|
+
repoPath:
|
|
863
|
+
type: string
|
|
864
|
+
repoInfo:
|
|
865
|
+
type: object
|
|
866
|
+
description: UI-supplied repo metadata passed through to checks.
|
|
867
|
+
additionalProperties: true
|
|
868
|
+
suiteId:
|
|
869
|
+
type: string
|
|
870
|
+
checks:
|
|
871
|
+
type: array
|
|
872
|
+
items:
|
|
873
|
+
$ref: '#/components/schemas/HealthcheckSelection'
|
|
874
|
+
HealthcheckSelection:
|
|
875
|
+
type: object
|
|
876
|
+
required:
|
|
877
|
+
- checkId
|
|
878
|
+
properties:
|
|
879
|
+
suiteId:
|
|
880
|
+
type: string
|
|
881
|
+
checkId:
|
|
882
|
+
type: string
|
|
883
|
+
config:
|
|
884
|
+
type: object
|
|
885
|
+
additionalProperties: true
|
|
886
|
+
cacheMode:
|
|
887
|
+
type: string
|
|
888
|
+
enum: [prefer, refresh, bypass]
|
|
889
|
+
default: prefer
|
|
890
|
+
HealthcheckRunResult:
|
|
891
|
+
type: object
|
|
892
|
+
required:
|
|
893
|
+
- jobId
|
|
894
|
+
- repoPath
|
|
895
|
+
- status
|
|
896
|
+
- checks
|
|
897
|
+
properties:
|
|
898
|
+
jobId:
|
|
899
|
+
type: string
|
|
900
|
+
repoPath:
|
|
901
|
+
type: string
|
|
902
|
+
status:
|
|
903
|
+
$ref: '#/components/schemas/HealthcheckStatus'
|
|
904
|
+
summary:
|
|
905
|
+
type: string
|
|
906
|
+
checks:
|
|
907
|
+
type: array
|
|
908
|
+
items:
|
|
909
|
+
$ref: '#/components/schemas/HealthcheckResult'
|
|
910
|
+
startedAt:
|
|
911
|
+
type: string
|
|
912
|
+
format: date-time
|
|
913
|
+
finishedAt:
|
|
914
|
+
type: string
|
|
915
|
+
format: date-time
|
|
916
|
+
HealthcheckResult:
|
|
917
|
+
type: object
|
|
918
|
+
required:
|
|
919
|
+
- checkId
|
|
920
|
+
- status
|
|
921
|
+
- summary
|
|
922
|
+
- explanation
|
|
923
|
+
properties:
|
|
924
|
+
suiteId:
|
|
925
|
+
type: string
|
|
926
|
+
checkId:
|
|
927
|
+
type: string
|
|
928
|
+
status:
|
|
929
|
+
$ref: '#/components/schemas/HealthcheckStatus'
|
|
930
|
+
summary:
|
|
931
|
+
type: string
|
|
932
|
+
explanation:
|
|
933
|
+
type: string
|
|
934
|
+
details:
|
|
935
|
+
type: array
|
|
936
|
+
items:
|
|
937
|
+
$ref: '#/components/schemas/HealthcheckDetail'
|
|
938
|
+
metrics:
|
|
939
|
+
type: object
|
|
940
|
+
additionalProperties: true
|
|
941
|
+
artifacts:
|
|
942
|
+
type: array
|
|
943
|
+
items:
|
|
944
|
+
$ref: '#/components/schemas/HealthcheckArtifact'
|
|
945
|
+
durationMs:
|
|
946
|
+
type: integer
|
|
947
|
+
minimum: 0
|
|
948
|
+
cached:
|
|
949
|
+
type: boolean
|
|
950
|
+
HealthcheckDetail:
|
|
951
|
+
type: object
|
|
952
|
+
required:
|
|
953
|
+
- severity
|
|
954
|
+
- message
|
|
955
|
+
properties:
|
|
956
|
+
code:
|
|
957
|
+
type: string
|
|
958
|
+
severity:
|
|
959
|
+
type: string
|
|
960
|
+
enum: [info, low, medium, high, critical]
|
|
961
|
+
message:
|
|
962
|
+
type: string
|
|
963
|
+
path:
|
|
964
|
+
type: string
|
|
965
|
+
line:
|
|
966
|
+
type: integer
|
|
967
|
+
minimum: 1
|
|
968
|
+
column:
|
|
969
|
+
type: integer
|
|
970
|
+
minimum: 1
|
|
971
|
+
remediation:
|
|
972
|
+
type: string
|
|
973
|
+
HealthcheckArtifact:
|
|
974
|
+
type: object
|
|
975
|
+
required:
|
|
976
|
+
- name
|
|
977
|
+
- path
|
|
978
|
+
properties:
|
|
979
|
+
name:
|
|
980
|
+
type: string
|
|
981
|
+
path:
|
|
982
|
+
type: string
|
|
983
|
+
contentType:
|
|
984
|
+
type: string
|
|
985
|
+
HealthcheckStatus:
|
|
986
|
+
type: string
|
|
987
|
+
enum: [na, failed, pass-partial, pass-full]
|
|
711
988
|
CancelResponse:
|
|
712
989
|
type: object
|
|
713
990
|
required:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-daemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/daemon.js",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"daemon": "tsx src/daemon.ts",
|
|
10
10
|
"daemon:setup": "tsx src/setup.ts",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"verify": "npm run lint && npm run typecheck && npm test",
|
|
11
13
|
"test": "vitest run",
|
|
12
14
|
"test:watch": "vitest",
|
|
13
15
|
"test:clone": "bash scripts/test-clone.sh",
|