redlinegate 0.0.2 → 0.0.3
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 +2 -2
- package/dist/bin/redline.js +247 -42
- package/dist/bin/redline.js.map +1 -1
- package/dist/commands/init.js +274 -26
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/remove.js +40 -0
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/status.js +94 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/redline-json.js +18 -1
- package/dist/config/redline-json.js.map +1 -1
- package/dist/core/git.js +50 -3
- package/dist/core/git.js.map +1 -1
- package/dist/core/version.js +6 -0
- package/dist/core/version.js.map +1 -1
- package/dist/detect/setup.js +200 -0
- package/dist/detect/setup.js.map +1 -0
- package/dist/detect/stack.js +56 -17
- package/dist/detect/stack.js.map +1 -1
- package/dist/metrics/options.js +0 -9
- package/dist/metrics/options.js.map +1 -1
- package/dist/platforms/azure/install.js +1 -5
- package/dist/platforms/azure/install.js.map +1 -1
- package/dist/platforms/azure/verify.js +4 -2
- package/dist/platforms/azure/verify.js.map +1 -1
- package/dist/platforms/github/install.js +110 -22
- package/dist/platforms/github/install.js.map +1 -1
- package/dist/platforms/github/vendor.js +81 -0
- package/dist/platforms/github/vendor.js.map +1 -0
- package/dist/platforms/github/verify.js +17 -2
- package/dist/platforms/github/verify.js.map +1 -1
- package/dist/platforms/types.js +4 -0
- package/dist/platforms/types.js.map +1 -1
- package/dist/render/standards.js +8 -1
- package/dist/render/standards.js.map +1 -1
- package/dist/render/vendors.js +9 -52
- package/dist/render/vendors.js.map +1 -1
- package/dist/rules/catalogue.js +127 -0
- package/dist/rules/catalogue.js.map +1 -0
- package/dist/ui/facts.js +30 -1
- package/dist/ui/facts.js.map +1 -1
- package/dist/ui/prompt.js +137 -8
- package/dist/ui/prompt.js.map +1 -1
- package/dist/ui/report.js +214 -0
- package/dist/ui/report.js.map +1 -0
- package/dist/ui/tty.js +358 -25
- package/dist/ui/tty.js.map +1 -1
- package/dist/ui/wizard.js +133 -19
- package/dist/ui/wizard.js.map +1 -1
- package/dist/verify/remote.js +33 -0
- package/dist/verify/remote.js.map +1 -1
- package/package.json +4 -1
- package/scripts/fetch-stars.mjs +92 -0
- package/scripts/lib/rules.d.mts +18 -0
- package/scripts/publish-local.mjs +183 -0
- package/standards/manifest.json +79 -8
- package/standards/stacks/angular.md +57 -0
- package/standards/stacks/dom.md +61 -0
- package/standards/stacks/svelte.md +45 -0
- package/standards/stacks/vue.md +54 -0
- package/templates/redline.yml +4 -0
- package/workflows/redline-gate.yml +28 -4
- package/scripts/measure-context.mjs +0 -101
package/dist/ui/wizard.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { RedlineError } from "../core/errors.js";
|
|
2
|
+
import { TOOL_PROBES } from "../detect/existing.js";
|
|
1
3
|
import { RUNGS } from "../enforce/ladder.js";
|
|
2
4
|
// Plain-English, at the moment of choosing. Every one of these was a term the
|
|
3
5
|
// operator met for the first time in output that assumed they knew it.
|
|
@@ -21,14 +23,20 @@ const STAND_DOWN_LABEL = {
|
|
|
21
23
|
dependencies: 'dependency vulnerability review',
|
|
22
24
|
policy: 'static analysis',
|
|
23
25
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The profile list, ordered by how much detection actually knows.
|
|
28
|
+
*
|
|
29
|
+
* A confident detection — the repository's own manifest naming its framework —
|
|
30
|
+
* is hoisted to the top and marked, because it is almost always the answer. A
|
|
31
|
+
* guess is not: hoisting it puts it on the first row with the cursor already on
|
|
32
|
+
* it, which reads as chosen no matter how carefully it is left unticked. So a
|
|
33
|
+
* guess sorts alphabetically with everything else and says what it is.
|
|
34
|
+
*/
|
|
35
|
+
function profileChoices(manifest, detected, confidence) {
|
|
36
|
+
// Alphabetical, always. Hoisting the detected profile to the first row put the
|
|
37
|
+
// cursor on it before anything was typed, which reads as chosen no matter how
|
|
38
|
+
// carefully it is left unticked.
|
|
39
|
+
const ids = Object.keys(manifest.profiles).sort((a, b) => a.localeCompare(b));
|
|
32
40
|
return ids.map((id) => {
|
|
33
41
|
// The stacks' own titles, not their ids. `JavaScript, React (web)` tells an
|
|
34
42
|
// operator what they are choosing; `javascript, react` makes them guess at
|
|
@@ -37,7 +45,12 @@ function profileChoices(manifest, detected) {
|
|
|
37
45
|
return {
|
|
38
46
|
value: id,
|
|
39
47
|
label: id,
|
|
40
|
-
hint: stacks.join(', ') +
|
|
48
|
+
hint: stacks.join(', ') +
|
|
49
|
+
(id === detected
|
|
50
|
+
? confidence === 'high'
|
|
51
|
+
? ' — looks like this repository, but pick it yourself'
|
|
52
|
+
: ' — a guess; nothing here names this framework'
|
|
53
|
+
: ''),
|
|
41
54
|
};
|
|
42
55
|
});
|
|
43
56
|
}
|
|
@@ -72,6 +85,7 @@ function vendorChoices(manifest) {
|
|
|
72
85
|
*/
|
|
73
86
|
export async function runWizard(p, facts) {
|
|
74
87
|
const { manifest, survey, recorded } = facts;
|
|
88
|
+
const defaultOwner = facts.defaultOwner ?? 'the platform team';
|
|
75
89
|
p.intro('Redline');
|
|
76
90
|
// Multi-select, because a repository is routinely more than one bundle: a
|
|
77
91
|
// React application with its own Terraform beside it is `web,infra`, and a
|
|
@@ -79,13 +93,33 @@ export async function runWizard(p, facts) {
|
|
|
79
93
|
//
|
|
80
94
|
// `.redline.json` still records one string — resolveProfile takes the list
|
|
81
95
|
// and sorts it — so nothing downstream had to learn a new shape.
|
|
96
|
+
// Detection never ticks a row. Which standards a repository is held to is the
|
|
97
|
+
// one answer here that changes what every future pull request is judged
|
|
98
|
+
// against, and a ticked row is consent — an operator pressing enter through
|
|
99
|
+
// the menu has agreed to whatever was ticked, whether or not they read it.
|
|
100
|
+
// Detection earns a label on the row and nothing more.
|
|
101
|
+
//
|
|
102
|
+
// What the repository already recorded is different: that is a decision a
|
|
103
|
+
// person made here before, and re-asking for it from scratch on every run
|
|
104
|
+
// would be its own kind of rude.
|
|
82
105
|
const recordedProfiles = recorded?.profile?.split(',').map((s) => s.trim());
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
106
|
+
let profiles = await p.multiselect('Which standards apply here?', profileChoices(manifest, facts.detectedProfile, facts.detectedConfidence), recordedProfiles ?? []);
|
|
107
|
+
// And if nothing is chosen, ask once more rather than choose something.
|
|
108
|
+
// Falling back to the detected profile is how "I did not pick anything"
|
|
109
|
+
// quietly became "Redline picked for me", which is the whole complaint.
|
|
110
|
+
//
|
|
111
|
+
// Once, not until-they-comply: a loop here never ends against a caller that
|
|
112
|
+
// keeps answering nothing, and a prompt that cannot be escaped is worse than
|
|
113
|
+
// one that gives up and says how to pass the answer as a flag.
|
|
114
|
+
if (profiles.length === 0) {
|
|
115
|
+
p.note('Nothing selected — Redline needs at least one profile to know which rules apply here. ' +
|
|
116
|
+
`The checkout looks like ${facts.detectedProfile}; take that, or take another.`);
|
|
117
|
+
profiles = await p.multiselect('Which standards apply here?', profileChoices(manifest, facts.detectedProfile, facts.detectedConfidence), []);
|
|
118
|
+
}
|
|
119
|
+
if (profiles.length === 0) {
|
|
120
|
+
throw new RedlineError('usage', 'no profile chosen, so there are no rules to render', `pick one from the menu, or name it outright: redline init --profile ${facts.detectedProfile}`);
|
|
121
|
+
}
|
|
122
|
+
const profile = profiles.join(',');
|
|
89
123
|
const hostChoices = [
|
|
90
124
|
{
|
|
91
125
|
value: 'github',
|
|
@@ -134,22 +168,93 @@ export async function runWizard(p, facts) {
|
|
|
134
168
|
hint: 'resource naming, @type, paging, error bodies. Only if you implement TMF APIs',
|
|
135
169
|
},
|
|
136
170
|
], ['speckit']);
|
|
171
|
+
// What Redline should set up, not what it noticed. A question whose only
|
|
172
|
+
// outcome was a sentence in the report earned nothing: the operator read a
|
|
173
|
+
// list of tools, ticked the ones they had, and nothing happened differently.
|
|
174
|
+
//
|
|
175
|
+
// Only controls Redline can install with no account and no token appear here,
|
|
176
|
+
// and only on a host where they mean something — on Azure DevOps the list is
|
|
177
|
+
// empty and the question does not get asked at all. What the repository
|
|
178
|
+
// already runs is still detected, and still stands Redline's own gate jobs
|
|
179
|
+
// down; it just does not need a question to do it.
|
|
180
|
+
const already = new Set(facts.recorded?.integrations?.length
|
|
181
|
+
? facts.recorded.integrations
|
|
182
|
+
: survey.tools.map((tool) => tool.id));
|
|
183
|
+
const setupChoices = facts.offerable.map((offer) => ({
|
|
184
|
+
value: offer.id,
|
|
185
|
+
label: offer.label,
|
|
186
|
+
...(already.has(offer.id)
|
|
187
|
+
? { disabled: `already here — ${offer.evidence ?? 'detected in this repository'}` }
|
|
188
|
+
: { hint: offer.hint }),
|
|
189
|
+
}));
|
|
190
|
+
const setup = setupChoices.length > 0
|
|
191
|
+
? await p.multiselect('What should Redline set up alongside its own checks?', setupChoices, [])
|
|
192
|
+
: [];
|
|
193
|
+
// The recorded list stays what the repository RUNS — detection plus anything
|
|
194
|
+
// stated with --integrations — and gains whatever was just installed, because
|
|
195
|
+
// after this run it does run it.
|
|
196
|
+
const integrations = [...new Set([...already, ...setup])];
|
|
137
197
|
// A capability a detected tool already covers starts deselected and says
|
|
138
198
|
// which tool. `standDown` is advisory: nothing here is decided for the
|
|
139
199
|
// operator, only defaulted.
|
|
140
200
|
const covered = new Map();
|
|
141
|
-
for (const
|
|
142
|
-
|
|
201
|
+
for (const id of integrations) {
|
|
202
|
+
const probe = TOOL_PROBES.find((candidate) => candidate.id === id);
|
|
203
|
+
if (probe === undefined || probe.standsDown === null)
|
|
143
204
|
continue;
|
|
144
|
-
covered.set(
|
|
205
|
+
covered.set(probe.standsDown, probe.label);
|
|
145
206
|
}
|
|
146
207
|
const capabilityChoices = Object.entries(CAPABILITY_HINTS).map(([id, hint]) => ({ value: id, label: id, hint }));
|
|
147
208
|
const capabilities = await p.multiselect('What should Redline install?', capabilityChoices, ['gate', 'merge-policy', 'labels']);
|
|
209
|
+
// Asked here, and asked blind: the wizard runs before the platform is
|
|
210
|
+
// resolved, so nothing yet knows whether this organisation publishes a gate.
|
|
211
|
+
// That is why it defaults to `org` and why the mid-run offer still exists —
|
|
212
|
+
// this question is the operator's preference, and the later one is the same
|
|
213
|
+
// question asked once the host has actually answered. An operator who already
|
|
214
|
+
// knows their organisation has nothing can say so here and never see it.
|
|
215
|
+
const gateSource = capabilities.includes('gate')
|
|
216
|
+
? await p.select('Where should the merge gate live?', [
|
|
217
|
+
{
|
|
218
|
+
value: 'org',
|
|
219
|
+
label: 'in the organisation',
|
|
220
|
+
hint: 'one shared workflow; a pull request cannot edit the gate that judges it',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
value: 'local',
|
|
224
|
+
label: 'in this repository',
|
|
225
|
+
hint: 'no organisation setup needed — weaker: a PR can edit its own gate',
|
|
226
|
+
},
|
|
227
|
+
], 'org')
|
|
228
|
+
: 'org';
|
|
229
|
+
// Asked only when the capability was taken. GitHub silently ignores an owner
|
|
230
|
+
// it cannot resolve, so a CODEOWNERS seeded with a team that does not exist
|
|
231
|
+
// reports as installed and enforces nothing — the one failure mode worth a
|
|
232
|
+
// question, and only for the operators who will actually get a file.
|
|
233
|
+
const reviewOwners = capabilities.includes('review-ownership')
|
|
234
|
+
? (await p.text('Who owns the paths Redline protects?', {
|
|
235
|
+
placeholder: `${defaultOwner} — the default, which may not exist here`,
|
|
236
|
+
hint: 'a team, a user or an email; several separated by commas. GitHub ignores an owner it cannot resolve',
|
|
237
|
+
}))
|
|
238
|
+
.split(',')
|
|
239
|
+
.map((owner) => owner.trim())
|
|
240
|
+
.filter((owner) => owner !== '')
|
|
241
|
+
: [];
|
|
148
242
|
if (covered.size > 0) {
|
|
149
243
|
const named = [...covered]
|
|
150
244
|
.map(([job, label]) => `${label} already covers ${STAND_DOWN_LABEL[job] ?? job}`)
|
|
151
245
|
.join('; ');
|
|
152
|
-
p.note(`${named}.
|
|
246
|
+
p.note(`${named}. Those gate jobs are stood down rather than run a second time — ` +
|
|
247
|
+
'the rest of the gate still runs.');
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// The other half of that question, and the half that was missing: what
|
|
251
|
+
// happens when the answer is "none of these". Redline turns on what the
|
|
252
|
+
// host itself provides, and is plain that it does not install anybody
|
|
253
|
+
// else's scanner — offering to would be a promise it cannot keep.
|
|
254
|
+
p.note('Nothing ticked — Redline will switch on the host controls itself: secret scanning, ' +
|
|
255
|
+
'push protection and dependency alerts. It does not install third-party scanners ' +
|
|
256
|
+
'(SonarQube, Snyk, Mend). Add one when you want it, then re-run ' +
|
|
257
|
+
'`redline init --integrations <id>` and the gate stops duplicating what it covers.');
|
|
153
258
|
}
|
|
154
259
|
const rung = await p.select('How hard should the check bite?', RUNGS.map((value) => ({ value, label: value, hint: RUNG_HINTS[value] })), recorded?.rung ?? 'observe');
|
|
155
260
|
const action = await p.select('Ready?', [
|
|
@@ -158,6 +263,11 @@ export async function runWizard(p, facts) {
|
|
|
158
263
|
label: 'Dry run',
|
|
159
264
|
hint: 'print the plan — writes nothing, contacts no host, needs no credential',
|
|
160
265
|
},
|
|
266
|
+
{
|
|
267
|
+
value: 'no-commit',
|
|
268
|
+
label: 'Write the files only',
|
|
269
|
+
hint: 'writes into your working tree, uncommitted — no host changes, no pull request',
|
|
270
|
+
},
|
|
161
271
|
{
|
|
162
272
|
value: 'apply',
|
|
163
273
|
label: 'Apply',
|
|
@@ -172,7 +282,11 @@ export async function runWizard(p, facts) {
|
|
|
172
282
|
speckit: contexts.includes('speckit'),
|
|
173
283
|
tmf: contexts.includes('tmf'),
|
|
174
284
|
capabilities,
|
|
285
|
+
integrations,
|
|
286
|
+
setup,
|
|
287
|
+
reviewOwners,
|
|
175
288
|
rung,
|
|
289
|
+
gateSource,
|
|
176
290
|
action,
|
|
177
291
|
};
|
|
178
292
|
}
|
package/dist/ui/wizard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../cli/ui/wizard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../cli/ui/wizard.ts"],"names":[],"mappings":"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"}
|
package/dist/verify/remote.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isRedlineError } from "../core/errors.js";
|
|
2
2
|
import { renderForTarget, managedPaths } from "../sync/render.js";
|
|
3
3
|
export const CALLER_WORKFLOW = '.github/workflows/redline.yml';
|
|
4
|
+
import { VENDORED_GATE_PATH, stampedVersion } from "../platforms/github/vendor.js";
|
|
5
|
+
export { VENDORED_GATE_PATH } from "../platforms/github/vendor.js";
|
|
4
6
|
// Verify a repository without a checkout.
|
|
5
7
|
//
|
|
6
8
|
// Every check that can be sourced from the host is, and every check that cannot
|
|
@@ -70,6 +72,37 @@ export async function verifyRemote(host, ref, opts) {
|
|
|
70
72
|
: machinery.publishes !== machinery.expected
|
|
71
73
|
? `${machinery.path} publishes ${machinery.publishes}, but the policy requires ${machinery.expected}`
|
|
72
74
|
: `publishes ${machinery.publishes}`);
|
|
75
|
+
// --- vendored gate ---------------------------------------------------------
|
|
76
|
+
// A gate referenced from the organisation updates itself: one merge there
|
|
77
|
+
// reaches every repository that points at it. A vendored one does not — it is
|
|
78
|
+
// a copy, and a copy is only as current as the run that wrote it. Nothing
|
|
79
|
+
// else in this report would notice a repository sitting three releases behind
|
|
80
|
+
// on the workflow that decides whether its pull requests can merge.
|
|
81
|
+
//
|
|
82
|
+
// Reported against the CLI running the check rather than against npm: this is
|
|
83
|
+
// the same comparison `redline init --repair` would act on, so a repository
|
|
84
|
+
// told it is behind can always be brought level by the binary that told it.
|
|
85
|
+
if (gateOwned && config.gateSource === 'local') {
|
|
86
|
+
const vendored = await host.readRemoteFile(ref, VENDORED_GATE_PATH);
|
|
87
|
+
const pin = vendored === null ? null : stampedVersion(vendored.content);
|
|
88
|
+
if (vendored === null) {
|
|
89
|
+
add('gate-vendored', false, `${VENDORED_GATE_PATH} is missing, but .redline.json says the gate is vendored here — ` +
|
|
90
|
+
'the caller workflow references a file that is not in this repository');
|
|
91
|
+
}
|
|
92
|
+
else if (pin === null || config.gateVersion === '') {
|
|
93
|
+
// One of the two sides has no version to compare. That is not drift, and
|
|
94
|
+
// reporting it as stale would file work against a repository whose gate
|
|
95
|
+
// may be perfectly current — a development build writes no stamp on
|
|
96
|
+
// purpose. Unknown, with the reason, is the honest answer.
|
|
97
|
+
add('gate-vendored', true, 'vendored, version not recorded — cannot tell current from stale here', true);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
add('gate-vendored', pin === opts.cliVersion, pin === opts.cliVersion
|
|
101
|
+
? `vendored, current with redlinegate ${pin}`
|
|
102
|
+
: `vendored at redlinegate ${pin}, but this check runs ${opts.cliVersion} — ` +
|
|
103
|
+
're-run redline init --repair in that repository to bring the gate level');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
73
106
|
// --- merge policy ----------------------------------------------------------
|
|
74
107
|
const policy = await host.readPolicy(ref);
|
|
75
108
|
if (!config.capabilities.mergePolicy) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../cli/verify/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAInD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../cli/verify/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAInD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA8BlE,MAAM,CAAC,MAAM,eAAe,GAAG,+BAA+B,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,0CAA0C;AAC1C,EAAE;AACF,gFAAgF;AAChF,iFAAiF;AACjF,yEAAyE;AACzE,iFAAiF;AACjF,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAsB,EACtB,GAAY,EACZ,IAAyB;IAEzB,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,EAAW,EAAE,MAAc,EAAE,OAAO,GAAG,KAAK,EAAQ,EAAE;QAChF,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9E,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;IAEtC,IAAI,MAA4B,CAAC;IACjC,IAAI,CAAC;QACH,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,4EAA4E;QAC5E,4EAA4E;QAC5E,kCAAkC;QAClC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,IAAI,iDAAiD,CAAC,CAAC;QAClF,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC;IACD,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,WAAW,MAAM,CAAC,OAAO,gBAAgB,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE3F,8EAA8E;IAC9E,8EAA8E;IAC9E,wDAAwD;IACxD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAClD,IAAI,IAAI;gBAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,QAAQ,GAAG,eAAe,CAAC;YAC/B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,QAAQ;SACT,CAAC,CAAC;QACH,GAAG,CACD,WAAW,EACX,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAC3B,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YACzB,CAAC,CAAC,8BAA8B,IAAI,CAAC,gBAAgB,EAAE;YACvD,CAAC,CAAC,4BAA4B,IAAI,CAAC,gBAAgB,KAAK,QAAQ,CAAC,KAAK;iBACjE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBAClB,IAAI,CAAC,IAAI,CAAC,uDAAuD,CACzE,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CACD,WAAW,EACX,IAAI,EACJ,oCAAoC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC3F,IAAI,CACL,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,OAAO,IAAI,IAAI,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC;IAC3C,GAAG,CACD,gBAAgB,EAChB,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,SAAS,KAAK,SAAS,CAAC,QAAQ,CAAC,EAC/E,CAAC,SAAS;QACR,CAAC,CAAC,SAAS,CAAC,OAAO;YACjB,CAAC,CAAC,sBAAsB,SAAS,CAAC,IAAI,0CAA0C,SAAS,CAAC,SAAS,IAAI,SAAS,EAAE;YAClH,CAAC,CAAC,gEAAgE;QACpE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO;YAClB,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,0CAA0C,SAAS,CAAC,QAAQ,EAAE;YACjF,CAAC,CAAC,SAAS,CAAC,SAAS,KAAK,IAAI;gBAC5B,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,kGAAkG;gBACrH,CAAC,CAAC,SAAS,CAAC,SAAS,KAAK,SAAS,CAAC,QAAQ;oBAC1C,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,cAAc,SAAS,CAAC,SAAS,6BAA6B,SAAS,CAAC,QAAQ,EAAE;oBACrG,CAAC,CAAC,aAAa,SAAS,CAAC,SAAS,EAAE,CAC7C,CAAC;IAEF,8EAA8E;IAC9E,0EAA0E;IAC1E,8EAA8E;IAC9E,0EAA0E;IAC1E,8EAA8E;IAC9E,oEAAoE;IACpE,EAAE;IACF,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAC5E,IAAI,SAAS,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxE,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,GAAG,CACD,eAAe,EACf,KAAK,EACL,GAAG,kBAAkB,kEAAkE;gBACrF,sEAAsE,CACzE,CAAC;QACJ,CAAC;aAAM,IAAI,GAAG,KAAK,IAAI,IAAI,MAAM,CAAC,WAAW,KAAK,EAAE,EAAE,CAAC;YACrD,yEAAyE;YACzE,wEAAwE;YACxE,oEAAoE;YACpE,2DAA2D;YAC3D,GAAG,CACD,eAAe,EACf,IAAI,EACJ,sEAAsE,EACtE,IAAI,CACL,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,GAAG,CACD,eAAe,EACf,GAAG,KAAK,IAAI,CAAC,UAAU,EACvB,GAAG,KAAK,IAAI,CAAC,UAAU;gBACrB,CAAC,CAAC,sCAAsC,GAAG,EAAE;gBAC7C,CAAC,CAAC,2BAA2B,GAAG,yBAAyB,IAAI,CAAC,UAAU,KAAK;oBACzE,yEAAyE,CAChF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;QACrC,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,mDAAmD,CAAC,CAAC;IACjF,CAAC;SAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,GAAG,CAAC,cAAc,EAAE,KAAK,EAAE,mEAAmE,CAAC,CAAC;IAClG,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;QAC1F,CAAC;QACD,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC7B,qEAAqE;YACrE,wEAAwE;YACxE,QAAQ,CAAC,IAAI,CAAC,0CAA0C,MAAM,CAAC,iBAAiB,GAAG,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,CACX,eAAe,SAAS,CAAC,QAAQ,iDAAiD;gBAChF,qFAAqF,CACxF,CAAC;QACJ,CAAC;QACD,GAAG,CACD,cAAc,EACd,QAAQ,CAAC,MAAM,KAAK,CAAC,EACrB,QAAQ,CAAC,MAAM,KAAK,CAAC;YACnB,CAAC,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,iBAAiB,uBAAuB;YAClG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CACxB,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACnD,4EAA4E;IAC5E,8EAA8E;IAC9E,qEAAqE;IACrE,MAAM,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;IAC3E,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC;IACnE,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,GAAG,CAAC,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1F,CAAC;SAAM,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACnF,GAAG,CACD,gBAAgB,EAChB,IAAI,EACJ,2BAA2B,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAC3E,IAAI,CACL,CAAC;IACJ,CAAC;SAAM,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,yEAAyE;QACzE,uEAAuE;QACvE,4EAA4E;QAC5E,4DAA4D;QAC5D,GAAG,CACD,gBAAgB,EAChB,IAAI,EACJ,mBAAmB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,+BAA+B,EAChG,IAAI,CACL,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,+BAA+B,CAAC,CAAC;IAC/D,CAAC;IAED,8EAA8E;IAC9E,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC;IACnD,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QAChB,GAAG,CAAC,qBAAqB,EAAE,IAAI,EAAE,6DAA6D,CAAC,CAAC;IAClG,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACpD,GAAG,CACD,qBAAqB,EACrB,CAAC,SAAS,IAAI,KAAK,EACnB,CAAC,SAAS;YACR,CAAC,CAAC,4DAA4D;YAC9D,CAAC,CAAC,KAAK;gBACL,CAAC,CAAC,GAAG,SAAS,CAAC,QAAQ,oBAAoB,EAAE,EAAE;gBAC/C,CAAC,CAAC,GAAG,SAAS,CAAC,QAAQ,4BAA4B,EAAE,gBAAgB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,CAC5G,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,GAAG,CACD,eAAe,EACf,MAAM,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,EAChC,MAAM,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;QAC9B,CAAC,CAAC,qCAAqC;QACvC,CAAC,CAAC,uCAAuC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5E,CAAC;IAEF,6EAA6E;IAC7E,2EAA2E;IAC3E,8DAA8D;IAC9D,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;IAC7D,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAC1B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redlinegate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Engineering control plane for AI-assisted development: standards, merge gates and evidence across GitHub and Azure DevOps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
52
52
|
"prepack": "npm run build",
|
|
53
|
+
"stars": "node scripts/fetch-stars.mjs",
|
|
54
|
+
"publish:local": "node scripts/publish-local.mjs",
|
|
55
|
+
"publish:local:clean": "npm rm -g redlinegate",
|
|
53
56
|
"test": "node --test 'cli/**/__tests__/*.test.ts' 'scripts/**/__tests__/*.test.mjs'",
|
|
54
57
|
"typecheck": "tsc --noEmit"
|
|
55
58
|
},
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Refreshes web/lib/github-stars.json.
|
|
4
|
+
*
|
|
5
|
+
* Star counts drift. Rather than pretend otherwise we snapshot them, record the
|
|
6
|
+
* date the snapshot was taken, and render that date next to the number. The
|
|
7
|
+
* build never calls the network: if this file is stale, the site says so; if a
|
|
8
|
+
* repo is missing from it, the site omits the count for that repo rather than
|
|
9
|
+
* guessing.
|
|
10
|
+
*
|
|
11
|
+
* Run: node scripts/fetch-stars.mjs (needs an authenticated `gh`)
|
|
12
|
+
*/
|
|
13
|
+
import { execFile } from "node:child_process";
|
|
14
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
|
|
17
|
+
const run = promisify(execFile);
|
|
18
|
+
|
|
19
|
+
const SOURCE = new URL("../web/lib/skills-catalog.ts", import.meta.url);
|
|
20
|
+
const OUT = new URL("../web/lib/github-stars.json", import.meta.url);
|
|
21
|
+
|
|
22
|
+
/** Pull every `owner`/`repo` pair out of the PUBLISHERS table. */
|
|
23
|
+
function repoSlugs(src) {
|
|
24
|
+
const re = /owner:\s*"([^"]+)",\s*\n\s*repo:\s*"([^"]+)"/g;
|
|
25
|
+
const slugs = new Set();
|
|
26
|
+
for (const m of src.matchAll(re)) slugs.add(`${m[1]}/${m[2]}`);
|
|
27
|
+
return [...slugs].sort();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function stars(slug) {
|
|
31
|
+
// execFile, not exec: `slug` goes in as one argv entry, never through a shell.
|
|
32
|
+
const { stdout } = await run("gh", [
|
|
33
|
+
"api",
|
|
34
|
+
`repos/${slug}`,
|
|
35
|
+
"--jq",
|
|
36
|
+
".stargazers_count",
|
|
37
|
+
]);
|
|
38
|
+
const n = Number.parseInt(stdout.trim(), 10);
|
|
39
|
+
if (!Number.isFinite(n)) throw new Error(`unparseable count for ${slug}`);
|
|
40
|
+
return n;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const src = await readFile(SOURCE, "utf8");
|
|
44
|
+
const slugs = repoSlugs(src);
|
|
45
|
+
if (slugs.length === 0) {
|
|
46
|
+
console.error("No owner/repo pairs found — did PUBLISHERS move?");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const previous = await readFile(OUT, "utf8").then(
|
|
51
|
+
(t) => JSON.parse(t).stars ?? {},
|
|
52
|
+
() => ({}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const next = {};
|
|
56
|
+
let failed = 0;
|
|
57
|
+
for (const slug of slugs) {
|
|
58
|
+
try {
|
|
59
|
+
next[slug] = await stars(slug);
|
|
60
|
+
console.log(` ${String(next[slug]).padStart(7)} ${slug}`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
failed += 1;
|
|
63
|
+
// Drop the repo rather than carry the previous run's number forward. Every
|
|
64
|
+
// value in this file is stamped with one shared fetchedAt, so a carried
|
|
65
|
+
// number would be published under a date on which nobody counted it —
|
|
66
|
+
// the exact misstatement the dated snapshot exists to prevent. A repo with
|
|
67
|
+
// no number simply renders without the stars fact, which is honest.
|
|
68
|
+
console.warn(` failed: ${slug} — ${err.message.split("\n")[0]}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (failed === slugs.length) {
|
|
73
|
+
console.error("Every request failed; leaving the existing snapshot alone.");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const payload = {
|
|
78
|
+
fetchedAt: new Date().toISOString().slice(0, 10),
|
|
79
|
+
stars: Object.fromEntries(Object.entries(next).sort(([a], [b]) => a.localeCompare(b))),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const dropped = Object.keys(previous).filter((slug) => !(slug in next));
|
|
83
|
+
if (dropped.length) {
|
|
84
|
+
console.warn(
|
|
85
|
+
`\n${dropped.length} repo(s) in the previous snapshot have no number this run ` +
|
|
86
|
+
`and were dropped rather than re-dated: ${dropped.join(", ")}.\n` +
|
|
87
|
+
`Re-run to restore them; their pages render without a stars fact meanwhile.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await writeFile(OUT, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
92
|
+
console.log(`\nWrote ${Object.keys(payload.stars).length} repos to lib/github-stars.json`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Types for the CI rule parser, so cli/rules/__tests__/catalogue.test.ts can hold
|
|
2
|
+
// the CLI's own reader against it without an untyped import. The runtime module
|
|
3
|
+
// stays plain .mjs: it is run by node directly, never built.
|
|
4
|
+
export interface ScriptRule {
|
|
5
|
+
id: string;
|
|
6
|
+
stack: string;
|
|
7
|
+
severity: 'BLOCKER' | 'HIGH' | 'SUGGESTION';
|
|
8
|
+
text: string;
|
|
9
|
+
source: string;
|
|
10
|
+
line: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export declare const ROOT: string;
|
|
14
|
+
export declare const SEVERITIES: readonly string[];
|
|
15
|
+
export declare const RANK: Record<string, number>;
|
|
16
|
+
export declare const RULE_ID: RegExp;
|
|
17
|
+
export declare const RESERVED_RULE_IDS: ReadonlySet<string>;
|
|
18
|
+
export declare function loadRules(root?: string): Map<string, ScriptRule>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Build, pack and install this CLI globally, so `redline` on PATH is the working tree.
|
|
3
|
+
//
|
|
4
|
+
// The whole reason this is a script and not a one-line npm chain: which `npm` runs
|
|
5
|
+
// the global install decides whether the install is visible at all.
|
|
6
|
+
//
|
|
7
|
+
// Under pnpm, `npm` resolves to ./node_modules/.bin/npm — a locally installed npm
|
|
8
|
+
// that is not shimmed by Volta. Its `npm i -g` writes into its own global prefix,
|
|
9
|
+
// while the `redline` command on PATH is a Volta shim pointing at Volta's package
|
|
10
|
+
// image. The install reports success, the tarball is correct, and the binary you
|
|
11
|
+
// run is still the old one. That failure is silent and costs an hour.
|
|
12
|
+
//
|
|
13
|
+
// So: resolve npm explicitly, never through a node_modules/.bin that a package
|
|
14
|
+
// manager put in front, and verify afterwards that the binary on PATH is actually
|
|
15
|
+
// the build we just made.
|
|
16
|
+
|
|
17
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
|
+
// Present only in a current build: the check at the end greps the installed
|
|
25
|
+
// binary's own help for it.
|
|
26
|
+
const MARKER = 'redline status';
|
|
27
|
+
|
|
28
|
+
const run = (file, args, opts = {}) =>
|
|
29
|
+
execFileSync(file, args, { cwd: ROOT, stdio: 'inherit', ...opts });
|
|
30
|
+
|
|
31
|
+
// Volta sets _VOLTA_TOOL_RECURSION on anything it launches, and its shims skip
|
|
32
|
+
// their own hook when they see it — sensible, since it stops a tool Volta ran
|
|
33
|
+
// from re-entering Volta forever. It also means a global install performed from
|
|
34
|
+
// inside a script Volta started never updates the shim: the files land, npm says
|
|
35
|
+
// "added 1 package", and the `redline` on PATH keeps running the old build. That
|
|
36
|
+
// single variable is why every publish this session appeared to succeed and
|
|
37
|
+
// changed nothing. Clearing it for the install restores the behaviour of typing
|
|
38
|
+
// the command in a shell.
|
|
39
|
+
const withoutVoltaGuard = () => {
|
|
40
|
+
const env = { ...process.env };
|
|
41
|
+
delete env['_VOLTA_TOOL_RECURSION'];
|
|
42
|
+
return env;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const quiet = (command) => {
|
|
46
|
+
try {
|
|
47
|
+
return execSync(command, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
48
|
+
} catch {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The npm that a global install has to go through.
|
|
55
|
+
*
|
|
56
|
+
* Under Volta this is `~/.volta/bin/npm` — the SHIM. Not `volta which npm`,
|
|
57
|
+
* which returns the image binary underneath it: installing through that writes
|
|
58
|
+
* the package into the node image and never triggers the hook that builds the
|
|
59
|
+
* package image the `redline` shim actually executes. The install reports
|
|
60
|
+
* success, the files land, and the command keeps running the old build.
|
|
61
|
+
*
|
|
62
|
+
* Then any npm on PATH that is not inside a node_modules — that is one a package
|
|
63
|
+
* manager shadowed, as pnpm does with the npm semantic-release depends on.
|
|
64
|
+
*/
|
|
65
|
+
function resolveNpm() {
|
|
66
|
+
const shim = join(process.env.HOME ?? '', '.volta', 'bin', 'npm');
|
|
67
|
+
if (existsSync(shim)) return shim;
|
|
68
|
+
|
|
69
|
+
for (const dir of (process.env.PATH ?? '').split(':')) {
|
|
70
|
+
if (dir === '' || dir.includes('node_modules')) continue;
|
|
71
|
+
const candidate = join(dir, 'npm');
|
|
72
|
+
if (existsSync(candidate)) return candidate;
|
|
73
|
+
}
|
|
74
|
+
return 'npm';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The `redline` a person actually gets when they type it.
|
|
79
|
+
*
|
|
80
|
+
* PATH first. Falling back to Volta's shim matters because a non-interactive
|
|
81
|
+
* shell does not always carry ~/.volta/bin, and reporting "not installed" to
|
|
82
|
+
* somebody whose terminal runs it perfectly well is worse than useless.
|
|
83
|
+
*/
|
|
84
|
+
function liveBinary() {
|
|
85
|
+
// The shim first, when there is one. It is what a person's interactive shell
|
|
86
|
+
// runs, and it is the copy that goes stale — `command -v redline` in a
|
|
87
|
+
// non-interactive shell can easily resolve to a different, fresher copy and
|
|
88
|
+
// report success for a binary nobody types.
|
|
89
|
+
const shim = join(process.env.HOME ?? '', '.volta', 'bin', 'redline');
|
|
90
|
+
if (existsSync(shim)) return shim;
|
|
91
|
+
return quiet('command -v redline') !== '' ? 'redline' : '';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const npm = resolveNpm();
|
|
95
|
+
console.log(` using npm at ${npm}`);
|
|
96
|
+
|
|
97
|
+
// A unique version per publish, and this is the load-bearing part.
|
|
98
|
+
//
|
|
99
|
+
// package.json carries 0.0.0-development permanently — semantic-release sets the
|
|
100
|
+
// real version at publish time. Both npm and Volta key a global package by
|
|
101
|
+
// name@version, so every local publish looked identical to the last one and was
|
|
102
|
+
// served from cache: the tarball was rebuilt, the install reported success, and
|
|
103
|
+
// Volta restored the SAME old package image behind the shim. Each publish was
|
|
104
|
+
// actively downgrading the binary back to whatever was cached first.
|
|
105
|
+
//
|
|
106
|
+
// Stamping a timestamp version defeats both caches, and has a second use: the
|
|
107
|
+
// version `redline --version` prints is now the moment it was installed, so a
|
|
108
|
+
// stale binary is visible without any of this archaeology.
|
|
109
|
+
const manifestPath = join(ROOT, 'package.json');
|
|
110
|
+
const original = readFileSync(manifestPath, 'utf8');
|
|
111
|
+
const manifest = JSON.parse(original);
|
|
112
|
+
const localVersion = `0.0.0-local.${Math.floor(Date.now() / 1000)}`;
|
|
113
|
+
const tarball = `redlinegate-${localVersion}.tgz`;
|
|
114
|
+
|
|
115
|
+
// Clear Volta's entry first, and before anything else.
|
|
116
|
+
//
|
|
117
|
+
// Volta keeps its own copy of a global package — the one its shim runs — apart
|
|
118
|
+
// from the node image npm writes to. While it holds an entry, `npm i -g` updates
|
|
119
|
+
// only the node image and the shim keeps executing the old code. Order matters:
|
|
120
|
+
// clearing it after the pack leaves the shim deleted and not recreated, which is
|
|
121
|
+
// how `redline` kept vanishing from PATH.
|
|
122
|
+
const hasVolta = quiet('volta --version') !== '';
|
|
123
|
+
if (hasVolta) {
|
|
124
|
+
try {
|
|
125
|
+
execFileSync('volta', ['uninstall', 'redlinegate'], {
|
|
126
|
+
stdio: 'ignore',
|
|
127
|
+
env: withoutVoltaGuard(),
|
|
128
|
+
});
|
|
129
|
+
} catch {
|
|
130
|
+
// Not installed through Volta. Nothing to clear.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
writeFileSync(
|
|
136
|
+
manifestPath,
|
|
137
|
+
JSON.stringify({ ...manifest, version: localVersion }, null, 2) + '\n',
|
|
138
|
+
'utf8'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
run(npm, ['run', 'build']);
|
|
142
|
+
run(npm, ['pack']);
|
|
143
|
+
|
|
144
|
+
run(npm, ['i', '-g', `./${tarball}`], { env: withoutVoltaGuard() });
|
|
145
|
+
} finally {
|
|
146
|
+
// The real version is semantic-release's to set. Restoring it has to survive a
|
|
147
|
+
// failed build, or a crash here leaves a timestamp version committed.
|
|
148
|
+
writeFileSync(manifestPath, original, 'utf8');
|
|
149
|
+
rmSync(join(ROOT, tarball), { force: true });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Verify by RUNNING both and comparing what they print.
|
|
153
|
+
//
|
|
154
|
+
// Every other way of checking this was wrong at least once. A marker string in
|
|
155
|
+
// --help stays true across builds, so it passed while the installed copy was
|
|
156
|
+
// three changes behind. Comparing files means guessing which of the copies the
|
|
157
|
+
// command resolves to — and under Volta there are two, in different places,
|
|
158
|
+
// only one of which the shim executes. Executing it answers the only question
|
|
159
|
+
// that matters: does the command a person types run the code just built.
|
|
160
|
+
const binary = liveBinary();
|
|
161
|
+
if (binary === '') {
|
|
162
|
+
console.error('\n redline is not on PATH after installing. Check your shell PATH.\n');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const live = quiet(`${binary} --help`);
|
|
167
|
+
const built = quiet(`node "${join(ROOT, 'dist', 'bin', 'redline.js')}" --help`);
|
|
168
|
+
const digest = (text) => createHash('sha256').update(text).digest('hex').slice(0, 12);
|
|
169
|
+
|
|
170
|
+
if (live === '' || built === '') {
|
|
171
|
+
console.error('\n Could not run redline to compare builds.\n');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
if (live !== built) {
|
|
175
|
+
console.error('\n The installed redline is STALE: it runs older code than this build.');
|
|
176
|
+
console.error(` installed ${digest(live)} built ${digest(built)}`);
|
|
177
|
+
console.error('\n Under Volta the package image is cached by name@version, and this version');
|
|
178
|
+
console.error(' never changes. Clear it and install again:');
|
|
179
|
+
console.error(' volta uninstall redlinegate');
|
|
180
|
+
console.error(' ~/.volta/bin/npm i -g ./redlinegate-<version>.tgz\n');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
console.log(` installed redline runs this build (${digest(built)})`);
|