coverage-check 0.1.1 → 0.2.1
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 +105 -51
- package/package.json +2 -1
- package/src/commands/check-args.mts +110 -0
- package/src/commands/check.mts +41 -94
- package/src/commands/check.test.mts +253 -25
- package/src/commands/store-put.mts +37 -22
- package/src/commands/store-put.test.mts +117 -23
- package/src/coverage-check.mts +2 -0
- package/src/github-comment.mts +8 -3
- package/src/github-comment.test.mts +4 -5
- package/src/report.mts +0 -9
- package/src/report.test.mts +1 -18
- package/src/s3-suite-store.mts +138 -0
- package/src/s3-suite-store.test.mts +308 -0
- package/src/step-summary.mts +89 -0
- package/src/step-summary.test.mts +189 -0
- package/src/store-factory.mts +23 -0
- package/src/store-factory.test.mts +67 -0
- package/src/suite-store.mts +67 -17
- package/src/suite-store.test.mts +124 -30
- package/src/types.mts +1 -1
|
@@ -30,6 +30,38 @@ describe("main argument validation", () => {
|
|
|
30
30
|
it("returns exit code 2 when a flag is missing its value", async () => {
|
|
31
31
|
expect(await main(["--rules"])).toBe(2);
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
it("returns exit code 2 when a flag token follows as the value (e.g. --rules --pr)", async () => {
|
|
35
|
+
expect(await main(["--rules", "--pr"])).toBe(2);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns exit code 2 when --pr is set but repo is empty", async () => {
|
|
39
|
+
const saved = process.env["GITHUB_REPOSITORY"];
|
|
40
|
+
delete process.env["GITHUB_REPOSITORY"];
|
|
41
|
+
try {
|
|
42
|
+
expect(await main(["--pr", "42"])).toBe(2);
|
|
43
|
+
} finally {
|
|
44
|
+
if (saved !== undefined) process.env["GITHUB_REPOSITORY"] = saved;
|
|
45
|
+
else delete process.env["GITHUB_REPOSITORY"];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("uses fallback defaults when GITHUB_REPOSITORY/REF_NAME/STEP_SUMMARY are unset", async () => {
|
|
50
|
+
const saved: Record<string, string | undefined> = {};
|
|
51
|
+
for (const key of ["GITHUB_REPOSITORY", "GITHUB_REF_NAME", "GITHUB_STEP_SUMMARY"]) {
|
|
52
|
+
saved[key] = process.env[key];
|
|
53
|
+
delete process.env[key];
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
// Any call exercises the default-init lines; unknown flag triggers parse error
|
|
57
|
+
expect(await main(["--unknown-flag"])).toBe(2);
|
|
58
|
+
} finally {
|
|
59
|
+
for (const [k, v] of Object.entries(saved)) {
|
|
60
|
+
if (v !== undefined) process.env[k] = v;
|
|
61
|
+
else delete process.env[k];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
33
65
|
});
|
|
34
66
|
|
|
35
67
|
describe("main integration", () => {
|
|
@@ -96,6 +128,47 @@ describe("main integration", () => {
|
|
|
96
128
|
).toBe(2);
|
|
97
129
|
});
|
|
98
130
|
|
|
131
|
+
it("accepts --branch flag", async () => {
|
|
132
|
+
expect(
|
|
133
|
+
await main([
|
|
134
|
+
"--rules",
|
|
135
|
+
join(tmpDir, "nonexistent.yml"),
|
|
136
|
+
"--branch",
|
|
137
|
+
"main",
|
|
138
|
+
"--artifacts",
|
|
139
|
+
artifactsDir,
|
|
140
|
+
]),
|
|
141
|
+
).toBe(2);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("returns 2 when both --store-fs and --store-s3 are provided", async () => {
|
|
145
|
+
expect(
|
|
146
|
+
await main([
|
|
147
|
+
"--rules",
|
|
148
|
+
join(tmpDir, "nonexistent.yml"),
|
|
149
|
+
"--artifacts",
|
|
150
|
+
artifactsDir,
|
|
151
|
+
"--store-fs",
|
|
152
|
+
"/tmp/store",
|
|
153
|
+
"--store-s3",
|
|
154
|
+
"my-bucket",
|
|
155
|
+
]),
|
|
156
|
+
).toBe(2);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("accepts --store-s3 flag (parse succeeds, fails on missing rules)", async () => {
|
|
160
|
+
expect(
|
|
161
|
+
await main([
|
|
162
|
+
"--rules",
|
|
163
|
+
join(tmpDir, "nonexistent.yml"),
|
|
164
|
+
"--artifacts",
|
|
165
|
+
artifactsDir,
|
|
166
|
+
"--store-s3",
|
|
167
|
+
"my-bucket/prefix",
|
|
168
|
+
]),
|
|
169
|
+
).toBe(2);
|
|
170
|
+
});
|
|
171
|
+
|
|
99
172
|
it("returns 0 when no coverage data found — skips git entirely", async () => {
|
|
100
173
|
expect(await main(["--rules", rulesPath, "--artifacts", artifactsDir])).toBe(0);
|
|
101
174
|
});
|
|
@@ -160,7 +233,6 @@ describe("runCheck with suite store", () => {
|
|
|
160
233
|
writeFileSync(rulesPath, "rules:\n - paths: backend/**\n patch_coverage_min: 90\n");
|
|
161
234
|
store = new FileSystemSuiteStore(storeDir);
|
|
162
235
|
|
|
163
|
-
// Set up a minimal git repo so HEAD is valid for diff tests
|
|
164
236
|
const repoDir = join(tmpDir, "repo");
|
|
165
237
|
mkdirSync(join(repoDir, "backend"), { recursive: true });
|
|
166
238
|
|
|
@@ -219,7 +291,10 @@ describe("runCheck with suite store", () => {
|
|
|
219
291
|
});
|
|
220
292
|
|
|
221
293
|
it("returns 0 when store has suites but diff is empty (base=head)", async () => {
|
|
222
|
-
await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n")
|
|
294
|
+
await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"), {
|
|
295
|
+
sha: "test-sha",
|
|
296
|
+
branch: "main",
|
|
297
|
+
});
|
|
223
298
|
expect(
|
|
224
299
|
await runCheck({
|
|
225
300
|
rules: rulesPath,
|
|
@@ -237,15 +312,18 @@ describe("runCheck with suite store", () => {
|
|
|
237
312
|
});
|
|
238
313
|
|
|
239
314
|
it("excludes the current suite from the store during check", async () => {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
315
|
+
await store.put("backend", Buffer.from("SF:backend/foo.mts\nDA:1,1\nend_of_record\n"), {
|
|
316
|
+
sha: "test-sha",
|
|
317
|
+
branch: "main",
|
|
318
|
+
});
|
|
319
|
+
await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"), {
|
|
320
|
+
sha: "test-sha",
|
|
321
|
+
branch: "main",
|
|
322
|
+
});
|
|
245
323
|
expect(
|
|
246
324
|
await runCheck({
|
|
247
325
|
rules: rulesPath,
|
|
248
|
-
artifacts: artifactsDir,
|
|
326
|
+
artifacts: artifactsDir,
|
|
249
327
|
base: "HEAD",
|
|
250
328
|
head: "HEAD",
|
|
251
329
|
pr: null,
|
|
@@ -253,16 +331,17 @@ describe("runCheck with suite store", () => {
|
|
|
253
331
|
json: null,
|
|
254
332
|
stripPrefixes: [],
|
|
255
333
|
store,
|
|
256
|
-
suite: "backend",
|
|
334
|
+
suite: "backend",
|
|
257
335
|
}),
|
|
258
336
|
).toBe(0);
|
|
259
337
|
});
|
|
260
338
|
|
|
261
339
|
it("includes non-current suites from the store", async () => {
|
|
262
|
-
|
|
263
|
-
|
|
340
|
+
await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"), {
|
|
341
|
+
sha: "test-sha",
|
|
342
|
+
branch: "main",
|
|
343
|
+
});
|
|
264
344
|
writeFileSync(join(artifactsDir, "lcov.info"), "SF:backend/foo.mts\nDA:1,1\nend_of_record\n");
|
|
265
|
-
// With suite=backend, frontend from store should still be merged
|
|
266
345
|
expect(
|
|
267
346
|
await runCheck({
|
|
268
347
|
rules: rulesPath,
|
|
@@ -280,17 +359,19 @@ describe("runCheck with suite store", () => {
|
|
|
280
359
|
});
|
|
281
360
|
|
|
282
361
|
it("handles a store that returns null from get() gracefully", async () => {
|
|
283
|
-
// Custom store: list returns a suite, but get returns null (e.g. file was deleted)
|
|
284
362
|
const nullStore = {
|
|
285
363
|
async list() {
|
|
286
364
|
return ["backend"];
|
|
287
365
|
},
|
|
288
|
-
async get(_suite: string) {
|
|
366
|
+
async get(_suite: string, _opts?: { sha?: string; branch?: string }) {
|
|
289
367
|
return null;
|
|
290
368
|
},
|
|
291
|
-
async put(
|
|
369
|
+
async put(
|
|
370
|
+
_suite: string,
|
|
371
|
+
_lcov: Buffer,
|
|
372
|
+
_meta: { sha: string; branch: string },
|
|
373
|
+
): Promise<void> {},
|
|
292
374
|
};
|
|
293
|
-
// No local artifacts + store returns null → no reports → return 0
|
|
294
375
|
expect(
|
|
295
376
|
await runCheck({
|
|
296
377
|
rules: rulesPath,
|
|
@@ -308,7 +389,10 @@ describe("runCheck with suite store", () => {
|
|
|
308
389
|
});
|
|
309
390
|
|
|
310
391
|
it("constructs a real runUrl when GITHUB_SERVER_URL and GITHUB_RUN_ID are set", async () => {
|
|
311
|
-
await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n")
|
|
392
|
+
await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"), {
|
|
393
|
+
sha: "test-sha",
|
|
394
|
+
branch: "main",
|
|
395
|
+
});
|
|
312
396
|
|
|
313
397
|
const calls: string[][] = [];
|
|
314
398
|
const gh = async (args: string[]) => {
|
|
@@ -322,7 +406,6 @@ describe("runCheck with suite store", () => {
|
|
|
322
406
|
process.env["GITHUB_RUN_ID"] = "12345";
|
|
323
407
|
|
|
324
408
|
try {
|
|
325
|
-
// With frontend from store and empty diff (HEAD=HEAD), passes
|
|
326
409
|
await runCheck({
|
|
327
410
|
rules: rulesPath,
|
|
328
411
|
artifacts: artifactsDir,
|
|
@@ -336,7 +419,6 @@ describe("runCheck with suite store", () => {
|
|
|
336
419
|
suite: null,
|
|
337
420
|
gh,
|
|
338
421
|
});
|
|
339
|
-
// gh was called (lookup for existing comment)
|
|
340
422
|
expect(calls.length).toBeGreaterThanOrEqual(1);
|
|
341
423
|
} finally {
|
|
342
424
|
if (origServer === undefined) delete process.env["GITHUB_SERVER_URL"];
|
|
@@ -347,7 +429,6 @@ describe("runCheck with suite store", () => {
|
|
|
347
429
|
});
|
|
348
430
|
|
|
349
431
|
it("accepts --store and --suite flags via main()", async () => {
|
|
350
|
-
// --store and --suite valid flags, no lcov → returns 0
|
|
351
432
|
expect(
|
|
352
433
|
await main([
|
|
353
434
|
"--rules",
|
|
@@ -361,6 +442,21 @@ describe("runCheck with suite store", () => {
|
|
|
361
442
|
]),
|
|
362
443
|
).toBe(0);
|
|
363
444
|
});
|
|
445
|
+
|
|
446
|
+
it("accepts --store-fs flag as alias for --store", async () => {
|
|
447
|
+
expect(
|
|
448
|
+
await main([
|
|
449
|
+
"--rules",
|
|
450
|
+
rulesPath,
|
|
451
|
+
"--artifacts",
|
|
452
|
+
artifactsDir,
|
|
453
|
+
"--store-fs",
|
|
454
|
+
storeDir,
|
|
455
|
+
"--suite",
|
|
456
|
+
"backend",
|
|
457
|
+
]),
|
|
458
|
+
).toBe(0);
|
|
459
|
+
});
|
|
364
460
|
});
|
|
365
461
|
|
|
366
462
|
describe("with a real git repo and a known diff", () => {
|
|
@@ -527,7 +623,6 @@ describe("with a real git repo and a known diff", () => {
|
|
|
527
623
|
gh,
|
|
528
624
|
});
|
|
529
625
|
expect(result).toBe(1);
|
|
530
|
-
// gh was called: first to look up existing comment, then to post
|
|
531
626
|
expect(calls.length).toBeGreaterThanOrEqual(1);
|
|
532
627
|
});
|
|
533
628
|
|
|
@@ -580,7 +675,6 @@ describe("with a real git repo and a known diff", () => {
|
|
|
580
675
|
suite: null,
|
|
581
676
|
gh,
|
|
582
677
|
});
|
|
583
|
-
// Still returns 1 even though gh call failed
|
|
584
678
|
expect(result).toBe(1);
|
|
585
679
|
});
|
|
586
680
|
|
|
@@ -615,10 +709,11 @@ describe("with a real git repo and a known diff", () => {
|
|
|
615
709
|
mkdirSync(storeDir);
|
|
616
710
|
const store = new FileSystemSuiteStore(storeDir);
|
|
617
711
|
|
|
618
|
-
|
|
619
|
-
|
|
712
|
+
await store.put("frontend", Buffer.from("SF:web/app.tsx\nDA:1,1\nend_of_record\n"), {
|
|
713
|
+
sha: "test-sha",
|
|
714
|
+
branch: "main",
|
|
715
|
+
});
|
|
620
716
|
|
|
621
|
-
// Current artifacts has backend coverage (line 2 now covered)
|
|
622
717
|
writeFileSync(
|
|
623
718
|
join(artifactsDir, "lcov.info"),
|
|
624
719
|
"SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
|
|
@@ -639,4 +734,137 @@ describe("with a real git repo and a known diff", () => {
|
|
|
639
734
|
}),
|
|
640
735
|
).toBe(0);
|
|
641
736
|
});
|
|
737
|
+
|
|
738
|
+
it("writes step summary when summaryFile is provided", async () => {
|
|
739
|
+
const summaryFile = join(tmpDir, "summary.md");
|
|
740
|
+
writeFileSync(summaryFile, "");
|
|
741
|
+
writeFileSync(
|
|
742
|
+
join(artifactsDir, "lcov.info"),
|
|
743
|
+
"SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
|
|
744
|
+
);
|
|
745
|
+
await runCheck({
|
|
746
|
+
rules: rulesPath,
|
|
747
|
+
artifacts: artifactsDir,
|
|
748
|
+
base: baseSha,
|
|
749
|
+
head: headSha,
|
|
750
|
+
pr: null,
|
|
751
|
+
repo: "",
|
|
752
|
+
json: null,
|
|
753
|
+
stripPrefixes: [],
|
|
754
|
+
store: null,
|
|
755
|
+
suite: "backend",
|
|
756
|
+
summaryFile,
|
|
757
|
+
});
|
|
758
|
+
const content = readFileSync(summaryFile, "utf8");
|
|
759
|
+
expect(content).toContain("Coverage summary");
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it("does not write step summary when summaryFile is null even if env var is set", async () => {
|
|
763
|
+
const summaryFile = join(tmpDir, "should-not-exist.md");
|
|
764
|
+
writeFileSync(
|
|
765
|
+
join(artifactsDir, "lcov.info"),
|
|
766
|
+
"SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
|
|
767
|
+
);
|
|
768
|
+
const origEnv = process.env["GITHUB_STEP_SUMMARY"];
|
|
769
|
+
process.env["GITHUB_STEP_SUMMARY"] = summaryFile;
|
|
770
|
+
try {
|
|
771
|
+
await runCheck({
|
|
772
|
+
rules: rulesPath,
|
|
773
|
+
artifacts: artifactsDir,
|
|
774
|
+
base: baseSha,
|
|
775
|
+
head: headSha,
|
|
776
|
+
pr: null,
|
|
777
|
+
repo: "",
|
|
778
|
+
json: null,
|
|
779
|
+
stripPrefixes: [],
|
|
780
|
+
store: null,
|
|
781
|
+
suite: "backend",
|
|
782
|
+
summaryFile: null,
|
|
783
|
+
});
|
|
784
|
+
} finally {
|
|
785
|
+
if (origEnv === undefined) delete process.env["GITHUB_STEP_SUMMARY"];
|
|
786
|
+
else process.env["GITHUB_STEP_SUMMARY"] = origEnv;
|
|
787
|
+
}
|
|
788
|
+
expect(() => readFileSync(summaryFile, "utf8")).toThrow();
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it("uses N/A runUrl when GITHUB_SERVER_URL and GITHUB_RUN_ID are unset", async () => {
|
|
792
|
+
writeFileSync(
|
|
793
|
+
join(artifactsDir, "lcov.info"),
|
|
794
|
+
"SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
|
|
795
|
+
);
|
|
796
|
+
const savedServer = process.env["GITHUB_SERVER_URL"];
|
|
797
|
+
const savedRunId = process.env["GITHUB_RUN_ID"];
|
|
798
|
+
delete process.env["GITHUB_SERVER_URL"];
|
|
799
|
+
delete process.env["GITHUB_RUN_ID"];
|
|
800
|
+
try {
|
|
801
|
+
await runCheck({
|
|
802
|
+
rules: rulesPath,
|
|
803
|
+
artifacts: artifactsDir,
|
|
804
|
+
base: baseSha,
|
|
805
|
+
head: headSha,
|
|
806
|
+
pr: null,
|
|
807
|
+
repo: "",
|
|
808
|
+
json: null,
|
|
809
|
+
stripPrefixes: [],
|
|
810
|
+
store: null,
|
|
811
|
+
suite: "backend",
|
|
812
|
+
});
|
|
813
|
+
} finally {
|
|
814
|
+
if (savedServer !== undefined) process.env["GITHUB_SERVER_URL"] = savedServer;
|
|
815
|
+
else delete process.env["GITHUB_SERVER_URL"];
|
|
816
|
+
if (savedRunId !== undefined) process.env["GITHUB_RUN_ID"] = savedRunId;
|
|
817
|
+
else delete process.env["GITHUB_RUN_ID"];
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it("returns 2 when writeSummary throws (unwritable summaryFile path)", async () => {
|
|
822
|
+
writeFileSync(
|
|
823
|
+
join(artifactsDir, "lcov.info"),
|
|
824
|
+
"SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
|
|
825
|
+
);
|
|
826
|
+
// Pass the tmp directory itself as summaryFile — appendFileSync on a dir throws EISDIR
|
|
827
|
+
expect(
|
|
828
|
+
await runCheck({
|
|
829
|
+
rules: rulesPath,
|
|
830
|
+
artifacts: artifactsDir,
|
|
831
|
+
base: baseSha,
|
|
832
|
+
head: headSha,
|
|
833
|
+
pr: null,
|
|
834
|
+
repo: "",
|
|
835
|
+
json: null,
|
|
836
|
+
stripPrefixes: [],
|
|
837
|
+
store: null,
|
|
838
|
+
suite: "backend",
|
|
839
|
+
summaryFile: tmpDir,
|
|
840
|
+
}),
|
|
841
|
+
).toBe(2);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it("does not write summary when summaryFile is undefined and GITHUB_STEP_SUMMARY is unset", async () => {
|
|
845
|
+
writeFileSync(
|
|
846
|
+
join(artifactsDir, "lcov.info"),
|
|
847
|
+
"SF:backend/foo.mts\nDA:1,1\nDA:2,1\nend_of_record\n",
|
|
848
|
+
);
|
|
849
|
+
const savedSummary = process.env["GITHUB_STEP_SUMMARY"];
|
|
850
|
+
delete process.env["GITHUB_STEP_SUMMARY"];
|
|
851
|
+
try {
|
|
852
|
+
await runCheck({
|
|
853
|
+
rules: rulesPath,
|
|
854
|
+
artifacts: artifactsDir,
|
|
855
|
+
base: baseSha,
|
|
856
|
+
head: headSha,
|
|
857
|
+
pr: null,
|
|
858
|
+
repo: "",
|
|
859
|
+
json: null,
|
|
860
|
+
stripPrefixes: [],
|
|
861
|
+
store: null,
|
|
862
|
+
suite: "backend",
|
|
863
|
+
summaryFile: undefined,
|
|
864
|
+
});
|
|
865
|
+
} finally {
|
|
866
|
+
if (savedSummary !== undefined) process.env["GITHUB_STEP_SUMMARY"] = savedSummary;
|
|
867
|
+
else delete process.env["GITHUB_STEP_SUMMARY"];
|
|
868
|
+
}
|
|
869
|
+
});
|
|
642
870
|
});
|
|
@@ -2,35 +2,40 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { parseLcov } from "../lcov-parser.mts";
|
|
3
3
|
import { mergeLcov, toLcov } from "../lcov-merge.mts";
|
|
4
4
|
import { collectLcovFiles, buildStripPrefixes } from "../load-artifacts.mts";
|
|
5
|
-
import {
|
|
5
|
+
import { makeStore } from "../store-factory.mts";
|
|
6
|
+
import { assertSafePathComponent } from "../suite-store.mts";
|
|
7
|
+
import type { SuiteStore } from "../suite-store.mts";
|
|
6
8
|
|
|
7
9
|
const stdout = (msg: string) => process.stdout.write(`${msg}\n`);
|
|
8
10
|
const stderr = (msg: string) => process.stderr.write(`${msg}\n`);
|
|
9
11
|
|
|
10
12
|
export type StorePutArgs = {
|
|
11
13
|
suite: string;
|
|
12
|
-
store:
|
|
14
|
+
store: SuiteStore;
|
|
13
15
|
artifacts: string;
|
|
14
16
|
stripPrefixes: string[];
|
|
15
|
-
sha: string
|
|
16
|
-
|
|
17
|
+
sha: string;
|
|
18
|
+
branch: string;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
function parseArgs(argv: string[]): StorePutArgs {
|
|
20
|
-
|
|
22
|
+
let storeFs: string | null = null;
|
|
23
|
+
let storeS3: string | null = null;
|
|
24
|
+
const args = {
|
|
21
25
|
suite: "",
|
|
22
|
-
store: "",
|
|
23
26
|
artifacts: "./coverage-artifacts",
|
|
24
|
-
stripPrefixes: [],
|
|
25
|
-
sha:
|
|
26
|
-
|
|
27
|
+
stripPrefixes: [] as string[],
|
|
28
|
+
sha: "",
|
|
29
|
+
branch: "",
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
for (let i = 0; i < argv.length; i++) {
|
|
30
33
|
const flag = argv[i]!;
|
|
31
34
|
const next = argv[i + 1];
|
|
32
35
|
const val = (): string => {
|
|
33
|
-
if (next === undefined
|
|
36
|
+
if (next === undefined || next.startsWith("--")) {
|
|
37
|
+
throw new Error(`${flag} requires a value`);
|
|
38
|
+
}
|
|
34
39
|
i++;
|
|
35
40
|
return next;
|
|
36
41
|
};
|
|
@@ -39,7 +44,11 @@ function parseArgs(argv: string[]): StorePutArgs {
|
|
|
39
44
|
args.suite = val();
|
|
40
45
|
break;
|
|
41
46
|
case "--store":
|
|
42
|
-
|
|
47
|
+
case "--store-fs":
|
|
48
|
+
storeFs = val();
|
|
49
|
+
break;
|
|
50
|
+
case "--store-s3":
|
|
51
|
+
storeS3 = val();
|
|
43
52
|
break;
|
|
44
53
|
case "--artifacts":
|
|
45
54
|
args.artifacts = val();
|
|
@@ -50,8 +59,8 @@ function parseArgs(argv: string[]): StorePutArgs {
|
|
|
50
59
|
case "--sha":
|
|
51
60
|
args.sha = val();
|
|
52
61
|
break;
|
|
53
|
-
case "--
|
|
54
|
-
args.
|
|
62
|
+
case "--branch":
|
|
63
|
+
args.branch = val();
|
|
55
64
|
break;
|
|
56
65
|
default:
|
|
57
66
|
throw new Error(`unknown flag: ${flag}`);
|
|
@@ -59,8 +68,16 @@ function parseArgs(argv: string[]): StorePutArgs {
|
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
if (!args.suite) throw new Error("--suite is required");
|
|
62
|
-
if (
|
|
63
|
-
|
|
71
|
+
if (storeFs && storeS3) throw new Error("--store-fs and --store-s3 are mutually exclusive");
|
|
72
|
+
if (!storeFs && !storeS3) throw new Error("--store-fs/--store or --store-s3 is required");
|
|
73
|
+
if (!args.sha) throw new Error("--sha is required");
|
|
74
|
+
if (!args.branch) throw new Error("--branch is required");
|
|
75
|
+
assertSafePathComponent(args.suite, "suite");
|
|
76
|
+
assertSafePathComponent(args.sha, "sha");
|
|
77
|
+
assertSafePathComponent(args.branch, "branch");
|
|
78
|
+
|
|
79
|
+
const store = makeStore({ fs: storeFs, s3: storeS3 })!;
|
|
80
|
+
return { ...args, store };
|
|
64
81
|
}
|
|
65
82
|
|
|
66
83
|
export async function main(argv: string[]): Promise<number> {
|
|
@@ -68,8 +85,7 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
68
85
|
try {
|
|
69
86
|
args = parseArgs(argv);
|
|
70
87
|
} catch (err) {
|
|
71
|
-
|
|
72
|
-
stderr(`coverage-check store-put: ${err instanceof Error ? err.message : err}`);
|
|
88
|
+
stderr(`coverage-check store-put: ${String(err)}`);
|
|
73
89
|
return 2;
|
|
74
90
|
}
|
|
75
91
|
return runStorePut(args);
|
|
@@ -87,14 +103,13 @@ export async function runStorePut(args: StorePutArgs): Promise<number> {
|
|
|
87
103
|
const merged = mergeLcov(reports);
|
|
88
104
|
const lcovText = toLcov(merged);
|
|
89
105
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
ref: args.ref ?? undefined,
|
|
106
|
+
await args.store.put(args.suite, Buffer.from(lcovText, "utf8"), {
|
|
107
|
+
sha: args.sha,
|
|
108
|
+
branch: args.branch,
|
|
94
109
|
});
|
|
95
110
|
|
|
96
111
|
stdout(
|
|
97
|
-
`coverage-check store-put: stored suite "${args.suite}" (${lcovFiles.length} file(s))
|
|
112
|
+
`coverage-check store-put: stored suite "${args.suite}" (${lcovFiles.length} file(s)) sha=${args.sha} branch=${args.branch}`,
|
|
98
113
|
);
|
|
99
114
|
return 0;
|
|
100
115
|
}
|