coverage-check 0.7.0 → 0.7.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 +19 -2
- package/dist/src/s3-diagnostics.d.mts +9 -0
- package/dist/src/s3-diagnostics.mjs +60 -0
- package/dist/src/s3-suite-store-reads.d.mts +17 -0
- package/dist/src/s3-suite-store-reads.mjs +57 -0
- package/dist/src/s3-suite-store.d.mts +7 -9
- package/dist/src/s3-suite-store.mjs +67 -75
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -51,12 +51,29 @@ The `--suite` flag on `check` tells the tool to use fresh `--artifacts` for the
|
|
|
51
51
|
**S3 key layout:**
|
|
52
52
|
|
|
53
53
|
```text
|
|
54
|
-
<prefix>/<suite>/sha/<sha>/lcov.info
|
|
55
|
-
<prefix>/<suite>/branch/<encoded-branch>/latest.json # pointer
|
|
54
|
+
<prefix>/<suite>/sha/<sha>/lcov.info.gz # gzip payload for new versioned writes
|
|
55
|
+
<prefix>/<suite>/branch/<encoded-branch>/latest.json # pointer with sha, payloadKey, encoding, byte counts, timestamp
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
S3-backed stores need `s3:PutObject` for writes and `s3:GetObject` for reading branch pointers and baselines. The pointer reader also checks the previous unencoded pointer key (for example `branch/main/latest.json`) so stores written before branch-name encoding remain readable.
|
|
59
59
|
|
|
60
|
+
Versioned S3 writes (`store-put --sha ... --branch ...`) gzip the LCOV payload and write pointer
|
|
61
|
+
metadata with `payloadKey`, `encoding`, `rawBytes`, and `storedBytes`. Existing raw
|
|
62
|
+
`sha/<sha>/lcov.info` payloads and legacy `<suite>/lcov.info` payloads remain readable. Legacy
|
|
63
|
+
writes without `--sha`/`--branch` keep the old raw `<suite>/lcov.info` layout.
|
|
64
|
+
|
|
65
|
+
Every S3 operation logs a concise diagnostic line to stderr with the operation name, bucket, key,
|
|
66
|
+
elapsed time, and byte counts where applicable. Use these lines to distinguish payload writes,
|
|
67
|
+
branch-pointer reads, and branch-pointer writes when CI storage stalls.
|
|
68
|
+
|
|
69
|
+
S3 request bounds are configurable with environment variables:
|
|
70
|
+
|
|
71
|
+
| Variable | Default | Purpose |
|
|
72
|
+
| ----------------------------------------- | ------- | ---------------------------------------- |
|
|
73
|
+
| `COVERAGE_CHECK_S3_CONNECTION_TIMEOUT_MS` | `5000` | Socket connection timeout for S3 calls |
|
|
74
|
+
| `COVERAGE_CHECK_S3_REQUEST_TIMEOUT_MS` | `30000` | Whole-request timeout for S3 calls |
|
|
75
|
+
| `COVERAGE_CHECK_S3_MAX_ATTEMPTS` | `2` | AWS SDK attempt count, including retries |
|
|
76
|
+
|
|
60
77
|
### Suite store with filesystem
|
|
61
78
|
|
|
62
79
|
For local development or simpler deployments:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type ClientLike = {
|
|
2
|
+
send(cmd: object): Promise<unknown>;
|
|
3
|
+
};
|
|
4
|
+
export type S3OperationDetails = {
|
|
5
|
+
rawBytes?: number;
|
|
6
|
+
storedBytes?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function createS3Client(region?: string): ClientLike;
|
|
9
|
+
export declare function sendS3(client: ClientLike, bucket: string, operation: string, key: string, command: object, details?: S3OperationDetails): Promise<unknown>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
2
|
+
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
|
3
|
+
export function createS3Client(region) {
|
|
4
|
+
return new S3Client({
|
|
5
|
+
maxAttempts: readPositiveIntEnv("COVERAGE_CHECK_S3_MAX_ATTEMPTS", 2),
|
|
6
|
+
region,
|
|
7
|
+
requestHandler: new NodeHttpHandler({
|
|
8
|
+
connectionTimeout: readPositiveIntEnv("COVERAGE_CHECK_S3_CONNECTION_TIMEOUT_MS", 5000),
|
|
9
|
+
requestTimeout: readPositiveIntEnv("COVERAGE_CHECK_S3_REQUEST_TIMEOUT_MS", 30000),
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export async function sendS3(client, bucket, operation, key, command, details = {}) {
|
|
14
|
+
const start = performance.now();
|
|
15
|
+
try {
|
|
16
|
+
const result = await client.send(command);
|
|
17
|
+
logS3(bucket, operation, key, "ok", performance.now() - start, details);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
logS3(bucket, operation, key, "failed", performance.now() - start, {
|
|
22
|
+
...details,
|
|
23
|
+
error: formatError(err),
|
|
24
|
+
});
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function logS3(bucket, operation, key, status, elapsedMs, details) {
|
|
29
|
+
const parts = [
|
|
30
|
+
"coverage-check s3",
|
|
31
|
+
`operation=${JSON.stringify(operation)}`,
|
|
32
|
+
`status=${status}`,
|
|
33
|
+
`bucket=${JSON.stringify(bucket)}`,
|
|
34
|
+
`key=${JSON.stringify(key)}`,
|
|
35
|
+
`elapsed_ms=${Math.round(elapsedMs)}`,
|
|
36
|
+
];
|
|
37
|
+
if (details.rawBytes !== undefined)
|
|
38
|
+
parts.push(`raw_bytes=${details.rawBytes}`);
|
|
39
|
+
if (details.storedBytes !== undefined)
|
|
40
|
+
parts.push(`stored_bytes=${details.storedBytes}`);
|
|
41
|
+
if (details.error !== undefined)
|
|
42
|
+
parts.push(`error=${JSON.stringify(details.error)}`);
|
|
43
|
+
process.stderr.write(`${parts.join(" ")}\n`);
|
|
44
|
+
}
|
|
45
|
+
function readPositiveIntEnv(name, fallback) {
|
|
46
|
+
const raw = process.env[name];
|
|
47
|
+
if (raw === undefined || raw === "")
|
|
48
|
+
return fallback;
|
|
49
|
+
const value = Number(raw);
|
|
50
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
51
|
+
process.stderr.write(`coverage-check s3 invalid ${name}=${JSON.stringify(raw)}; using ${fallback}\n`);
|
|
52
|
+
return fallback;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
function formatError(err) {
|
|
57
|
+
if (err instanceof Error)
|
|
58
|
+
return `${err.name}: ${err.message}`;
|
|
59
|
+
return String(err);
|
|
60
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type StoredPointer = {
|
|
2
|
+
sha: string;
|
|
3
|
+
timestamp?: string;
|
|
4
|
+
payloadKey?: string;
|
|
5
|
+
encoding?: "gzip";
|
|
6
|
+
rawBytes?: number;
|
|
7
|
+
storedBytes?: number;
|
|
8
|
+
};
|
|
9
|
+
type ReadContext = {
|
|
10
|
+
bucket: string;
|
|
11
|
+
key(...parts: string[]): string;
|
|
12
|
+
sendS3(operation: string, key: string, command: object): Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
export declare function getLegacy(ctx: ReadContext, suite: string): Promise<Buffer | null>;
|
|
15
|
+
export declare function shouldWritePointer(ctx: ReadContext, suite: string, branch: string, incomingTimestamp: string): Promise<boolean>;
|
|
16
|
+
export declare function readPointer(ctx: ReadContext, suite: string, branch: string): Promise<StoredPointer>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
|
2
|
+
import { encodeBranchName, isNewerTimestamp } from "./suite-store.mjs";
|
|
3
|
+
import { bodyToBuffer, isNotFound } from "./s3-utils.mjs";
|
|
4
|
+
export async function getLegacy(ctx, suite) {
|
|
5
|
+
const key = ctx.key(suite, "lcov.info");
|
|
6
|
+
try {
|
|
7
|
+
const resp = (await ctx.sendS3("get legacy coverage payload", key, new GetObjectCommand({
|
|
8
|
+
Bucket: ctx.bucket,
|
|
9
|
+
Key: key,
|
|
10
|
+
})));
|
|
11
|
+
return bodyToBuffer(resp.Body);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (isNotFound(err))
|
|
15
|
+
return null;
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function shouldWritePointer(ctx, suite, branch, incomingTimestamp) {
|
|
20
|
+
const key = ctx.key(suite, "branch", encodeBranchName(branch), "latest.json");
|
|
21
|
+
try {
|
|
22
|
+
const resp = (await ctx.sendS3("get branch pointer for compare", key, new GetObjectCommand({
|
|
23
|
+
Bucket: ctx.bucket,
|
|
24
|
+
Key: key,
|
|
25
|
+
})));
|
|
26
|
+
if (resp.Body === undefined)
|
|
27
|
+
return true;
|
|
28
|
+
const body = await bodyToBuffer(resp.Body);
|
|
29
|
+
const current = JSON.parse(body.toString("utf8"));
|
|
30
|
+
return !isNewerTimestamp(current.timestamp, incomingTimestamp);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (isNotFound(err))
|
|
34
|
+
return true;
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function readPointer(ctx, suite, branch) {
|
|
39
|
+
const keys = [
|
|
40
|
+
ctx.key(suite, "branch", encodeBranchName(branch), "latest.json"),
|
|
41
|
+
ctx.key(suite, "branch", branch, "latest.json"),
|
|
42
|
+
];
|
|
43
|
+
let lastNotFound;
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
try {
|
|
46
|
+
const resp = (await ctx.sendS3("get branch pointer", key, new GetObjectCommand({ Bucket: ctx.bucket, Key: key })));
|
|
47
|
+
const body = await bodyToBuffer(resp.Body);
|
|
48
|
+
return JSON.parse(body.toString("utf8"));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (!isNotFound(err))
|
|
52
|
+
throw err;
|
|
53
|
+
lastNotFound = err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw lastNotFound;
|
|
57
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
import type { ClientLike, S3OperationDetails } from "./s3-diagnostics.mts";
|
|
1
2
|
import type { SuitePutMeta, SuiteStore } from "./suite-store.mts";
|
|
2
|
-
type ClientLike = {
|
|
3
|
-
send(cmd: object): Promise<unknown>;
|
|
4
|
-
};
|
|
5
3
|
export type S3SuiteStoreOptions = {
|
|
6
4
|
bucket: string;
|
|
7
5
|
prefix?: string;
|
|
@@ -10,19 +8,19 @@ export type S3SuiteStoreOptions = {
|
|
|
10
8
|
client?: ClientLike;
|
|
11
9
|
};
|
|
12
10
|
export declare class S3SuiteStore implements SuiteStore {
|
|
13
|
-
|
|
11
|
+
readonly bucket: string;
|
|
14
12
|
private readonly prefix;
|
|
15
13
|
private readonly client;
|
|
16
14
|
constructor({ bucket, prefix, region, client }: S3SuiteStoreOptions);
|
|
17
|
-
|
|
15
|
+
key(...parts: string[]): string;
|
|
18
16
|
list(): Promise<string[]>;
|
|
19
17
|
get(suite: string, opts?: {
|
|
20
18
|
sha?: string;
|
|
21
19
|
branch?: string;
|
|
22
20
|
}): Promise<Buffer | null>;
|
|
23
21
|
put(suite: string, lcov: Buffer, meta?: SuitePutMeta): Promise<void>;
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
private
|
|
22
|
+
sendS3(operation: string, key: string, command: object, details?: S3OperationDetails): Promise<unknown>;
|
|
23
|
+
private getVersionedPayload;
|
|
24
|
+
private putLegacyPayload;
|
|
25
|
+
private putPointer;
|
|
27
26
|
}
|
|
28
|
-
export {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { GetObjectCommand, ListObjectsV2Command, PutObjectCommand
|
|
2
|
-
import {
|
|
1
|
+
import { GetObjectCommand, ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
2
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
3
|
+
import { assertSafePathComponent, assertValidTimestamp, encodeBranchName } from "./suite-store.mjs";
|
|
4
|
+
import { createS3Client, sendS3 } from "./s3-diagnostics.mjs";
|
|
5
|
+
import { getLegacy, readPointer, shouldWritePointer } from "./s3-suite-store-reads.mjs";
|
|
3
6
|
import { bodyToBuffer, isNotFound } from "./s3-utils.mjs";
|
|
4
7
|
export class S3SuiteStore {
|
|
5
8
|
bucket;
|
|
@@ -8,7 +11,7 @@ export class S3SuiteStore {
|
|
|
8
11
|
constructor({ bucket, prefix, region, client }) {
|
|
9
12
|
this.bucket = bucket;
|
|
10
13
|
this.prefix = prefix ? prefix.replace(/\/+$/, "") : "";
|
|
11
|
-
this.client = client ??
|
|
14
|
+
this.client = client ?? createS3Client(region);
|
|
12
15
|
}
|
|
13
16
|
key(...parts) {
|
|
14
17
|
return this.prefix ? [this.prefix, ...parts].join("/") : parts.join("/");
|
|
@@ -18,7 +21,7 @@ export class S3SuiteStore {
|
|
|
18
21
|
const suites = [];
|
|
19
22
|
let continuationToken;
|
|
20
23
|
do {
|
|
21
|
-
const resp = (await this.
|
|
24
|
+
const resp = (await this.sendS3("list suites", this.key(), new ListObjectsV2Command({
|
|
22
25
|
Bucket: this.bucket,
|
|
23
26
|
Prefix: pfx,
|
|
24
27
|
Delimiter: "/",
|
|
@@ -36,112 +39,101 @@ export class S3SuiteStore {
|
|
|
36
39
|
if (opts?.sha !== undefined)
|
|
37
40
|
assertSafePathComponent(opts.sha, "sha");
|
|
38
41
|
let sha = opts?.sha;
|
|
42
|
+
let pointer = null;
|
|
39
43
|
if (!sha) {
|
|
40
44
|
const branch = opts?.branch ?? "main";
|
|
41
45
|
try {
|
|
42
|
-
|
|
46
|
+
pointer = await readPointer(this, suite, branch);
|
|
43
47
|
assertSafePathComponent(pointer.sha, "sha");
|
|
44
48
|
sha = pointer.sha;
|
|
45
49
|
}
|
|
46
50
|
catch (err) {
|
|
47
51
|
if (isNotFound(err))
|
|
48
|
-
return
|
|
52
|
+
return getLegacy(this, suite);
|
|
49
53
|
throw err;
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
|
-
|
|
53
|
-
const resp = (await this.client.send(new GetObjectCommand({
|
|
54
|
-
Bucket: this.bucket,
|
|
55
|
-
Key: this.key(suite, "sha", sha, "lcov.info"),
|
|
56
|
-
})));
|
|
57
|
-
return bodyToBuffer(resp.Body);
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
if (isNotFound(err))
|
|
61
|
-
return null;
|
|
62
|
-
throw err;
|
|
63
|
-
}
|
|
56
|
+
return this.getVersionedPayload(suite, sha, pointer, opts?.sha !== undefined);
|
|
64
57
|
}
|
|
65
58
|
async put(suite, lcov, meta) {
|
|
66
59
|
assertSafePathComponent(suite, "suite");
|
|
67
60
|
if (meta === undefined) {
|
|
68
|
-
await this.
|
|
69
|
-
Bucket: this.bucket,
|
|
70
|
-
Key: this.key(suite, "lcov.info"),
|
|
71
|
-
Body: lcov,
|
|
72
|
-
ContentType: "text/plain",
|
|
73
|
-
}));
|
|
61
|
+
await this.putLegacyPayload(suite, lcov);
|
|
74
62
|
return;
|
|
75
63
|
}
|
|
76
64
|
const { sha, branch } = meta;
|
|
77
65
|
assertSafePathComponent(sha, "sha");
|
|
78
66
|
const ts = meta.timestamp ?? new Date().toISOString();
|
|
79
67
|
assertValidTimestamp(ts);
|
|
80
|
-
|
|
68
|
+
const payload = gzipSync(lcov);
|
|
69
|
+
const payloadKey = this.key(suite, "sha", sha, "lcov.info.gz");
|
|
70
|
+
await this.sendS3("put coverage payload", payloadKey, new PutObjectCommand({
|
|
81
71
|
Bucket: this.bucket,
|
|
82
|
-
Key:
|
|
83
|
-
Body:
|
|
72
|
+
Key: payloadKey,
|
|
73
|
+
Body: payload,
|
|
74
|
+
ContentEncoding: "gzip",
|
|
84
75
|
ContentType: "text/plain",
|
|
85
|
-
}));
|
|
86
|
-
if (!(await
|
|
76
|
+
}), { rawBytes: lcov.byteLength, storedBytes: payload.byteLength });
|
|
77
|
+
if (!(await shouldWritePointer(this, suite, branch, ts)))
|
|
87
78
|
return;
|
|
88
|
-
await this.
|
|
89
|
-
Bucket: this.bucket,
|
|
90
|
-
Key: this.key(suite, "branch", encodeBranchName(branch), "latest.json"),
|
|
91
|
-
Body: Buffer.from(JSON.stringify({ sha, timestamp: ts }), "utf8"),
|
|
92
|
-
ContentType: "application/json",
|
|
93
|
-
}));
|
|
79
|
+
await this.putPointer(suite, branch, sha, ts, payloadKey, lcov.byteLength, payload.byteLength);
|
|
94
80
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const resp = (await this.client.send(new GetObjectCommand({
|
|
98
|
-
Bucket: this.bucket,
|
|
99
|
-
Key: this.key(suite, "lcov.info"),
|
|
100
|
-
})));
|
|
101
|
-
return bodyToBuffer(resp.Body);
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
if (isNotFound(err))
|
|
105
|
-
return null;
|
|
106
|
-
throw err;
|
|
107
|
-
}
|
|
81
|
+
sendS3(operation, key, command, details = {}) {
|
|
82
|
+
return sendS3(this.client, this.bucket, operation, key, command, details);
|
|
108
83
|
}
|
|
109
|
-
async
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Key: this.key(suite, "branch", encodeBranchName(branch), "latest.json"),
|
|
114
|
-
})));
|
|
115
|
-
if (resp.Body === undefined)
|
|
116
|
-
return true;
|
|
117
|
-
const body = await bodyToBuffer(resp.Body);
|
|
118
|
-
const current = JSON.parse(body.toString("utf8"));
|
|
119
|
-
return !isNewerTimestamp(current.timestamp, incomingTimestamp);
|
|
84
|
+
async getVersionedPayload(suite, sha, pointer, explicitSha) {
|
|
85
|
+
let candidates;
|
|
86
|
+
if (pointer?.payloadKey !== undefined) {
|
|
87
|
+
candidates = [{ key: pointer.payloadKey, encoding: pointer.encoding }];
|
|
120
88
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
89
|
+
else if (explicitSha) {
|
|
90
|
+
candidates = [
|
|
91
|
+
{ key: this.key(suite, "sha", sha, "lcov.info.gz"), encoding: "gzip" },
|
|
92
|
+
{ key: this.key(suite, "sha", sha, "lcov.info"), encoding: undefined },
|
|
93
|
+
];
|
|
125
94
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.key(suite, "branch", branch, "latest.json"),
|
|
131
|
-
];
|
|
132
|
-
let lastNotFound;
|
|
133
|
-
for (const key of keys) {
|
|
95
|
+
else {
|
|
96
|
+
candidates = [{ key: this.key(suite, "sha", sha, "lcov.info"), encoding: undefined }];
|
|
97
|
+
}
|
|
98
|
+
for (const candidate of candidates) {
|
|
134
99
|
try {
|
|
135
|
-
const resp = (await this.
|
|
100
|
+
const resp = (await this.sendS3("get coverage payload", candidate.key, new GetObjectCommand({
|
|
101
|
+
Bucket: this.bucket,
|
|
102
|
+
Key: candidate.key,
|
|
103
|
+
})));
|
|
136
104
|
const body = await bodyToBuffer(resp.Body);
|
|
137
|
-
return
|
|
105
|
+
return candidate.encoding === "gzip" ? gunzipSync(body) : body;
|
|
138
106
|
}
|
|
139
107
|
catch (err) {
|
|
140
108
|
if (!isNotFound(err))
|
|
141
109
|
throw err;
|
|
142
|
-
lastNotFound = err;
|
|
143
110
|
}
|
|
144
111
|
}
|
|
145
|
-
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
async putLegacyPayload(suite, lcov) {
|
|
115
|
+
const key = this.key(suite, "lcov.info");
|
|
116
|
+
await this.sendS3("put legacy coverage payload", key, new PutObjectCommand({
|
|
117
|
+
Bucket: this.bucket,
|
|
118
|
+
Key: key,
|
|
119
|
+
Body: lcov,
|
|
120
|
+
ContentType: "text/plain",
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
async putPointer(suite, branch, sha, timestamp, payloadKey, rawBytes, storedBytes) {
|
|
124
|
+
const pointerKey = this.key(suite, "branch", encodeBranchName(branch), "latest.json");
|
|
125
|
+
await this.sendS3("put branch pointer", pointerKey, new PutObjectCommand({
|
|
126
|
+
Bucket: this.bucket,
|
|
127
|
+
Key: pointerKey,
|
|
128
|
+
Body: Buffer.from(JSON.stringify({
|
|
129
|
+
sha,
|
|
130
|
+
timestamp,
|
|
131
|
+
payloadKey,
|
|
132
|
+
encoding: "gzip",
|
|
133
|
+
rawBytes,
|
|
134
|
+
storedBytes,
|
|
135
|
+
}), "utf8"),
|
|
136
|
+
ContentType: "application/json",
|
|
137
|
+
}));
|
|
146
138
|
}
|
|
147
139
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coverage-check",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Patch-coverage gate: checks that newly added lines meet per-path coverage thresholds. Supports per-suite LCOV accumulation for conditional CI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonathan Ong",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@aws-sdk/client-s3": "^3.1048.0",
|
|
35
|
+
"@smithy/node-http-handler": "^4.7.7",
|
|
35
36
|
"js-yaml": "^4.1.1",
|
|
36
37
|
"monocart-coverage-reports": "^2.12.11"
|
|
37
38
|
},
|