@oorabona/release-it-preset 1.0.0-rc.2 → 1.1.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/README.md CHANGED
@@ -654,7 +654,7 @@ pnpm release-it-preset doctor --json
654
654
  |----------|--------|
655
655
  | Environment | Known env vars, source (env / default / unset), publish-mode consistency |
656
656
  | Repository | Git repo presence, branch vs `GIT_REQUIRE_BRANCH`, latest tag, commit count, dirty WD, upstream tracking, remote URL |
657
- | Configuration | `CHANGELOG.md` exists + Keep a Changelog format + `[Unreleased]` content, `.release-it.json` parseable + `extends` field, `package.json` valid semver version |
657
+ | Configuration | `CHANGELOG.md` exists + Keep a Changelog format + `[Unreleased]` content, `.release-it.json` parseable + `extends` field, `package.json` valid semver version, `release-it` peer range satisfied, `release-it` major version advisor |
658
658
  | Readiness Summary | `PASS`/`WARN`/`FAIL` counts, score `N/M checks passing`, status (`READY`/`WARNINGS`/`BLOCKED`), actionable recommendations |
659
659
 
660
660
  **Exit codes:**
@@ -396,11 +396,149 @@ export function validateConfiguration(deps) {
396
396
  }
397
397
  }
398
398
  checks.push(detectWorkspaceIntegration(deps));
399
+ for (const result of validateReleaseItPeer(deps)) {
400
+ checks.push(result);
401
+ }
399
402
  return { checks, status: worstStatus(checks.map((c) => c.status)) };
400
403
  }
401
404
  // ---------------------------------------------------------------------------
402
405
  // 4. Summary
403
406
  // ---------------------------------------------------------------------------
407
+ // ---------------------------------------------------------------------------
408
+ // 3b. Release-it peer-dependency checks
409
+ // ---------------------------------------------------------------------------
410
+ /**
411
+ * Reads the preset's declared peerDependencies.release-it range.
412
+ * Looks in node_modules/@oorabona/release-it-preset/package.json first
413
+ * (installed package context), then ./package.json (source repo / dev),
414
+ * then falls back to a hardcoded constant.
415
+ */
416
+ function readPresetPeerRange(deps) {
417
+ const FALLBACK = '^19.0.0 || ^20.0.0';
418
+ const candidates = [
419
+ 'node_modules/@oorabona/release-it-preset/package.json',
420
+ 'package.json',
421
+ ];
422
+ for (const candidate of candidates) {
423
+ try {
424
+ if (!deps.existsSync(candidate))
425
+ continue;
426
+ const raw = deps.readFileSync(candidate, 'utf8');
427
+ const pkg = JSON.parse(raw);
428
+ const peers = pkg.peerDependencies;
429
+ if (peers?.['release-it']) {
430
+ return peers['release-it'];
431
+ }
432
+ }
433
+ catch {
434
+ // continue to next candidate
435
+ }
436
+ }
437
+ return FALLBACK;
438
+ }
439
+ /**
440
+ * Extracts the highest major version number from a semver range string.
441
+ * Handles OR-joined ranges like "^19.0.0 || ^20.0.0" → 20.
442
+ */
443
+ function highestMajorFromRange(range) {
444
+ const matches = range.match(/(\d+)\.\d+\.\d+/g) ?? [];
445
+ let max = 0;
446
+ for (const m of matches) {
447
+ const major = parseInt(m.split('.')[0], 10);
448
+ if (major > max)
449
+ max = major;
450
+ }
451
+ return max;
452
+ }
453
+ /**
454
+ * Checks whether an installed version satisfies a simplified peer range.
455
+ * Supports "^X.Y.Z || ^A.B.C" — checks that the installed major matches
456
+ * any major present in the range.
457
+ */
458
+ function satisfiesPeerRange(version, range) {
459
+ const installedMajor = parseInt(version.replace(/^v/, '').split('.')[0], 10);
460
+ const allowedMajors = Array.from(range.matchAll(/[~^]?(\d+)\.\d+\.\d+/g), (m) => parseInt(m[1], 10));
461
+ return allowedMajors.includes(installedMajor);
462
+ }
463
+ /**
464
+ * Runs Check A (peer range satisfaction) and Check B (major version advisor).
465
+ * Returns an array of CheckResult to be appended into validateConfiguration.
466
+ * Check B is silently skipped when the npm registry is unreachable.
467
+ */
468
+ export function validateReleaseItPeer(deps) {
469
+ const results = [];
470
+ const peerRange = readPresetPeerRange(deps);
471
+ // --- Check A: release-it in supported peer range ---
472
+ const lsOutput = safeExec('npm ls release-it --depth=0 --json', deps);
473
+ if (!lsOutput) {
474
+ results.push({
475
+ name: 'release-it peer dependency',
476
+ status: 'FAIL',
477
+ value: 'not found',
478
+ detail: 'release-it is not installed. Run: pnpm add -D release-it@^20',
479
+ });
480
+ }
481
+ else {
482
+ let installedVersion;
483
+ try {
484
+ const parsed = JSON.parse(lsOutput);
485
+ installedVersion = parsed.dependencies?.['release-it']?.version;
486
+ }
487
+ catch {
488
+ // parse failure treated as not found
489
+ }
490
+ if (!installedVersion) {
491
+ results.push({
492
+ name: 'release-it peer dependency',
493
+ status: 'FAIL',
494
+ value: 'not found',
495
+ detail: 'release-it is not installed. Run: pnpm add -D release-it@^20',
496
+ });
497
+ }
498
+ else if (!satisfiesPeerRange(installedVersion, peerRange)) {
499
+ results.push({
500
+ name: 'release-it peer dependency',
501
+ status: 'FAIL',
502
+ value: installedVersion,
503
+ detail: `Installed release-it ${installedVersion} is outside the supported range (${peerRange}). Run: pnpm add -D release-it@^20`,
504
+ });
505
+ }
506
+ else {
507
+ results.push({
508
+ name: 'release-it peer dependency',
509
+ status: 'PASS',
510
+ value: installedVersion,
511
+ });
512
+ }
513
+ }
514
+ // --- Check B: release-it major version advisor ---
515
+ // On network failure (null), skip the check entirely — no FAIL on outage.
516
+ const latestOutput = safeExec('npm view release-it version', deps);
517
+ if (latestOutput) {
518
+ const latestVersion = latestOutput.trim();
519
+ const latestMajor = parseInt(latestVersion.replace(/^v/, '').split('.')[0], 10);
520
+ const supportedMaxMajor = highestMajorFromRange(peerRange);
521
+ if (!Number.isNaN(latestMajor) && !Number.isNaN(supportedMaxMajor)) {
522
+ if (latestMajor > supportedMaxMajor) {
523
+ results.push({
524
+ name: 'release-it major version',
525
+ status: 'WARN',
526
+ value: latestVersion,
527
+ detail: `release-it ${latestMajor}.x available; preset's peer range max is ${supportedMaxMajor}.x. Coordinate with the preset maintainer before upgrading.`,
528
+ });
529
+ }
530
+ else {
531
+ results.push({
532
+ name: 'release-it major version',
533
+ status: 'PASS',
534
+ value: latestVersion,
535
+ });
536
+ }
537
+ }
538
+ }
539
+ // If latestOutput is null (network failure), push nothing for Check B.
540
+ return results;
541
+ }
404
542
  export function summarize(report) {
405
543
  const allChecks = [
406
544
  ...report.environment.checks,
@@ -65,6 +65,12 @@ export declare function safeExec(command: string, deps: DoctorDeps): string | nu
65
65
  export declare function collectEnvironment(deps: DoctorDeps): EnvironmentSection;
66
66
  export declare function inspectRepository(deps: DoctorDeps): RepositorySection;
67
67
  export declare function validateConfiguration(deps: DoctorDeps): ConfigurationSection;
68
+ /**
69
+ * Runs Check A (peer range satisfaction) and Check B (major version advisor).
70
+ * Returns an array of CheckResult to be appended into validateConfiguration.
71
+ * Check B is silently skipped when the npm registry is unreachable.
72
+ */
73
+ export declare function validateReleaseItPeer(deps: DoctorDeps): CheckResult[];
68
74
  export declare function summarize(report: Omit<DoctorReport, 'summary'>): DoctorSummary;
69
75
  export declare function runDoctor(deps: DoctorDeps): DoctorReport;
70
76
  export declare function formatHuman(report: DoctorReport): string;
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@oorabona/release-it-preset",
3
- "version": "1.0.0-rc.2",
4
- "description": "Shared release-it preset with OIDC trusted publishing, smart npm dist-tag selection, and monorepo per-package changelog support",
3
+ "version": "1.1.0",
4
+ "description": "Release tooling for solo and small-team JS maintainers: human-curated changelogs, OIDC zero-config publishing, recovery presets, monorepo support, pre-release diagnostics.",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "release-it",
8
8
  "release",
9
- "version",
10
9
  "changelog",
11
10
  "conventional-commits",
12
11
  "keep-a-changelog",
@@ -16,9 +15,12 @@
16
15
  "oidc",
17
16
  "trusted-publishing",
18
17
  "github-actions",
19
- "automation",
20
18
  "typescript",
21
- "semver"
19
+ "semver",
20
+ "cli",
21
+ "developer-tools",
22
+ "pnpm",
23
+ "slsa"
22
24
  ],
23
25
  "author": "Olivier Orabona",
24
26
  "license": "MIT",