redlinegate 0.0.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/LICENSE +21 -0
- package/README.md +188 -0
- package/commands/redline-init.md +54 -0
- package/commands/redline-verify.md +69 -0
- package/dist/bin/redline.js +605 -0
- package/dist/bin/redline.js.map +1 -0
- package/dist/commands/exempt.js +45 -0
- package/dist/commands/exempt.js.map +1 -0
- package/dist/commands/init.js +615 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/policy.js +25 -0
- package/dist/commands/policy.js.map +1 -0
- package/dist/commands/remove.js +427 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/review.js +72 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/sync.js +42 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/verify.js +478 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/config/redline-json.js +187 -0
- package/dist/config/redline-json.js.map +1 -0
- package/dist/core/errors.js +25 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git.js +152 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/log.js +22 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/severity.js +16 -0
- package/dist/core/severity.js.map +1 -0
- package/dist/core/version.js +15 -0
- package/dist/core/version.js.map +1 -0
- package/dist/detect/scan.js +79 -0
- package/dist/detect/scan.js.map +1 -0
- package/dist/detect/stack.js +71 -0
- package/dist/detect/stack.js.map +1 -0
- package/dist/enforce/ladder.js +148 -0
- package/dist/enforce/ladder.js.map +1 -0
- package/dist/exempt/parse.js +86 -0
- package/dist/exempt/parse.js.map +1 -0
- package/dist/metrics/options.js +257 -0
- package/dist/metrics/options.js.map +1 -0
- package/dist/metrics/run.js +72 -0
- package/dist/metrics/run.js.map +1 -0
- package/dist/platforms/azure/client.js +53 -0
- package/dist/platforms/azure/client.js.map +1 -0
- package/dist/platforms/azure/index.js +69 -0
- package/dist/platforms/azure/index.js.map +1 -0
- package/dist/platforms/azure/install.js +917 -0
- package/dist/platforms/azure/install.js.map +1 -0
- package/dist/platforms/azure/policy-types.js +70 -0
- package/dist/platforms/azure/policy-types.js.map +1 -0
- package/dist/platforms/azure/verify.js +309 -0
- package/dist/platforms/azure/verify.js.map +1 -0
- package/dist/platforms/detect.js +40 -0
- package/dist/platforms/detect.js.map +1 -0
- package/dist/platforms/github/client.js +49 -0
- package/dist/platforms/github/client.js.map +1 -0
- package/dist/platforms/github/index.js +54 -0
- package/dist/platforms/github/index.js.map +1 -0
- package/dist/platforms/github/install.js +558 -0
- package/dist/platforms/github/install.js.map +1 -0
- package/dist/platforms/github/push.js +102 -0
- package/dist/platforms/github/push.js.map +1 -0
- package/dist/platforms/github/remote.js +36 -0
- package/dist/platforms/github/remote.js.map +1 -0
- package/dist/platforms/github/verify.js +366 -0
- package/dist/platforms/github/verify.js.map +1 -0
- package/dist/platforms/http.js +58 -0
- package/dist/platforms/http.js.map +1 -0
- package/dist/platforms/pull-request-templates.js +157 -0
- package/dist/platforms/pull-request-templates.js.map +1 -0
- package/dist/platforms/remote.js +21 -0
- package/dist/platforms/remote.js.map +1 -0
- package/dist/platforms/resolve.js +69 -0
- package/dist/platforms/resolve.js.map +1 -0
- package/dist/platforms/shape.js +19 -0
- package/dist/platforms/shape.js.map +1 -0
- package/dist/platforms/types.js +24 -0
- package/dist/platforms/types.js.map +1 -0
- package/dist/policy/checks.js +91 -0
- package/dist/policy/checks.js.map +1 -0
- package/dist/policy/diff.js +50 -0
- package/dist/policy/diff.js.map +1 -0
- package/dist/registry/discover.js +74 -0
- package/dist/registry/discover.js.map +1 -0
- package/dist/registry/serialize.js +41 -0
- package/dist/registry/serialize.js.map +1 -0
- package/dist/registry/types.js +2 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/remove/host.js +294 -0
- package/dist/remove/host.js.map +1 -0
- package/dist/render/commands.js +139 -0
- package/dist/render/commands.js.map +1 -0
- package/dist/render/manifest.js +82 -0
- package/dist/render/manifest.js.map +1 -0
- package/dist/render/markers.js +166 -0
- package/dist/render/markers.js.map +1 -0
- package/dist/render/profile.js +23 -0
- package/dist/render/profile.js.map +1 -0
- package/dist/render/standards.js +169 -0
- package/dist/render/standards.js.map +1 -0
- package/dist/render/vendors.js +199 -0
- package/dist/render/vendors.js.map +1 -0
- package/dist/review/engines/api.js +95 -0
- package/dist/review/engines/api.js.map +1 -0
- package/dist/review/engines/embedded.js +20 -0
- package/dist/review/engines/embedded.js.map +1 -0
- package/dist/review/engines/types.js +2 -0
- package/dist/review/engines/types.js.map +1 -0
- package/dist/review/glob.js +29 -0
- package/dist/review/glob.js.map +1 -0
- package/dist/review/prompt.js +45 -0
- package/dist/review/prompt.js.map +1 -0
- package/dist/review/rules.js +25 -0
- package/dist/review/rules.js.map +1 -0
- package/dist/review/schema.js +84 -0
- package/dist/review/schema.js.map +1 -0
- package/dist/review/scope.js +27 -0
- package/dist/review/scope.js.map +1 -0
- package/dist/sarif/map.js +58 -0
- package/dist/sarif/map.js.map +1 -0
- package/dist/sarif/parse.js +88 -0
- package/dist/sarif/parse.js.map +1 -0
- package/dist/sarif/types.js +2 -0
- package/dist/sarif/types.js.map +1 -0
- package/dist/sync/host.js +21 -0
- package/dist/sync/host.js.map +1 -0
- package/dist/sync/plan.js +47 -0
- package/dist/sync/plan.js.map +1 -0
- package/dist/sync/render.js +57 -0
- package/dist/sync/render.js.map +1 -0
- package/dist/sync/run.js +118 -0
- package/dist/sync/run.js.map +1 -0
- package/dist/verify/host.js +52 -0
- package/dist/verify/host.js.map +1 -0
- package/dist/verify/remote.js +146 -0
- package/dist/verify/remote.js.map +1 -0
- package/package.json +52 -0
- package/platforms/azure/gate-template.yml +105 -0
- package/rulesets/redline-org-ruleset.json +46 -0
- package/rulesets/redline-ruleset.json +49 -0
- package/scripts/assign-rule-ids.mjs +129 -0
- package/scripts/build-baseline.mjs +124 -0
- package/scripts/build-correlation.mjs +74 -0
- package/scripts/build-dashboard.mjs +584 -0
- package/scripts/build-digest.mjs +164 -0
- package/scripts/build-inbox.mjs +133 -0
- package/scripts/build-registry.mjs +42 -0
- package/scripts/build-roi.mjs +181 -0
- package/scripts/check-pins.mjs +88 -0
- package/scripts/collect-telemetry.mjs +300 -0
- package/scripts/lib/__tests__/baseline.test.mjs +132 -0
- package/scripts/lib/__tests__/correlate.test.mjs +131 -0
- package/scripts/lib/__tests__/dora.test.mjs +110 -0
- package/scripts/lib/__tests__/exemptions.test.mjs +85 -0
- package/scripts/lib/__tests__/metrics-scanner.test.mjs +65 -0
- package/scripts/lib/__tests__/sarif.test.mjs +125 -0
- package/scripts/lib/__tests__/spend.test.mjs +64 -0
- package/scripts/lib/baseline.mjs +156 -0
- package/scripts/lib/correlate.mjs +125 -0
- package/scripts/lib/dora.mjs +133 -0
- package/scripts/lib/exemptions.mjs +79 -0
- package/scripts/lib/metrics.mjs +172 -0
- package/scripts/lib/rules.mjs +107 -0
- package/scripts/lib/sarif.mjs +108 -0
- package/scripts/lib/spend.mjs +69 -0
- package/scripts/measure-context.mjs +101 -0
- package/scripts/render-self.mjs +18 -0
- package/scripts/score-seeds.mjs +294 -0
- package/scripts/validate.mjs +394 -0
- package/standards/core.md +124 -0
- package/standards/manifest.json +214 -0
- package/standards/stacks/csharp.md +29 -0
- package/standards/stacks/go.md +30 -0
- package/standards/stacks/java.md +30 -0
- package/standards/stacks/javascript.md +42 -0
- package/standards/stacks/kotlin.md +27 -0
- package/standards/stacks/microservices.md +37 -0
- package/standards/stacks/nodejs.md +34 -0
- package/standards/stacks/python.md +29 -0
- package/standards/stacks/react-native.md +29 -0
- package/standards/stacks/react.md +46 -0
- package/standards/stacks/swift.md +26 -0
- package/standards/stacks/terraform.md +27 -0
- package/templates/CODEOWNERS +29 -0
- package/templates/azure/pull_request_template.md +72 -0
- package/templates/github/pull_request_template.md +72 -0
- package/templates/redline.yml +35 -0
- package/templates/repo-context.md +64 -0
- package/workflows/dashboard.yml +119 -0
- package/workflows/inbox.yml +97 -0
- package/workflows/redline-collect.yml +61 -0
- package/workflows/redline-gate.yml +301 -0
- package/workflows/redline-sync.yml +79 -0
- package/workflows/seed-canary.yml +231 -0
- package/workflows/verify-onboarding.yml +115 -0
- package/workflows/weekly-digest.yml +78 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { RedlineError } from "../../core/errors.js";
|
|
2
|
+
const repoPath = (ref) => `/repos/${ref.org}/${ref.repo}`;
|
|
3
|
+
const ok = (status, body, what) => {
|
|
4
|
+
if (status >= 400 || body === null) {
|
|
5
|
+
throw new RedlineError('host', `${what} returned ${status}`);
|
|
6
|
+
}
|
|
7
|
+
return body;
|
|
8
|
+
};
|
|
9
|
+
// Commit a set of files onto a branch without a working copy.
|
|
10
|
+
//
|
|
11
|
+
// Sync runs against hundreds of repositories from one workflow. Cloning each to
|
|
12
|
+
// commit a handful of rendered files would dominate the run and needs write
|
|
13
|
+
// access to a checkout this process does not have, so the git data API does the
|
|
14
|
+
// work: one tree built from the files, one commit, one ref update. It is also
|
|
15
|
+
// atomic in a way a file-at-a-time contents API is not — a run that dies halfway
|
|
16
|
+
// leaves the branch untouched rather than half-updated.
|
|
17
|
+
export async function pushRemoteFiles(client, ref, push) {
|
|
18
|
+
// The base is always the default branch, never whatever the sync branch
|
|
19
|
+
// already points at: a stale sync branch must be re-based onto current main,
|
|
20
|
+
// or it would re-propose changes the repository already merged.
|
|
21
|
+
const baseRef = await client.rest('GET', `${repoPath(ref)}/git/ref/heads/${ref.defaultBranch}`);
|
|
22
|
+
const baseSha = ok(baseRef.status, baseRef.body, `reading heads/${ref.defaultBranch}`).object?.sha;
|
|
23
|
+
if (!baseSha)
|
|
24
|
+
throw new RedlineError('host', `${ref.defaultBranch} has no commit sha`);
|
|
25
|
+
const baseCommit = await client.rest('GET', `${repoPath(ref)}/git/commits/${baseSha}`);
|
|
26
|
+
const baseTree = ok(baseCommit.status, baseCommit.body, 'reading the base commit').tree?.sha;
|
|
27
|
+
if (!baseTree)
|
|
28
|
+
throw new RedlineError('host', 'base commit has no tree');
|
|
29
|
+
const tree = await client.rest('POST', `${repoPath(ref)}/git/trees`, {
|
|
30
|
+
base_tree: baseTree,
|
|
31
|
+
tree: push.files.map((file) => ({
|
|
32
|
+
path: file.path,
|
|
33
|
+
mode: '100644',
|
|
34
|
+
type: 'blob',
|
|
35
|
+
content: file.content,
|
|
36
|
+
})),
|
|
37
|
+
});
|
|
38
|
+
const treeSha = ok(tree.status, tree.body, 'creating the tree').sha;
|
|
39
|
+
if (!treeSha)
|
|
40
|
+
throw new RedlineError('host', 'tree creation returned no sha');
|
|
41
|
+
// An identical tree means the repository already carries exactly what this
|
|
42
|
+
// run would push. Committing it anyway would open a pull request with an
|
|
43
|
+
// empty diff on every scheduled run, on every already-current repository.
|
|
44
|
+
if (treeSha === baseTree)
|
|
45
|
+
return { commit: null, branch: push.branch };
|
|
46
|
+
const commit = await client.rest('POST', `${repoPath(ref)}/git/commits`, {
|
|
47
|
+
message: push.message,
|
|
48
|
+
tree: treeSha,
|
|
49
|
+
parents: [baseSha],
|
|
50
|
+
});
|
|
51
|
+
const commitSha = ok(commit.status, commit.body, 'creating the commit').sha;
|
|
52
|
+
if (!commitSha)
|
|
53
|
+
throw new RedlineError('host', 'commit creation returned no sha');
|
|
54
|
+
// Create the branch, or force it onto the new commit when a previous run left
|
|
55
|
+
// one. Force is safe here and only here: the branch is Redline's own, named
|
|
56
|
+
// by Redline, and its only content is a re-render of the same files.
|
|
57
|
+
const created = await client.rest('POST', `${repoPath(ref)}/git/refs`, {
|
|
58
|
+
ref: `refs/heads/${push.branch}`,
|
|
59
|
+
sha: commitSha,
|
|
60
|
+
});
|
|
61
|
+
if (created.status === 422) {
|
|
62
|
+
const updated = await client.rest('PATCH', `${repoPath(ref)}/git/refs/heads/${push.branch}`, { sha: commitSha, force: true });
|
|
63
|
+
ok(updated.status, updated.body ?? {}, `updating ${push.branch}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
ok(created.status, created.body ?? {}, `creating ${push.branch}`);
|
|
67
|
+
}
|
|
68
|
+
return { commit: commitSha, branch: push.branch };
|
|
69
|
+
}
|
|
70
|
+
// Open a pull request for a branch, or return the one already open for it.
|
|
71
|
+
//
|
|
72
|
+
// Idempotence is the whole requirement: sync runs on a schedule, and a target
|
|
73
|
+
// whose previous pull request is still unmerged must get an updated branch, not
|
|
74
|
+
// a second pull request competing with the first.
|
|
75
|
+
export async function openOrUpdatePullRequest(client, ref, pr) {
|
|
76
|
+
const existing = await client.rest('GET', `${repoPath(ref)}/pulls?state=open&head=${ref.org}:${pr.branch}`);
|
|
77
|
+
const open = existing.status < 400 ? (existing.body ?? []) : [];
|
|
78
|
+
if (open.length > 0 && open[0]) {
|
|
79
|
+
const found = open[0];
|
|
80
|
+
// The branch already carries the new commit; refresh the body so a reader
|
|
81
|
+
// sees the version this run proposes rather than the one it replaced.
|
|
82
|
+
await client.rest('PATCH', `${repoPath(ref)}/pulls/${found.number}`, {
|
|
83
|
+
title: pr.title,
|
|
84
|
+
body: pr.body,
|
|
85
|
+
});
|
|
86
|
+
return { number: found.number, url: found.html_url, created: false };
|
|
87
|
+
}
|
|
88
|
+
const created = await client.rest('POST', `${repoPath(ref)}/pulls`, { title: pr.title, body: pr.body, head: pr.branch, base: ref.defaultBranch });
|
|
89
|
+
const body = ok(created.status, created.body, 'opening the pull request');
|
|
90
|
+
if (typeof body.number !== 'number') {
|
|
91
|
+
throw new RedlineError('host', 'pull request creation returned no number');
|
|
92
|
+
}
|
|
93
|
+
if (pr.labels.length > 0) {
|
|
94
|
+
// Labels are a nicety. A repository that has not created them yet must not
|
|
95
|
+
// cost the pull request that carries the standards change.
|
|
96
|
+
await client.rest(`POST`, `${repoPath(ref)}/issues/${body.number}/labels`, {
|
|
97
|
+
labels: pr.labels,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { number: body.number, url: body.html_url ?? '', created: true };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../../cli/platforms/github/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RedlineError } from "../../core/errors.js";
|
|
2
|
+
import { parseRemoteConfig } from "../remote.js";
|
|
3
|
+
const repoPath = (ref) => `/repos/${ref.org}/${ref.repo}`;
|
|
4
|
+
// A file read at a ref, or null when the repository does not carry it. The
|
|
5
|
+
// distinction matters everywhere this is used: "no .redline.json" means not
|
|
6
|
+
// onboarded, and reporting that as a read failure would turn every un-onboarded
|
|
7
|
+
// repository in the estate into an error.
|
|
8
|
+
export async function readRemoteFile(client, ref, path) {
|
|
9
|
+
const response = await client.rest('GET', `${repoPath(ref)}/contents/${path}?ref=${encodeURIComponent(ref.defaultBranch)}`);
|
|
10
|
+
if (response.status === 404)
|
|
11
|
+
return null;
|
|
12
|
+
if (response.status >= 400) {
|
|
13
|
+
throw new RedlineError('host', `${ref.org}/${ref.repo}: reading ${path} returned ${response.status}`);
|
|
14
|
+
}
|
|
15
|
+
const body = response.body;
|
|
16
|
+
if (!body?.content || body.encoding !== 'base64') {
|
|
17
|
+
// A path that resolves to a directory comes back as an array, and a file
|
|
18
|
+
// over 1MB comes back with an empty content field. Both are real states
|
|
19
|
+
// that would otherwise decode to an empty string and parse as invalid.
|
|
20
|
+
throw new RedlineError('host', `${ref.org}/${ref.repo}: ${path} is not a readable file (no base64 content)`);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
content: Buffer.from(body.content, 'base64').toString('utf8'),
|
|
24
|
+
sha: body.sha ?? '',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function readRemoteConfig(client, ref) {
|
|
28
|
+
const file = await readRemoteFile(client, ref, '.redline.json');
|
|
29
|
+
if (!file)
|
|
30
|
+
return { config: null, ref: ref.defaultBranch };
|
|
31
|
+
return {
|
|
32
|
+
config: parseRemoteConfig(file.content, `${ref.org}/${ref.repo}`),
|
|
33
|
+
ref: ref.defaultBranch,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../../cli/platforms/github/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAA2B,MAAM,cAAc,CAAC;AAI1E,MAAM,QAAQ,GAAG,CAAC,GAAY,EAAU,EAAE,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;AAQ3E,2EAA2E;AAC3E,4EAA4E;AAC5E,gFAAgF;AAChF,0CAA0C;AAC1C,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAkC,EAClC,GAAY,EACZ,IAAY;IAEZ,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,KAAK,EACL,GAAG,QAAQ,CAAC,GAAG,CAAC,aAAa,IAAI,QAAQ,kBAAkB,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CACjF,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QAC3B,MAAM,IAAI,YAAY,CACpB,MAAM,EACN,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,aAAa,IAAI,aAAa,QAAQ,CAAC,MAAM,EAAE,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjD,yEAAyE;QACzE,wEAAwE;QACxE,uEAAuE;QACvE,MAAM,IAAI,YAAY,CACpB,MAAM,EACN,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,6CAA6C,CAC7E,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC7D,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE;KACpB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAkC,EAClC,GAAY;IAEZ,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAChE,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,aAAa,EAAE,CAAC;IAC3D,OAAO;QACL,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACjE,GAAG,EAAE,GAAG,CAAC,aAAa;KACvB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { RedlineError } from "../../core/errors.js";
|
|
4
|
+
import { REQUIRED_CHECK, RULESET_NAME } from "./install.js";
|
|
5
|
+
import { createHostShapeError, isNonNullObject } from "../shape.js";
|
|
6
|
+
const hostShapeError = createHostShapeError('GitHub');
|
|
7
|
+
function assertOk(status, path) {
|
|
8
|
+
if (status < 200 || status >= 300) {
|
|
9
|
+
throw new RedlineError('host', `GitHub returned HTTP ${status} reading ${path}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function parseRulesetList(body) {
|
|
13
|
+
if (!Array.isArray(body))
|
|
14
|
+
throw hostShapeError('the rulesets list');
|
|
15
|
+
return body.map((item) => {
|
|
16
|
+
if (!isNonNullObject(item) || typeof item['id'] !== 'number' || typeof item['name'] !== 'string') {
|
|
17
|
+
throw hostShapeError('a ruleset summary');
|
|
18
|
+
}
|
|
19
|
+
return { id: item['id'], name: item['name'] };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
// `active`, `evaluate` or `disabled`. Anything but `active` means the ruleset
|
|
23
|
+
// is readable and not applying — the rules come back unchanged, so nothing
|
|
24
|
+
// else in this file can tell the difference. Absent is read as active: the
|
|
25
|
+
// field has always been present on a real ruleset, and guessing "not in force"
|
|
26
|
+
// from a shape surprise would fail a healthy repository.
|
|
27
|
+
function parseRulesetEnforcement(body) {
|
|
28
|
+
if (!isNonNullObject(body))
|
|
29
|
+
throw hostShapeError('a ruleset detail');
|
|
30
|
+
const enforcement = body['enforcement'];
|
|
31
|
+
return typeof enforcement === 'string' ? enforcement : 'active';
|
|
32
|
+
}
|
|
33
|
+
function parseRulesetRules(body) {
|
|
34
|
+
if (!isNonNullObject(body) || !Array.isArray(body['rules'])) {
|
|
35
|
+
throw hostShapeError('a ruleset detail');
|
|
36
|
+
}
|
|
37
|
+
return body['rules'].map((rule) => {
|
|
38
|
+
if (!isNonNullObject(rule) || typeof rule['type'] !== 'string') {
|
|
39
|
+
throw hostShapeError('a ruleset rule');
|
|
40
|
+
}
|
|
41
|
+
const parameters = rule['parameters'];
|
|
42
|
+
return {
|
|
43
|
+
type: rule['type'],
|
|
44
|
+
...(isNonNullObject(parameters) ? { parameters } : {}),
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function parsePullRequestParams(parameters) {
|
|
49
|
+
const p = parameters ?? {};
|
|
50
|
+
return {
|
|
51
|
+
requiredApprovingReviewCount: typeof p['required_approving_review_count'] === 'number'
|
|
52
|
+
? p['required_approving_review_count']
|
|
53
|
+
: 0,
|
|
54
|
+
dismissStaleReviewsOnPush: p['dismiss_stale_reviews_on_push'] === true,
|
|
55
|
+
requireCodeOwnerReview: p['require_code_owner_review'] === true,
|
|
56
|
+
requiredReviewThreadResolution: p['required_review_thread_resolution'] === true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function parseRequiredCheckContexts(parameters) {
|
|
60
|
+
const checks = parameters?.['required_status_checks'];
|
|
61
|
+
if (checks === undefined)
|
|
62
|
+
return [];
|
|
63
|
+
if (!Array.isArray(checks))
|
|
64
|
+
throw hostShapeError('required status checks');
|
|
65
|
+
return checks.map((c) => {
|
|
66
|
+
if (!isNonNullObject(c) || typeof c['context'] !== 'string') {
|
|
67
|
+
throw hostShapeError('a required status check');
|
|
68
|
+
}
|
|
69
|
+
return c['context'];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function parsePullRequestHeadSha(body) {
|
|
73
|
+
const head = isNonNullObject(body) ? body['head'] : undefined;
|
|
74
|
+
if (!isNonNullObject(head) || typeof head['sha'] !== 'string') {
|
|
75
|
+
throw hostShapeError('a pull request');
|
|
76
|
+
}
|
|
77
|
+
return head['sha'];
|
|
78
|
+
}
|
|
79
|
+
function parseCheckRunNames(body) {
|
|
80
|
+
if (!isNonNullObject(body) || !Array.isArray(body['check_runs'])) {
|
|
81
|
+
throw hostShapeError('a check-runs list');
|
|
82
|
+
}
|
|
83
|
+
return body['check_runs'].map((run) => {
|
|
84
|
+
if (!isNonNullObject(run) || typeof run['name'] !== 'string') {
|
|
85
|
+
throw hostShapeError('a check run');
|
|
86
|
+
}
|
|
87
|
+
return run['name'];
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// null means GitHub did not report the block at all, which is not the same
|
|
91
|
+
// thing as reporting it empty — see readSecurityState.
|
|
92
|
+
function parseSecurityAnalysisStatuses(body) {
|
|
93
|
+
if (!isNonNullObject(body))
|
|
94
|
+
throw hostShapeError('a repository');
|
|
95
|
+
const analysis = body['security_and_analysis'];
|
|
96
|
+
if (analysis === undefined)
|
|
97
|
+
return null;
|
|
98
|
+
if (!isNonNullObject(analysis))
|
|
99
|
+
throw hostShapeError('security_and_analysis');
|
|
100
|
+
const statuses = {};
|
|
101
|
+
for (const [key, value] of Object.entries(analysis)) {
|
|
102
|
+
if (isNonNullObject(value) && typeof value['status'] === 'string') {
|
|
103
|
+
statuses[key] = value['status'];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return statuses;
|
|
107
|
+
}
|
|
108
|
+
function parsePullRequestNumbers(body) {
|
|
109
|
+
if (!Array.isArray(body))
|
|
110
|
+
throw hostShapeError('the pull request list');
|
|
111
|
+
return body.map((item) => {
|
|
112
|
+
if (!isNonNullObject(item) || typeof item['number'] !== 'number') {
|
|
113
|
+
throw hostShapeError('a pull request summary');
|
|
114
|
+
}
|
|
115
|
+
return item['number'];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// The caller workflow `installGate` writes. Its job id is half of the required
|
|
119
|
+
// check name — templates/redline.yml carries a DO NOT RENAME THE JOB warning
|
|
120
|
+
// for exactly this reason — so reading it back is what separates "the gate has
|
|
121
|
+
// not run on this pull request yet" from "nothing here can ever publish that
|
|
122
|
+
// check".
|
|
123
|
+
export const CALLER_WORKFLOW = '.github/workflows/redline.yml';
|
|
124
|
+
// The other half: whatever REQUIRED_CHECK puts after the separator, which is
|
|
125
|
+
// the aggregate job id inside workflows/redline-gate.yml. Derived rather than
|
|
126
|
+
// re-typed, so a change to the exported constant carries through.
|
|
127
|
+
const CHECK_SEPARATOR = ' / ';
|
|
128
|
+
const AGGREGATE_JOB = REQUIRED_CHECK.slice(REQUIRED_CHECK.indexOf(CHECK_SEPARATOR) + CHECK_SEPARATOR.length);
|
|
129
|
+
// Still not a YAML parse — this file is Redline's own, `init` rewrites it
|
|
130
|
+
// wholesale, and a zero-runtime-dependency CLI does not gain a YAML parser for
|
|
131
|
+
// one question. It is structural rather than shape-matched, which is a
|
|
132
|
+
// different thing: the scan finds the `jobs:` mapping and reads job ids from
|
|
133
|
+
// inside it and nowhere else.
|
|
134
|
+
//
|
|
135
|
+
// The version that pattern-matched a two-space key anywhere in the file took
|
|
136
|
+
// the last one it had seen before the `uses:` line, which in the shipped
|
|
137
|
+
// template is ` pull_request:` from the `on:` block. An inline comment on the
|
|
138
|
+
// job-id line — a plausible edit, given the DO NOT RENAME THE JOB banner four
|
|
139
|
+
// lines above it — was enough to fail the gate with "publishes pull_request /
|
|
140
|
+
// gate ... rename the job back", naming a job that exists nowhere about a job
|
|
141
|
+
// id that was already correct. Where this scan cannot attribute a name it
|
|
142
|
+
// returns null, whose message says the file could not be read for one; a
|
|
143
|
+
// message that admits it could not tell is honest, a fabricated job name is
|
|
144
|
+
// not.
|
|
145
|
+
const KEY = /^\s*(?:(["'])([\w.-]+)\1|([\w.-]+))\s*:(.*)$/;
|
|
146
|
+
const USES_THE_GATE = /^\s*uses:\s*["']?\S*redline-gate\.yml@/;
|
|
147
|
+
const indentOf = (line) => line.length - line.trimStart().length;
|
|
148
|
+
const skippable = (line) => line.trim() === '' || line.trimStart().startsWith('#');
|
|
149
|
+
const keyName = (line) => {
|
|
150
|
+
const match = KEY.exec(line);
|
|
151
|
+
return match?.[2] ?? match?.[3] ?? null;
|
|
152
|
+
};
|
|
153
|
+
function lines(body) {
|
|
154
|
+
return body.split('\n').map((line) => (line.endsWith('\r') ? line.slice(0, -1) : line));
|
|
155
|
+
}
|
|
156
|
+
// A workflow nothing triggers on a pull request publishes nothing, however
|
|
157
|
+
// correctly its job is named — and the ruleset still requires the check it
|
|
158
|
+
// will never send. `on:` is quoted in some hand-edited workflows because bare
|
|
159
|
+
// `on` is a YAML boolean, and the trigger may be inline (`on: [pull_request]`)
|
|
160
|
+
// or a key in the block below it.
|
|
161
|
+
export function triggersOnPullRequest(body) {
|
|
162
|
+
const all = lines(body);
|
|
163
|
+
for (let i = 0; i < all.length; i += 1) {
|
|
164
|
+
const line = all[i] ?? '';
|
|
165
|
+
if (skippable(line) || indentOf(line) !== 0 || keyName(line) !== 'on')
|
|
166
|
+
continue;
|
|
167
|
+
const inline = KEY.exec(line)?.[4] ?? '';
|
|
168
|
+
if (inline.trim() !== '')
|
|
169
|
+
return inline.includes('pull_request');
|
|
170
|
+
for (const rest of all.slice(i + 1)) {
|
|
171
|
+
if (skippable(rest))
|
|
172
|
+
continue;
|
|
173
|
+
if (indentOf(rest) === 0)
|
|
174
|
+
break;
|
|
175
|
+
if (rest.includes('pull_request'))
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
export function callerJobId(body) {
|
|
183
|
+
const all = lines(body);
|
|
184
|
+
let jobsIndent = null;
|
|
185
|
+
let jobIdIndent = null;
|
|
186
|
+
let jobId = null;
|
|
187
|
+
for (const line of all) {
|
|
188
|
+
if (skippable(line))
|
|
189
|
+
continue;
|
|
190
|
+
const indent = indentOf(line);
|
|
191
|
+
if (jobsIndent === null) {
|
|
192
|
+
// Flow style (`jobs: {redline-gate: …}`) leaves content after the colon
|
|
193
|
+
// and is deliberately not attributed: `init` does not write it, and a
|
|
194
|
+
// truthful "could not read a job" beats a guess.
|
|
195
|
+
if (keyName(line) === 'jobs' && (KEY.exec(line)?.[4] ?? '').trim() === '')
|
|
196
|
+
jobsIndent = indent;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// Back out to the level of `jobs:` or above and the block is over, so no
|
|
200
|
+
// later key can be mistaken for a job id.
|
|
201
|
+
if (indent <= jobsIndent)
|
|
202
|
+
break;
|
|
203
|
+
if (USES_THE_GATE.test(line) && jobId !== null)
|
|
204
|
+
return jobId;
|
|
205
|
+
const name = keyName(line);
|
|
206
|
+
if (name === null)
|
|
207
|
+
continue;
|
|
208
|
+
// The first key inside the block fixes the job-id column; only keys at
|
|
209
|
+
// exactly that column are job ids, so `uses:` and `with:` below one are
|
|
210
|
+
// never mistaken for another job.
|
|
211
|
+
if (jobIdIndent === null)
|
|
212
|
+
jobIdIndent = indent;
|
|
213
|
+
if (indent === jobIdIndent)
|
|
214
|
+
jobId = name;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// The same parse for a local file and a remote read. Sharing it is the point:
|
|
219
|
+
// a remote verify that re-derived "what does this file publish" independently
|
|
220
|
+
// would eventually disagree with the local one, and the two answers are the
|
|
221
|
+
// difference between a repository being reported healthy and being reported
|
|
222
|
+
// broken.
|
|
223
|
+
export function machineryFromBody(body) {
|
|
224
|
+
const base = { path: CALLER_WORKFLOW, expected: REQUIRED_CHECK };
|
|
225
|
+
if (body === null)
|
|
226
|
+
return { ...base, present: false, publishes: null };
|
|
227
|
+
const jobId = triggersOnPullRequest(body) ? callerJobId(body) : null;
|
|
228
|
+
return {
|
|
229
|
+
...base,
|
|
230
|
+
present: true,
|
|
231
|
+
publishes: jobId === null ? null : `${jobId}${CHECK_SEPARATOR}${AGGREGATE_JOB}`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
export function createGitHubVerify(client) {
|
|
235
|
+
const repoPath = (ref) => `/repos/${ref.org}/${ref.repo}`;
|
|
236
|
+
return {
|
|
237
|
+
async readPolicy(ref) {
|
|
238
|
+
// Status is checked before the body is parsed, as azure/verify.ts does
|
|
239
|
+
// in all four of its methods. Without it a 403 error body ({"message":
|
|
240
|
+
// "Forbidden"}) fails the shape check instead, and the operator is sent
|
|
241
|
+
// after a host bug when the real problem is a token scope.
|
|
242
|
+
const listPath = `${repoPath(ref)}/rulesets`;
|
|
243
|
+
const list = await client.rest('GET', listPath);
|
|
244
|
+
assertOk(list.status, listPath);
|
|
245
|
+
const mine = parseRulesetList(list.body).find((r) => r.name === RULESET_NAME);
|
|
246
|
+
if (!mine)
|
|
247
|
+
return null;
|
|
248
|
+
const detailPath = `${repoPath(ref)}/rulesets/${mine.id}`;
|
|
249
|
+
const detail = await client.rest('GET', detailPath);
|
|
250
|
+
assertOk(detail.status, detailPath);
|
|
251
|
+
const rules = parseRulesetRules(detail.body);
|
|
252
|
+
const prParams = parsePullRequestParams(rules.find((r) => r.type === 'pull_request')?.parameters);
|
|
253
|
+
const checksRule = rules.find((r) => r.type === 'required_status_checks');
|
|
254
|
+
const enforcement = parseRulesetEnforcement(detail.body);
|
|
255
|
+
return {
|
|
256
|
+
requiredApprovals: prParams.requiredApprovingReviewCount,
|
|
257
|
+
dismissStaleReviews: prParams.dismissStaleReviewsOnPush,
|
|
258
|
+
requireCodeOwnerReview: prParams.requireCodeOwnerReview,
|
|
259
|
+
requireThreadResolution: prParams.requiredReviewThreadResolution,
|
|
260
|
+
requiredChecks: parseRequiredCheckContexts(checksRule?.parameters),
|
|
261
|
+
blocking: checksRule !== undefined,
|
|
262
|
+
...(enforcement === 'active'
|
|
263
|
+
? {}
|
|
264
|
+
: {
|
|
265
|
+
notEnforcedReason: `the ${RULESET_NAME} ruleset is set to "${enforcement}" rather than "active", so none of ` +
|
|
266
|
+
'its rules apply and every setting below is readable but inert',
|
|
267
|
+
}),
|
|
268
|
+
// No unownedSettings: the ruleset read here is the one named `Redline`
|
|
269
|
+
// and `applyPolicy` writes every field above into it, so each one is
|
|
270
|
+
// Redline's own and `verify` may hold the repository to all of them.
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
readGateMachinery(cwd) {
|
|
274
|
+
const abs = join(cwd, CALLER_WORKFLOW);
|
|
275
|
+
if (!existsSync(abs))
|
|
276
|
+
return machineryFromBody(null);
|
|
277
|
+
// A local read that cannot be completed — a directory at the path, a
|
|
278
|
+
// file the process cannot open — is a finding about this repository, not
|
|
279
|
+
// an internal defect. Unguarded it escaped verify() as "redline failed
|
|
280
|
+
// unexpectedly" with the host exit code, for a read that never left the
|
|
281
|
+
// machine.
|
|
282
|
+
let body;
|
|
283
|
+
try {
|
|
284
|
+
body = readFileSync(abs, 'utf8');
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
throw new RedlineError('failed', `cannot read ${CALLER_WORKFLOW}: ${error instanceof Error ? error.message : String(error)}`, 'restore it from redline init, or make it readable');
|
|
288
|
+
}
|
|
289
|
+
return machineryFromBody(body);
|
|
290
|
+
},
|
|
291
|
+
async readReportedCheckNames(ref, pr) {
|
|
292
|
+
const prPath = `${repoPath(ref)}/pulls/${pr}`;
|
|
293
|
+
const detail = await client.rest('GET', prPath);
|
|
294
|
+
assertOk(detail.status, prPath);
|
|
295
|
+
const sha = parsePullRequestHeadSha(detail.body);
|
|
296
|
+
const runsPath = `${repoPath(ref)}/commits/${sha}/check-runs?per_page=100`;
|
|
297
|
+
const runs = await client.rest('GET', runsPath);
|
|
298
|
+
assertOk(runs.status, runsPath);
|
|
299
|
+
return parseCheckRunNames(runs.body);
|
|
300
|
+
},
|
|
301
|
+
async readSecurityState(ref) {
|
|
302
|
+
const path = repoPath(ref);
|
|
303
|
+
const repo = await client.rest('GET', path);
|
|
304
|
+
assertOk(repo.status, path);
|
|
305
|
+
// GitHub omits security_and_analysis entirely for a requester without
|
|
306
|
+
// admin permission. "This token cannot see it" is not "it is off", and
|
|
307
|
+
// the difference matters: `denied` is the status that files pending-admin
|
|
308
|
+
// work and rewrites .redline.json, so reading an invisible setting as a
|
|
309
|
+
// refusal let a write-but-not-admin re-run overwrite a correct record
|
|
310
|
+
// with a false one. This is an INDETERMINATE read, not a definite one —
|
|
311
|
+
// GitHub does not say the feature is absent, only that this token
|
|
312
|
+
// cannot see it — so it degrades to `unknown`, not `unsupported`.
|
|
313
|
+
// `unsupported` is reserved for a definite "this does not exist here"
|
|
314
|
+
// answer (Azure's Advanced Security-unlicensed 404 is the one adapter
|
|
315
|
+
// that has one); conflating the two would let a plain `redline verify`
|
|
316
|
+
// pass a repository nobody has actually confirmed, since `unsupported`
|
|
317
|
+
// no longer fails it on its own (see cli/commands/verify.ts). Absent
|
|
318
|
+
// block -> unknown, which isPending excludes exactly like unsupported;
|
|
319
|
+
// present but not enabled -> denied, as before.
|
|
320
|
+
const statuses = parseSecurityAnalysisStatuses(repo.body);
|
|
321
|
+
const invisible = statuses === null;
|
|
322
|
+
const stateFor = (key) => invisible ? 'unknown' : statuses[key] === 'enabled' ? 'applied' : 'denied';
|
|
323
|
+
const note = invisible ? ' (not visible to this token)' : '';
|
|
324
|
+
// Dependabot alerts live on their own endpoint, and it answers with a
|
|
325
|
+
// status rather than a body: 204 enabled, 404 disabled (the repository
|
|
326
|
+
// read above already proved the repository itself resolves, so a 404
|
|
327
|
+
// here is about the feature, not the path). 403 is the one case where
|
|
328
|
+
// nothing was learned — an indeterminate read is not an answer, and
|
|
329
|
+
// `denied` is the status that files work against an administrator. Same
|
|
330
|
+
// reasoning as the block above: this is indeterminate, not a definite
|
|
331
|
+
// "not available here", so it degrades to `unknown`. Any other non-2xx
|
|
332
|
+
// is a host failure, same as everywhere else in this file.
|
|
333
|
+
const alertsPath = `${path}/vulnerability-alerts`;
|
|
334
|
+
const alerts = await client.rest('GET', alertsPath);
|
|
335
|
+
if (alerts.status !== 404 && alerts.status !== 403)
|
|
336
|
+
assertOk(alerts.status, alertsPath);
|
|
337
|
+
const alertsStatus = alerts.status === 403 ? 'unknown' : alerts.status === 404 ? 'denied' : 'applied';
|
|
338
|
+
return {
|
|
339
|
+
outcomes: [
|
|
340
|
+
{
|
|
341
|
+
capability: 'secret-scanning',
|
|
342
|
+
status: stateFor('secret_scanning'),
|
|
343
|
+
detail: `secret scanning${note}`,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
capability: 'push-protection',
|
|
347
|
+
status: stateFor('secret_scanning_push_protection'),
|
|
348
|
+
detail: `secret scanning push protection${note}`,
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
capability: 'dependency-alerts',
|
|
352
|
+
status: alertsStatus,
|
|
353
|
+
detail: `dependabot alerts (vulnerability alerts)${alertsStatus === 'unknown' ? ' (not visible to this token)' : ''}`,
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
};
|
|
357
|
+
},
|
|
358
|
+
async latestPullRequestNumber(ref) {
|
|
359
|
+
const path = `${repoPath(ref)}/pulls?state=all&per_page=1`;
|
|
360
|
+
const list = await client.rest('GET', path);
|
|
361
|
+
assertOk(list.status, path);
|
|
362
|
+
return parsePullRequestNumbers(list.body)[0] ?? null;
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../cli/platforms/github/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { RedlineError } from "../core/errors.js";
|
|
2
|
+
const IDEMPOTENT = new Set(['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD']);
|
|
3
|
+
// 429 is pre-execution (rate limiting), so any method may retry; a 5xx may have
|
|
4
|
+
// committed the request server-side, so only idempotent methods retry — a retried
|
|
5
|
+
// POST would duplicate the PR/policy it created.
|
|
6
|
+
const RETRYABLE = (method, status) => status === 429 || (status >= 500 && IDEMPOTENT.has(method.toUpperCase()));
|
|
7
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
8
|
+
export function createHttp(baseUrl, headers, opts = {}) {
|
|
9
|
+
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
10
|
+
const retries = opts.retries ?? 3;
|
|
11
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
12
|
+
return {
|
|
13
|
+
async request(method, path, body) {
|
|
14
|
+
const url = path.startsWith('http') ? path : `${baseUrl}${path}`;
|
|
15
|
+
const init = { method, headers: { ...headers } };
|
|
16
|
+
if (body !== undefined) {
|
|
17
|
+
init.body = JSON.stringify(body);
|
|
18
|
+
init.headers = { ...headers, 'content-type': 'application/json' };
|
|
19
|
+
}
|
|
20
|
+
let lastStatus = 0;
|
|
21
|
+
for (let attempt = 0; attempt < retries; attempt += 1) {
|
|
22
|
+
let response;
|
|
23
|
+
try {
|
|
24
|
+
response = await doFetch(url, init);
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
if (attempt === retries - 1) {
|
|
28
|
+
throw new RedlineError('host', `request to ${url} failed: ${String(cause)}`);
|
|
29
|
+
}
|
|
30
|
+
await sleep(2 ** attempt * 200);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (RETRYABLE(method, response.status)) {
|
|
34
|
+
lastStatus = response.status;
|
|
35
|
+
if (attempt === retries - 1)
|
|
36
|
+
break;
|
|
37
|
+
await sleep(2 ** attempt * 200);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
|
41
|
+
return { status: response.status, body: null };
|
|
42
|
+
}
|
|
43
|
+
const text = await response.text();
|
|
44
|
+
if (text === '') {
|
|
45
|
+
return { status: response.status, body: null };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return { status: response.status, body: JSON.parse(text) };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new RedlineError('host', `request to ${url} returned ${response.status} with a body that was not JSON`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new RedlineError('host', `request to ${url} kept returning ${lastStatus}`);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../cli/platforms/http.ts"],"names":[],"mappings":"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"}
|