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,148 @@
|
|
|
1
|
+
// The enforcement ladder.
|
|
2
|
+
//
|
|
3
|
+
// Enforcement was binary: advisory by default, blocking with `--blocking`.
|
|
4
|
+
// Neither end works across hundreds of repositories. Rolling blocking to all of
|
|
5
|
+
// them in one step is not achievable; leaving everything advisory means the
|
|
6
|
+
// organisation can never state a guarantee about any of them.
|
|
7
|
+
//
|
|
8
|
+
// Four rungs, and a repository climbs on recorded evidence rather than on
|
|
9
|
+
// assertion. Two decisions the roadmap left open are settled here, and both are
|
|
10
|
+
// settled toward the same principle: **the safe direction never needs permission.**
|
|
11
|
+
export const RUNGS = ['observe', 'warn', 'block-blocker', 'block-high'];
|
|
12
|
+
export const RUNG_INDEX = {
|
|
13
|
+
observe: 0,
|
|
14
|
+
warn: 1,
|
|
15
|
+
'block-blocker': 2,
|
|
16
|
+
'block-high': 3,
|
|
17
|
+
};
|
|
18
|
+
export const isRung = (value) => typeof value === 'string' && RUNGS.includes(value);
|
|
19
|
+
export const BEHAVIOUR = {
|
|
20
|
+
observe: {
|
|
21
|
+
blocks: false,
|
|
22
|
+
description: 'Findings are reported and recorded. Nothing is blocked, including a BLOCKER.',
|
|
23
|
+
},
|
|
24
|
+
warn: {
|
|
25
|
+
blocks: false,
|
|
26
|
+
description: 'Findings are reported as warnings on the pull request. Still nothing is blocked, but the check is visible in the merge box rather than buried in a log.',
|
|
27
|
+
},
|
|
28
|
+
'block-blocker': {
|
|
29
|
+
blocks: 'BLOCKER',
|
|
30
|
+
description: 'A BLOCKER finding stops the merge. HIGH and SUGGESTION are reported.',
|
|
31
|
+
},
|
|
32
|
+
'block-high': {
|
|
33
|
+
blocks: 'HIGH',
|
|
34
|
+
description: 'A BLOCKER or a HIGH stops the merge. The strictest rung, and the one that needs the most trust in the rule set.',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export const REQUIREMENTS = {
|
|
38
|
+
// Nothing to earn: observe is where a repository starts.
|
|
39
|
+
observe: { minSeedRecall: null, minActedOnRate: null, minSampleSize: 0, maxFalsePositives: null },
|
|
40
|
+
// Warning costs nobody a merge, so it asks only that the reviewer is running.
|
|
41
|
+
warn: { minSeedRecall: null, minActedOnRate: null, minSampleSize: 5, maxFalsePositives: null },
|
|
42
|
+
// The first rung that can stop a merge, and the first that needs proof the
|
|
43
|
+
// reviewer both catches real defects and is not flagging correct code.
|
|
44
|
+
'block-blocker': {
|
|
45
|
+
minSeedRecall: 1,
|
|
46
|
+
minActedOnRate: 0.6,
|
|
47
|
+
minSampleSize: 20,
|
|
48
|
+
maxFalsePositives: 0,
|
|
49
|
+
},
|
|
50
|
+
// Blocking on HIGH means blocking on judgement calls, so the bar for people
|
|
51
|
+
// actually agreeing with the findings is materially higher.
|
|
52
|
+
'block-high': {
|
|
53
|
+
minSeedRecall: 1,
|
|
54
|
+
minActedOnRate: 0.8,
|
|
55
|
+
minSampleSize: 50,
|
|
56
|
+
maxFalsePositives: 0,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
export function nextRung(current) {
|
|
60
|
+
return RUNGS[RUNG_INDEX[current] + 1] ?? null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* May this repository move to `to`?
|
|
64
|
+
*
|
|
65
|
+
* **Demotion never needs evidence.** Moving down is always allowed and always
|
|
66
|
+
* immediate: a repository whose gate is misfiring at 3am must be able to step
|
|
67
|
+
* back without waiting for anyone, and a ladder that made the safe direction hard
|
|
68
|
+
* would be switched off entirely rather than stepped down.
|
|
69
|
+
*
|
|
70
|
+
* **A market floor may raise a repository's minimum, never lower it below its
|
|
71
|
+
* own choice.** Markets have different regulators and different appetites, so the
|
|
72
|
+
* ladder is per repository with a per-market floor — a market may insist on more
|
|
73
|
+
* than a repository wants, and may not force one below where it has already got to.
|
|
74
|
+
*/
|
|
75
|
+
export function canPromote(from, to, evidence, marketFloor = 'observe') {
|
|
76
|
+
const blockers = [];
|
|
77
|
+
if (RUNG_INDEX[to] < RUNG_INDEX[from]) {
|
|
78
|
+
if (RUNG_INDEX[to] < RUNG_INDEX[marketFloor]) {
|
|
79
|
+
return {
|
|
80
|
+
to,
|
|
81
|
+
eligible: false,
|
|
82
|
+
blockers: [
|
|
83
|
+
`this market's floor is "${marketFloor}" — a repository may not go below it. ` +
|
|
84
|
+
'Raise it with the market owner, not here.',
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Down, and at or above the floor. Always allowed, no evidence asked for.
|
|
89
|
+
return { to, eligible: true, blockers: [] };
|
|
90
|
+
}
|
|
91
|
+
if (to === from)
|
|
92
|
+
return { to, eligible: true, blockers: [] };
|
|
93
|
+
// One rung at a time. Jumping observe to block-high skips the rung where the
|
|
94
|
+
// evidence for blocking is actually gathered.
|
|
95
|
+
if (RUNG_INDEX[to] > RUNG_INDEX[from] + 1) {
|
|
96
|
+
blockers.push(`"${from}" cannot jump to "${to}" — promote one rung at a time, through "${nextRung(from)}", ` +
|
|
97
|
+
'which is where the evidence for the next one is gathered');
|
|
98
|
+
}
|
|
99
|
+
const need = REQUIREMENTS[to];
|
|
100
|
+
if (evidence.sampleSize < need.minSampleSize) {
|
|
101
|
+
blockers.push(`${need.minSampleSize} reviewed pull requests are needed and ${evidence.sampleSize} have been recorded — ` +
|
|
102
|
+
'a perfect rate over a handful is a coincidence, not evidence');
|
|
103
|
+
}
|
|
104
|
+
if (need.minSeedRecall !== null) {
|
|
105
|
+
if (evidence.seedRecall === null) {
|
|
106
|
+
blockers.push('the seeded corpus has never been scored for this stack, so there is no evidence the reviewer ' +
|
|
107
|
+
'catches known defects at all');
|
|
108
|
+
}
|
|
109
|
+
else if (evidence.seedRecall < need.minSeedRecall) {
|
|
110
|
+
blockers.push(`seed BLOCKER recall is ${(evidence.seedRecall * 100).toFixed(0)}% and ` +
|
|
111
|
+
`${(need.minSeedRecall * 100).toFixed(0)}% is required — a reviewer that misses known defects ` +
|
|
112
|
+
'must not be given a veto');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (need.maxFalsePositives !== null) {
|
|
116
|
+
if (evidence.falsePositives === null) {
|
|
117
|
+
blockers.push('the clean corpus has never been scored, so nothing shows the reviewer stays quiet on correct code');
|
|
118
|
+
}
|
|
119
|
+
else if (evidence.falsePositives > need.maxFalsePositives) {
|
|
120
|
+
blockers.push(`${evidence.falsePositives} false positive(s) on the clean corpus — a reviewer that flags correct ` +
|
|
121
|
+
'code cannot be given a veto, whatever its recall');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (need.minActedOnRate !== null) {
|
|
125
|
+
if (evidence.actedOnRate === null) {
|
|
126
|
+
blockers.push('too few findings have fired to compute an acted-on rate');
|
|
127
|
+
}
|
|
128
|
+
else if (evidence.actedOnRate < need.minActedOnRate) {
|
|
129
|
+
blockers.push(`acted-on rate is ${(evidence.actedOnRate * 100).toFixed(0)}% and ` +
|
|
130
|
+
`${(need.minActedOnRate * 100).toFixed(0)}% is required — blocking on findings people already ` +
|
|
131
|
+
'ignore turns the gate into an obstacle rather than a control');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { to, eligible: blockers.length === 0, blockers };
|
|
135
|
+
}
|
|
136
|
+
/** The highest rung this repository could move to today, and why not higher. */
|
|
137
|
+
export function ladderStatus(current, evidence, marketFloor = 'observe') {
|
|
138
|
+
const next = nextRung(current);
|
|
139
|
+
return {
|
|
140
|
+
current,
|
|
141
|
+
next,
|
|
142
|
+
promotion: next ? canPromote(current, next, evidence, marketFloor) : null,
|
|
143
|
+
// A repository under its market's floor is not drifting, it is out of policy,
|
|
144
|
+
// and the difference matters to whoever has to act.
|
|
145
|
+
belowFloor: RUNG_INDEX[current] < RUNG_INDEX[marketFloor],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=ladder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ladder.js","sourceRoot":"","sources":["../../cli/enforce/ladder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// A structured exemption: who accepted a failing process check, why, and until when.
|
|
2
|
+
//
|
|
3
|
+
// The bare `redline-exempt` label recorded none of that. It downgraded process
|
|
4
|
+
// checks to warnings and left the estate unable to answer the two questions that
|
|
5
|
+
// matter about an exemption — is anyone still standing behind it, and was it ever
|
|
6
|
+
// meant to be permanent. An exemption nobody has to justify and nobody revisits
|
|
7
|
+
// is not an exemption, it is an opt-out.
|
|
8
|
+
export const EXEMPTION_HEADING = '## Redline exemption';
|
|
9
|
+
// Ninety days. Long enough for real remediation work to be scheduled, short
|
|
10
|
+
// enough that "temporary" has to be renewed by someone who still believes it.
|
|
11
|
+
export const MAX_DAYS = 90;
|
|
12
|
+
export const MIN_REASON_LENGTH = 20;
|
|
13
|
+
// HTML comments are guidance, not content.
|
|
14
|
+
//
|
|
15
|
+
// The shipped pull request template explains each field with a commented block
|
|
16
|
+
// that necessarily contains the words `reason:`, `until:` and `scope:` — so a
|
|
17
|
+
// parser reading the first match found the instructions instead of the author's
|
|
18
|
+
// answer, and rejected every correctly filled exemption. The template cannot
|
|
19
|
+
// stop describing its own fields, so the parser has to know what a comment is.
|
|
20
|
+
const withoutComments = (text) => text.replace(/<!--[\s\S]*?-->/g, '');
|
|
21
|
+
const field = (block, name) => {
|
|
22
|
+
const match = new RegExp(`^\\s*[-*]?\\s*${name}\\s*:\\s*(.+)$`, 'im').exec(block);
|
|
23
|
+
return match?.[1]?.trim() ?? null;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Parse the exemption block out of a pull request body.
|
|
27
|
+
*
|
|
28
|
+
* `now` is injected rather than read from the clock so the expiry rules are
|
|
29
|
+
* testable and so a gate run and a later audit of the same pull request can
|
|
30
|
+
* reach the same verdict about the same text.
|
|
31
|
+
*/
|
|
32
|
+
export function parseExemption(body, now) {
|
|
33
|
+
const problems = [];
|
|
34
|
+
const fail = (problem, detail) => {
|
|
35
|
+
problems.push({ problem, detail });
|
|
36
|
+
return { exemption: null, problems };
|
|
37
|
+
};
|
|
38
|
+
const searchable = withoutComments(body);
|
|
39
|
+
const start = searchable.toLowerCase().indexOf(EXEMPTION_HEADING.toLowerCase());
|
|
40
|
+
if (start === -1) {
|
|
41
|
+
return fail('no-block', `the pull request has no "${EXEMPTION_HEADING}" section — a label alone no longer exempts anything`);
|
|
42
|
+
}
|
|
43
|
+
// Everything until the next heading of the same or higher level. A reason that
|
|
44
|
+
// runs into the next section would otherwise swallow the rest of the body.
|
|
45
|
+
const rest = searchable.slice(start + EXEMPTION_HEADING.length);
|
|
46
|
+
const next = /^#{1,2}\s/m.exec(rest);
|
|
47
|
+
const block = next ? rest.slice(0, next.index) : rest;
|
|
48
|
+
const reason = field(block, 'reason');
|
|
49
|
+
if (!reason) {
|
|
50
|
+
return fail('no-reason', 'the exemption block has no `reason:` — say what is being accepted and why');
|
|
51
|
+
}
|
|
52
|
+
if (reason.length < MIN_REASON_LENGTH) {
|
|
53
|
+
return fail('reason-too-short', `the reason is ${reason.length} characters; at least ${MIN_REASON_LENGTH} are required — "needed for release" tells a later reader nothing`);
|
|
54
|
+
}
|
|
55
|
+
const until = field(block, 'until');
|
|
56
|
+
if (!until) {
|
|
57
|
+
return fail('no-until', 'the exemption block has no `until:` — an exemption with no end is a permanent one nobody chose');
|
|
58
|
+
}
|
|
59
|
+
const parsed = new Date(`${until}T23:59:59.999Z`);
|
|
60
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
61
|
+
return fail('until-unparseable', `\`until: ${until}\` is not a YYYY-MM-DD date`);
|
|
62
|
+
}
|
|
63
|
+
if (parsed.getTime() < now.getTime()) {
|
|
64
|
+
return fail('until-past', `this exemption expired on ${until} — renew it deliberately or fix the finding`);
|
|
65
|
+
}
|
|
66
|
+
// Ceil, not the raw fraction: `until` is a whole day and is inclusive to its
|
|
67
|
+
// end, so a date exactly MAX_DAYS out measures as slightly over and was
|
|
68
|
+
// refused as "91 days away" for asking for 90.
|
|
69
|
+
const days = Math.ceil((parsed.getTime() - now.getTime()) / 86400000 - 1);
|
|
70
|
+
if (days > MAX_DAYS) {
|
|
71
|
+
return fail('until-too-far', `\`until: ${until}\` is ${Math.round(days)} days away; the maximum is ${MAX_DAYS} — a longer exemption is a standards change, not an exemption`);
|
|
72
|
+
}
|
|
73
|
+
const scopeField = field(block, 'scope');
|
|
74
|
+
const scope = scopeField
|
|
75
|
+
? scopeField
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((s) => s.trim())
|
|
78
|
+
.filter((s) => s !== '')
|
|
79
|
+
: ['*'];
|
|
80
|
+
return { exemption: { reason, until, scope }, problems };
|
|
81
|
+
}
|
|
82
|
+
/** Whether an exemption covers a named check or rule id. */
|
|
83
|
+
export function covers(exemption, check) {
|
|
84
|
+
return exemption.scope.includes('*') || exemption.scope.includes(check);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../cli/exempt/parse.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,EAAE;AACF,+EAA+E;AAC/E,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,yCAAyC;AAyBzC,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAExD,4EAA4E;AAC5E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,CAAC;AAC3B,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC,2CAA2C;AAC3C,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,6EAA6E;AAC7E,+EAA+E;AAC/E,MAAM,eAAe,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;AAEvF,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,IAAY,EAAiB,EAAE;IAC3D,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,iBAAiB,IAAI,gBAAgB,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClF,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;AACpC,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,GAAS;IACpD,MAAM,QAAQ,GAAgC,EAAE,CAAC;IACjD,MAAM,IAAI,GAAG,CAAC,OAAyB,EAAE,MAAc,EAAmB,EAAE;QAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACvC,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC,WAAW,EAAE,CAAC,CAAC;IAChF,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,IAAI,CACT,UAAU,EACV,4BAA4B,iBAAiB,sDAAsD,CACpG,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,2EAA2E;IAC3E,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEtD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,WAAW,EAAE,2EAA2E,CAAC,CAAC;IACxG,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACtC,OAAO,IAAI,CACT,kBAAkB,EAClB,iBAAiB,MAAM,CAAC,MAAM,yBAAyB,iBAAiB,mEAAmE,CAC5I,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC,UAAU,EAAE,gGAAgG,CAAC,CAAC;IAC5H,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,gBAAgB,CAAC,CAAC;IAClD,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,mBAAmB,EAAE,YAAY,KAAK,6BAA6B,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC,YAAY,EAAE,6BAA6B,KAAK,6CAA6C,CAAC,CAAC;IAC7G,CAAC;IACD,6EAA6E;IAC7E,wEAAwE;IACxE,+CAA+C;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC;IAC1E,IAAI,IAAI,GAAG,QAAQ,EAAE,CAAC;QACpB,OAAO,IAAI,CACT,eAAe,EACf,YAAY,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,8BAA8B,QAAQ,+DAA+D,CAChJ,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU;QACtB,CAAC,CAAC,UAAU;aACP,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEV,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC;AAC3D,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,MAAM,CAAC,SAAoB,EAAE,KAAa;IACxD,OAAO,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { RedlineError } from "../core/errors.js";
|
|
2
|
+
const DATA = {
|
|
3
|
+
env: 'DATA_DIR',
|
|
4
|
+
type: 'path',
|
|
5
|
+
help: 'directory of collected telemetry',
|
|
6
|
+
default: 'data',
|
|
7
|
+
};
|
|
8
|
+
const ORG = { env: 'ORG', type: 'string', help: 'the GitHub organisation', required: true };
|
|
9
|
+
const TOKEN = {
|
|
10
|
+
env: 'GH_TOKEN',
|
|
11
|
+
type: 'string',
|
|
12
|
+
help: 'a token with org read access (or set GH_TOKEN in the environment)',
|
|
13
|
+
};
|
|
14
|
+
export const METRICS_COMMANDS = {
|
|
15
|
+
collect: {
|
|
16
|
+
summary: 'pull review outcomes for merged pull requests across the org',
|
|
17
|
+
runsIn: 'the metrics repo, on a schedule',
|
|
18
|
+
script: 'scripts/collect-telemetry.mjs',
|
|
19
|
+
options: {
|
|
20
|
+
org: ORG,
|
|
21
|
+
token: TOKEN,
|
|
22
|
+
days: { env: 'DAYS', type: 'number', help: 'how far back to look', default: '8' },
|
|
23
|
+
since: { env: 'SINCE', type: 'string', help: 'YYYY-MM-DD, instead of --days' },
|
|
24
|
+
out: { env: 'OUT', type: 'path', help: 'where to write the monthly JSONL', default: 'data' },
|
|
25
|
+
'dry-run': { env: 'DRY_RUN', type: 'boolean', help: 'print a sample and write nothing' },
|
|
26
|
+
'skip-sarif': { env: 'SKIP_SARIF', type: 'boolean', help: 'do not ingest code-scanning alerts' },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
dashboard: {
|
|
30
|
+
summary: 'build the telemetry dashboard page',
|
|
31
|
+
runsIn: 'the metrics repo, or any checkout of its data/',
|
|
32
|
+
script: 'scripts/build-dashboard.mjs',
|
|
33
|
+
options: {
|
|
34
|
+
org: ORG,
|
|
35
|
+
data: DATA,
|
|
36
|
+
days: { env: 'DAYS', type: 'number', help: 'window in days', default: '90' },
|
|
37
|
+
out: { env: 'OUT', type: 'path', help: 'output directory', default: 'dist' },
|
|
38
|
+
registry: { env: 'REGISTRY', type: 'path', help: 'the register, for coverage and the ladder', default: 'registry.json' },
|
|
39
|
+
onboarded: { env: 'ONBOARDED', type: 'number', help: 'onboarded repo count, if not read from the register' },
|
|
40
|
+
'seed-scores': { env: 'SEED_SCORES', type: 'path', help: 'seed score history', default: 'data/seed-scores.jsonl' },
|
|
41
|
+
'standards-version': { env: 'STANDARDS_VERSION', type: 'string', help: 'version to display' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
digest: {
|
|
45
|
+
summary: 'build the weekly stakeholder digest as an Adaptive Card',
|
|
46
|
+
runsIn: 'the metrics repo, weekly',
|
|
47
|
+
script: 'scripts/build-digest.mjs',
|
|
48
|
+
options: {
|
|
49
|
+
org: ORG,
|
|
50
|
+
data: DATA,
|
|
51
|
+
days: { env: 'DAYS', type: 'number', help: 'window in days', default: '7' },
|
|
52
|
+
out: { env: 'OUT', type: 'path', help: 'write the card here instead of stdout' },
|
|
53
|
+
'open-prs': { env: 'OPEN_PRS', type: 'number', help: 'open PR count, from the caller' },
|
|
54
|
+
'stale-prs': { env: 'STALE_PRS', type: 'number', help: 'stale PR count, from the caller' },
|
|
55
|
+
'seed-scores': { env: 'SEED_SCORES', type: 'path', help: 'seed score history, for the recall line' },
|
|
56
|
+
'dashboard-url': { env: 'DASHBOARD_URL', type: 'string', help: 'linked from the card' },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
inbox: {
|
|
60
|
+
summary: 'build the org-wide prioritised pull request inbox',
|
|
61
|
+
runsIn: 'the source repo, on a schedule',
|
|
62
|
+
script: 'scripts/build-inbox.mjs',
|
|
63
|
+
options: {
|
|
64
|
+
org: ORG,
|
|
65
|
+
token: TOKEN,
|
|
66
|
+
out: { env: 'OUT', type: 'path', help: 'output directory', default: 'dist' },
|
|
67
|
+
'max-pages': { env: 'MAX_PAGES', type: 'number', help: 'search pages to walk', default: '10' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
baseline: {
|
|
71
|
+
summary: 'compute the baseline every roadmap phase is measured against',
|
|
72
|
+
runsIn: 'a maintainer terminal, once, with org credentials',
|
|
73
|
+
script: 'scripts/build-baseline.mjs',
|
|
74
|
+
options: {
|
|
75
|
+
data: DATA,
|
|
76
|
+
org: { env: 'ORG', type: 'string', help: 'the org, to survey Redline\'s own PRs and SARIF producers' },
|
|
77
|
+
token: TOKEN,
|
|
78
|
+
registry: { env: 'REGISTRY', type: 'path', help: 'the derived register', default: 'registry.json' },
|
|
79
|
+
days: { env: 'DAYS', type: 'number', help: 'window in days', default: '90' },
|
|
80
|
+
out: { env: 'OUT', type: 'path', help: 'where to write the JSON', default: 'baseline.json' },
|
|
81
|
+
'spend-total': { env: 'SPEND_TOTAL', type: 'number', help: 'AI spend, from the vendor\'s own usage reporting' },
|
|
82
|
+
'spend-currency': { env: 'SPEND_CURRENCY', type: 'string', help: 'currency of --spend-total', default: 'USD' },
|
|
83
|
+
'spend-grain': {
|
|
84
|
+
env: 'SPEND_GRAIN',
|
|
85
|
+
type: 'string',
|
|
86
|
+
help: 'whether the spend figure is per repo or org-wide',
|
|
87
|
+
default: 'org',
|
|
88
|
+
values: ['repo', 'org', 'unknown'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
roi: {
|
|
93
|
+
summary: 'build the page that says what review cost against what it caught',
|
|
94
|
+
runsIn: 'the metrics repo, or any checkout of its data/',
|
|
95
|
+
script: 'scripts/build-roi.mjs',
|
|
96
|
+
options: {
|
|
97
|
+
data: DATA,
|
|
98
|
+
days: { env: 'DAYS', type: 'number', help: 'window in days', default: '90' },
|
|
99
|
+
out: { env: 'OUT', type: 'path', help: 'JSON output', default: 'roi.json' },
|
|
100
|
+
html: { env: 'HTML', type: 'path', help: 'the readable page', default: 'roi.html' },
|
|
101
|
+
'spend-total': { env: 'SPEND_TOTAL', type: 'number', help: 'AI spend for the window' },
|
|
102
|
+
'spend-currency': { env: 'SPEND_CURRENCY', type: 'string', help: 'currency of --spend-total', default: 'USD' },
|
|
103
|
+
'spend-grain': {
|
|
104
|
+
env: 'SPEND_GRAIN',
|
|
105
|
+
type: 'string',
|
|
106
|
+
help: 'per repo or org-wide — an org figure will not answer a per-repo question',
|
|
107
|
+
default: 'org',
|
|
108
|
+
values: ['repo', 'org', 'unknown'],
|
|
109
|
+
},
|
|
110
|
+
'spend-source': { env: 'SPEND_SOURCE', type: 'string', help: 'where the figure came from, recorded with it' },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
correlate: {
|
|
114
|
+
summary: 'research: whether ignoring a finding cost anything',
|
|
115
|
+
runsIn: 'the metrics repo. It is an experiment, not a loop',
|
|
116
|
+
script: 'scripts/build-correlation.mjs',
|
|
117
|
+
options: {
|
|
118
|
+
data: DATA,
|
|
119
|
+
days: { env: 'DAYS', type: 'number', help: 'how much history to read', default: '180' },
|
|
120
|
+
'window-days': { env: 'WINDOW_DAYS', type: 'number', help: 'how long after a merge a revert still counts', default: '30' },
|
|
121
|
+
'min-sample': {
|
|
122
|
+
env: 'MIN_SAMPLE',
|
|
123
|
+
type: 'number',
|
|
124
|
+
help: 'ignored findings a rule needs before any rate is reported',
|
|
125
|
+
default: '10',
|
|
126
|
+
},
|
|
127
|
+
out: { env: 'OUT', type: 'path', help: 'where to write the JSON', default: 'correlation.json' },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
'score-seeds': {
|
|
131
|
+
summary: 'score an automated reviewer against the seeded corpus',
|
|
132
|
+
runsIn: 'anywhere, against a pull request that carries the corpus',
|
|
133
|
+
script: 'scripts/score-seeds.mjs',
|
|
134
|
+
argv: true,
|
|
135
|
+
options: {
|
|
136
|
+
repo: { env: 'REPO', type: 'string', help: 'owner/name of the pilot repository', required: true },
|
|
137
|
+
pr: { env: 'PR', type: 'number', help: 'the pull request number', required: true },
|
|
138
|
+
token: TOKEN,
|
|
139
|
+
json: { env: 'JSON_OUT', type: 'boolean', help: 'machine-readable output' },
|
|
140
|
+
window: { env: 'WINDOW', type: 'number', help: 'lines around a marker a finding may land on', default: '4' },
|
|
141
|
+
history: { env: 'HISTORY', type: 'path', help: 'append the score to this JSONL' },
|
|
142
|
+
baseline: { env: 'BASELINE', type: 'boolean', help: 'record as a baseline rather than a run' },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
context: {
|
|
146
|
+
summary: 'measure what the skills render target saves, per profile',
|
|
147
|
+
runsIn: 'the source repo',
|
|
148
|
+
script: 'scripts/measure-context.mjs',
|
|
149
|
+
options: {
|
|
150
|
+
root: { env: 'ROOT', type: 'path', help: 'the repo to measure', default: '.' },
|
|
151
|
+
out: { env: 'OUT', type: 'path', help: 'also write the rows as JSON here' },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
export const REGISTRY_COMMAND = {
|
|
156
|
+
summary: 'derive the register of onboarded repositories by walking the org',
|
|
157
|
+
runsIn: 'the source repo, nightly',
|
|
158
|
+
script: 'scripts/build-registry.mjs',
|
|
159
|
+
options: {
|
|
160
|
+
org: ORG,
|
|
161
|
+
token: TOKEN,
|
|
162
|
+
source: {
|
|
163
|
+
env: 'SOURCE',
|
|
164
|
+
type: 'string',
|
|
165
|
+
help: 'owner/name of this repo, recorded so a consumer knows which estate the register describes',
|
|
166
|
+
required: true,
|
|
167
|
+
},
|
|
168
|
+
out: { env: 'OUT', type: 'path', help: 'where to write it', default: 'registry.json' },
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Turn parsed flags into the environment the runner reads.
|
|
173
|
+
*
|
|
174
|
+
* Every refusal here is one that used to be silent. An unset required option was
|
|
175
|
+
* a runner throwing halfway through; a non-numeric `--days` became `NaN` and
|
|
176
|
+
* produced an empty window; a mistyped `--spend-grain` became "unknown" and made
|
|
177
|
+
* the resulting number quietly less trustworthy than it looked.
|
|
178
|
+
*/
|
|
179
|
+
export function resolveEnv(spec, flags, env, name) {
|
|
180
|
+
const resolved = {};
|
|
181
|
+
for (const [flag, option] of Object.entries(spec.options)) {
|
|
182
|
+
const raw = flags[flag];
|
|
183
|
+
if (option.type === 'boolean') {
|
|
184
|
+
if (raw === true)
|
|
185
|
+
resolved[option.env] = '1';
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
// Precedence: the flag, then the environment, then the default. The
|
|
189
|
+
// environment stays honoured so a scheduled job can keep using a secret
|
|
190
|
+
// without putting a token on a command line, where it lands in shell history
|
|
191
|
+
// and in the process table.
|
|
192
|
+
const value = raw !== undefined ? String(raw) : (env[option.env] ?? option.default);
|
|
193
|
+
if (value === undefined || value === '') {
|
|
194
|
+
if (option.required) {
|
|
195
|
+
throw new RedlineError('usage', `redline ${name} needs --${flag} (${option.help})`, `or set ${option.env} in the environment`);
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (option.type === 'number' && !Number.isFinite(Number(value))) {
|
|
200
|
+
throw new RedlineError('usage', `--${flag} must be a number, not "${value}"`);
|
|
201
|
+
}
|
|
202
|
+
if (option.values && !option.values.includes(value)) {
|
|
203
|
+
throw new RedlineError('usage', `--${flag} must be one of ${option.values.join(', ')}, not "${value}"`);
|
|
204
|
+
}
|
|
205
|
+
resolved[option.env] = value;
|
|
206
|
+
}
|
|
207
|
+
return resolved;
|
|
208
|
+
}
|
|
209
|
+
/** The help for one command, generated from the same table that validates it. */
|
|
210
|
+
export function helpFor(name, spec) {
|
|
211
|
+
const left = (flag, option) => ` --${flag}${option.type === 'boolean' ? '' : ` <${option.type}>`}`;
|
|
212
|
+
// Sized to the widest flag in THIS command, not to a constant: a fixed column
|
|
213
|
+
// collides the moment a longer flag is added, and a help screen whose columns
|
|
214
|
+
// run together reads as unmaintained.
|
|
215
|
+
const width = Math.max(...Object.entries(spec.options).map(([f, o]) => left(f, o).length)) + 2;
|
|
216
|
+
const flags = Object.entries(spec.options).map(([flag, option]) => {
|
|
217
|
+
const suffix = option.required
|
|
218
|
+
? ' (required)'
|
|
219
|
+
: option.default
|
|
220
|
+
? ` (default: ${option.default})`
|
|
221
|
+
: '';
|
|
222
|
+
return left(flag, option).padEnd(width) + `${option.help}${suffix}`;
|
|
223
|
+
});
|
|
224
|
+
return [
|
|
225
|
+
`redline ${name} — ${spec.summary}`,
|
|
226
|
+
'',
|
|
227
|
+
`Runs in: ${spec.runsIn}.`,
|
|
228
|
+
'',
|
|
229
|
+
...flags,
|
|
230
|
+
'',
|
|
231
|
+
'Every option can also be set as an environment variable — see the names in the docs.',
|
|
232
|
+
].join('\n');
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The same validated flags, as argv, for a runner that parses its own.
|
|
236
|
+
*
|
|
237
|
+
* `--token` is deliberately never forwarded: it is a credential, and a command
|
|
238
|
+
* line is visible in the process table and lands in shell history. It travels as
|
|
239
|
+
* an environment variable, which is where the runner reads it from anyway.
|
|
240
|
+
*/
|
|
241
|
+
export function resolveArgv(spec, flags, env, name) {
|
|
242
|
+
const resolvedEnv = resolveEnv(spec, flags, env, name);
|
|
243
|
+
const argv = [];
|
|
244
|
+
for (const [flag, option] of Object.entries(spec.options)) {
|
|
245
|
+
if (option.env === 'GH_TOKEN')
|
|
246
|
+
continue;
|
|
247
|
+
const value = resolvedEnv[option.env];
|
|
248
|
+
if (value === undefined)
|
|
249
|
+
continue;
|
|
250
|
+
if (option.type === 'boolean')
|
|
251
|
+
argv.push(`--${flag}`);
|
|
252
|
+
else
|
|
253
|
+
argv.push(`--${flag}`, value);
|
|
254
|
+
}
|
|
255
|
+
return argv;
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../cli/metrics/options.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { isRedlineError, RedlineError } from "../core/errors.js";
|
|
13
|
+
import { METRICS_COMMANDS, REGISTRY_COMMAND, resolveArgv, resolveEnv } from "./options.js";
|
|
14
|
+
export function specFor(name) {
|
|
15
|
+
if (name === 'registry')
|
|
16
|
+
return REGISTRY_COMMAND;
|
|
17
|
+
const spec = METRICS_COMMANDS[name];
|
|
18
|
+
if (!spec) {
|
|
19
|
+
throw new RedlineError('usage', `unknown metrics command "${name}"`, `known: ${Object.keys(METRICS_COMMANDS).join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
return spec;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate the flags, then run the runner in this process.
|
|
25
|
+
*
|
|
26
|
+
* The runner reads its configuration from the environment, which is the contract
|
|
27
|
+
* it already documents. This layer is its front door: it decides what is valid,
|
|
28
|
+
* says so in `--help`, and refuses before anything runs — rather than letting a
|
|
29
|
+
* missing token surface as a 401 halfway through an org walk.
|
|
30
|
+
*/
|
|
31
|
+
export async function runMetrics(name, opts) {
|
|
32
|
+
const spec = specFor(name);
|
|
33
|
+
const env = opts.env ?? process.env;
|
|
34
|
+
const label = name === 'registry' ? 'registry' : `metrics ${name}`;
|
|
35
|
+
// Both paths validate through the same table. Which one a runner takes is a
|
|
36
|
+
// fact about that runner, never a difference in what the user may type.
|
|
37
|
+
const resolved = resolveEnv(spec, opts.flags, env, label);
|
|
38
|
+
const argv = spec.argv ? resolveArgv(spec, opts.flags, env, label) : null;
|
|
39
|
+
const script = join(opts.root, spec.script);
|
|
40
|
+
if (!existsSync(script) && !opts.load) {
|
|
41
|
+
throw new RedlineError('failed', `${spec.script} is missing from this installation`, 'reinstall redlinegate — the runners ship inside the package');
|
|
42
|
+
}
|
|
43
|
+
// Applied to the real environment because the runner reads process.env
|
|
44
|
+
// directly. Scoped to this process, which lives for exactly one command.
|
|
45
|
+
for (const [key, value] of Object.entries(resolved))
|
|
46
|
+
process.env[key] = value;
|
|
47
|
+
// A runner that parses its own argv reads process.argv, so it is set the way
|
|
48
|
+
// node would have: [execPath, script, ...flags].
|
|
49
|
+
const savedArgv = process.argv;
|
|
50
|
+
if (argv)
|
|
51
|
+
process.argv = [process.argv[0] ?? 'node', script, ...argv];
|
|
52
|
+
try {
|
|
53
|
+
const load = opts.load ?? ((path) => import(__rewriteRelativeImportExtension(pathToFileURL(path).href)));
|
|
54
|
+
await load(script);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (isRedlineError(error))
|
|
58
|
+
throw error;
|
|
59
|
+
// A runner that throws is reporting a real failure of the work — a bad
|
|
60
|
+
// credential, an unreachable host, a malformed data file. Letting it reach
|
|
61
|
+
// the CLI's catch-all would print "redline failed unexpectedly", which tells
|
|
62
|
+
// a reader the tool is broken when the token is.
|
|
63
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
+
throw new RedlineError('host', `redline ${name} failed: ${message.split('\n')[0]}`, /401|403|bad credentials/i.test(message)
|
|
65
|
+
? 'check the token — most of these commands need org read access'
|
|
66
|
+
: undefined);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
process.argv = savedArgv;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../cli/metrics/run.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAoB,MAAM,cAAc,CAAC;AAe7G,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,gBAAgB,CAAC;IACjD,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,YAAY,CACpB,OAAO,EACP,4BAA4B,IAAI,GAAG,EACnC,UAAU,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACrD,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAgB;IAC7D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC;IACnE,4EAA4E;IAC5E,wEAAwE;IACxE,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE1E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,YAAY,CACpB,QAAQ,EACR,GAAG,IAAI,CAAC,MAAM,oCAAoC,EAClD,6DAA6D,CAC9D,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,yEAAyE;IACzE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAE9E,6EAA6E;IAC7E,iDAAiD;IACjD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/B,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,kCAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAC,CAAC,CAAC;QAC/E,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,cAAc,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;QACvC,uEAAuE;QACvE,2EAA2E;QAC3E,6EAA6E;QAC7E,iDAAiD;QACjD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,YAAY,CACpB,MAAM,EACN,WAAW,IAAI,YAAY,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EACnD,0BAA0B,CAAC,IAAI,CAAC,OAAO,CAAC;YACtC,CAAC,CAAC,+DAA+D;YACjE,CAAC,CAAC,SAAS,CACd,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAC3B,CAAC;AACH,CAAC"}
|