@rtorcato/repo-tooling 3.11.1 → 3.12.0
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/dist/base/github-settings.js +73 -4
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
3
4
|
import chalk from 'chalk';
|
|
4
5
|
import fs from 'fs-extra';
|
|
5
6
|
const GH_TIMEOUT_MS = 10_000;
|
|
@@ -345,10 +346,55 @@ async function hasCodeScanningRuleset(gh, nwo, branch) {
|
|
|
345
346
|
}
|
|
346
347
|
return 'no';
|
|
347
348
|
}
|
|
349
|
+
/** Release config filenames semantic-release picks up that we can import. */
|
|
350
|
+
const RELEASE_CONFIG_FILES = ['release.config.mjs', 'release.config.js'];
|
|
351
|
+
/** How to stop the GH013 collision, wherever it is reported. */
|
|
352
|
+
const GIT_PLUGIN_REMEDY = 'Drop `@semantic-release/git` and `@semantic-release/changelog` from the release config (rtorcato/repo-tooling#417) — the shipped `semantic-release/github` preset already has, so a bare re-export of it is enough. Do NOT add a bypass actor to the ruleset: it exempts the one commit nobody reviews';
|
|
353
|
+
/**
|
|
354
|
+
* Does the repo's release config still resolve `@semantic-release/git`?
|
|
355
|
+
*
|
|
356
|
+
* That plugin pushes the release commit straight to the default branch, which a
|
|
357
|
+
* `code_scanning` ruleset rejects with GH013 — a commit created seconds earlier
|
|
358
|
+
* can never carry CodeQL results (#417). The two are individually correct and
|
|
359
|
+
* jointly unwinnable, and the failure is silent, because a merge that produces
|
|
360
|
+
* no release goes green either way.
|
|
361
|
+
*
|
|
362
|
+
* The config is imported rather than grepped, because every cheaper signal is
|
|
363
|
+
* wrong on a config we ship or recommend: a bare re-export names no plugin at
|
|
364
|
+
* all; a pinned older repo-tooling re-exports a preset that *does* have it; the
|
|
365
|
+
* current preset mentions it in a comment explaining its absence; and the
|
|
366
|
+
* documented way to drop it lists the name in a filter. Only the resolved
|
|
367
|
+
* `plugins` array distinguishes those. Importing config is what semantic-release
|
|
368
|
+
* itself does with this file.
|
|
369
|
+
*
|
|
370
|
+
* `false` on anything unreadable — a false negative is a missed warning, a false
|
|
371
|
+
* positive is a wrong one.
|
|
372
|
+
*/
|
|
373
|
+
export async function releaseUsesGitPlugin(dir) {
|
|
374
|
+
for (const name of RELEASE_CONFIG_FILES) {
|
|
375
|
+
const file = path.join(dir, name);
|
|
376
|
+
if (!(await fs.pathExists(file)))
|
|
377
|
+
continue;
|
|
378
|
+
try {
|
|
379
|
+
const mod = await import(pathToFileURL(file).href);
|
|
380
|
+
const plugins = mod.default?.plugins;
|
|
381
|
+
if (!Array.isArray(plugins))
|
|
382
|
+
return false;
|
|
383
|
+
return plugins.some((p) => (Array.isArray(p) ? p[0] : p) === '@semantic-release/git');
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
348
391
|
/**
|
|
349
392
|
* The #269 gap: CodeQL results are advisory by default — a High alert still
|
|
350
393
|
* merges unless a branch ruleset requires the code-scanning check. Only
|
|
351
394
|
* meaningful where CodeQL is actually on, so it no-ops otherwise.
|
|
395
|
+
*
|
|
396
|
+
* It also reports the #419 collision: a gate that is correctly in place is still
|
|
397
|
+
* drift when the release config pushes to the branch it guards.
|
|
352
398
|
*/
|
|
353
399
|
async function checkCodeScanningRuleset(gh, nwo, branch, dir) {
|
|
354
400
|
const check = CODE_SCANNING_CHECK;
|
|
@@ -358,14 +404,25 @@ async function checkCodeScanningRuleset(gh, nwo, branch, dir) {
|
|
|
358
404
|
const found = await hasCodeScanningRuleset(gh, nwo, branch);
|
|
359
405
|
if (found === 'skip')
|
|
360
406
|
return skip(check, 'could not read rulesets');
|
|
407
|
+
const gitPlugin = await releaseUsesGitPlugin(dir);
|
|
361
408
|
if (found === 'yes') {
|
|
409
|
+
if (gitPlugin) {
|
|
410
|
+
return {
|
|
411
|
+
check,
|
|
412
|
+
status: 'drift',
|
|
413
|
+
detail: `active ruleset requires code-scanning on ${branch}, but the release config still uses \`@semantic-release/git\` — its push to ${branch} will be rejected with GH013, and the release fails silently`,
|
|
414
|
+
hint: GIT_PLUGIN_REMEDY,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
362
417
|
return { check, status: 'ok', detail: `active ruleset requires code-scanning on ${branch}` };
|
|
363
418
|
}
|
|
364
419
|
return {
|
|
365
420
|
check,
|
|
366
421
|
status: 'drift',
|
|
367
422
|
detail: `CodeQL is on but no active ruleset requires code-scanning on ${branch} (High alerts stay advisory)`,
|
|
368
|
-
hint:
|
|
423
|
+
hint: gitPlugin
|
|
424
|
+
? `Run \`npx @rtorcato/repo-tooling fix github-settings\` to add a code_scanning branch ruleset that blocks merge on High+ CodeQL alerts. That ruleset will reject this repo's release commit with GH013 while the config uses \`@semantic-release/git\`. ${GIT_PLUGIN_REMEDY}`
|
|
425
|
+
: 'Run `npx @rtorcato/repo-tooling fix github-settings` to add a code_scanning branch ruleset that blocks merge on High+ CodeQL alerts',
|
|
369
426
|
};
|
|
370
427
|
}
|
|
371
428
|
/** The branch-protection body PUT to the API — mirrors the doctor standard. */
|
|
@@ -484,9 +541,13 @@ export async function applyGithubSettings(dir, exec) {
|
|
|
484
541
|
console.error(chalk.yellow(` could not apply ${cmd.label}: ${r.stderr.trim() || 'gh error'}`));
|
|
485
542
|
}
|
|
486
543
|
// Code-scanning ruleset (#269): POST only when CodeQL is on and no active gate
|
|
487
|
-
// covers the default branch
|
|
488
|
-
|
|
489
|
-
|
|
544
|
+
// covers the default branch. Asked directly rather than via the check's
|
|
545
|
+
// status, because that status is also `drift` when the gate is already
|
|
546
|
+
// installed and merely collides with the release config (#419) — keying the
|
|
547
|
+
// POST off it would file a duplicate ruleset.
|
|
548
|
+
const codeql = await codeqlEnabled(gh, info.nwo, dir);
|
|
549
|
+
const ruleset = codeql ? await hasCodeScanningRuleset(gh, info.nwo, info.branch) : 'skip';
|
|
550
|
+
if (ruleset === 'no') {
|
|
490
551
|
const label = `code-scanning ruleset on ${info.branch}`;
|
|
491
552
|
const r = await gh(['api', '-X', 'POST', `repos/${info.nwo}/rulesets`, '--input', '-'], CODE_SCANNING_RULESET_BODY);
|
|
492
553
|
if (r.ok)
|
|
@@ -494,6 +555,14 @@ export async function applyGithubSettings(dir, exec) {
|
|
|
494
555
|
else
|
|
495
556
|
console.error(chalk.yellow(` could not apply ${label}: ${r.stderr.trim() || 'gh error'}`));
|
|
496
557
|
}
|
|
558
|
+
// #419: the gate is right and the release config is wrong, so say so instead
|
|
559
|
+
// of quietly installing the half that breaks the next release. Warned on
|
|
560
|
+
// `yes` too — that repo is already broken, it just hasn't released yet.
|
|
561
|
+
if (ruleset !== 'skip' && (await releaseUsesGitPlugin(dir))) {
|
|
562
|
+
console.error(chalk.yellow(` warning: code-scanning is enforced on ${info.branch}, but the release config still uses \`@semantic-release/git\`.\n` +
|
|
563
|
+
` Its push to ${info.branch} will be rejected with GH013 and the release will fail silently.\n` +
|
|
564
|
+
` ${GIT_PLUGIN_REMEDY}.`));
|
|
565
|
+
}
|
|
497
566
|
if (applied.length === 0)
|
|
498
567
|
console.error(chalk.gray(' already configured — nothing to apply'));
|
|
499
568
|
return applied;
|
package/package.json
CHANGED