rvlib-shipkit 0.17.1 → 0.17.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.2
4
+
5
+ - **A release no longer probes the world.** The release gate runs only the checks that can BLOCK. A check declaring `blocking: false` cannot change the outcome, so running it spent time and a third party API call per publish to learn whether a DNS provider was reachable. `release.requireHealthy` still makes those failures fatal, and then they run.
6
+ - **The release gate stopped busting the check caches.** 0.17.0's gate ran fresh, so every cached probe refired on every publish. Caching exists to rate-limit exactly those calls.
7
+ - **New `release.skipTasks`.** Names the repo-wide tasks a release does not need, for a monorepo whose commit gate runs every package's suite. Releasing one unit already runs that unit's own tests and typecheck, and the other packages are not in its tarball.
8
+
9
+
3
10
  ## 0.17.1
4
11
 
5
12
  - **Fixed: `shipkit ci`'s fix pass ignored `defaults: { 'imports:check': false }`** and rewrote every relative import anyway. Reported from a repo that deliberately keeps relative imports: one plain `shipkit ci` rewrote 215 files with no tsconfig paths wired, so typecheck and the tests went red on the gate's own repair. A fix slot repairs the same concern its floor task checks, so one disable governs both directions now.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rvlib-shipkit",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "description": "All-in-one repo companion: ci gate (task DAG), auto-discovered health checks, release-contract fulfillness, asks queue, md-wiki tools, asset atelier, live tui",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,6 +36,12 @@ export type CheckOpts = {
36
36
  * decides severity and the engine that runs the rules must read the same
37
37
  * object, or a blocking rule failure silently reclassifies as a probe */
38
38
  config?: RepoConfig
39
+ /** run only the checks that can BLOCK. A check declaring blocking:false
40
+ * cannot change the outcome, so running it is pure cost: a release does
41
+ * not need to know whether a DNS provider is reachable, and probing one
42
+ * spends a third-party API call per publish. Undeclared checks are still
43
+ * collected, because an unclassified check stays fatal. */
44
+ onlyBlocking?: boolean
39
45
  }
40
46
 
41
47
  export type CheckRun = {
@@ -78,13 +84,15 @@ export async function collectChecks(root: string, o: CheckOpts = {}): Promise<Ch
78
84
  t.failed = false
79
85
  t.warned = true
80
86
  }
81
- for (const rule of [...RULES, ...(c.rules ?? [])])
87
+ for (const rule of [...RULES, ...(c.rules ?? [])]) {
88
+ if (o.onlyBlocking === true && !rule.blocking) continue
82
89
  suite.addCheck({
83
90
  name: rule.title,
84
91
  remainValidFor: rule.ttl,
85
92
  cacheExtra: rule.cacheKey != null ? () => rule.cacheKey!({ root, config: c }) : undefined,
86
93
  logic: async (t) => { await runRuleLint(rule, t, { root, config: c }); demote(t, rule.blocking, rule.title) },
87
94
  })
95
+ }
88
96
  const childEnv: Record<string, string | undefined> = {
89
97
  ...process.env,
90
98
  ...(o.fresh === true ? { SHIPKIT_FRESH: '1', RV_CHECK_FRESH: '1' } : {}),
@@ -95,6 +103,7 @@ export async function collectChecks(root: string, o: CheckOpts = {}): Promise<Ch
95
103
  // check is an error, never a guess
96
104
  const blocking = d.blocking ?? c.checkDefaults?.blocking
97
105
  if (blocking == null) undeclared.push(rel)
106
+ if (o.onlyBlocking === true && blocking !== true) continue
98
107
  suite.addCheck({
99
108
  name: d.name,
100
109
  remainValidFor: d.selfCached ? undefined : DISCOVERED_TTL,
@@ -27,9 +27,11 @@ export type CiOpts = {
27
27
  fix?: boolean
28
28
  /** also apply the structural SK rule fixers during the fix pass */
29
29
  fixRules?: boolean
30
- /** bust every check's TTL cache. A release gates with this on: a blocking
31
- * rule replaying a clean pass from hours ago is not a gate. */
32
- fresh?: boolean
30
+ /** run only the checks that can block (core/run-check onlyBlocking) */
31
+ onlyBlockingChecks?: boolean
32
+ /** task ids the caller does not need. A sub-unit release skips the tasks
33
+ * that gate the whole repo, since the unit's own gate already ran. */
34
+ skipTaskIds?: string[]
33
35
  /** report-only for self-maintaining rules and checks (SHIPKIT_FROZEN
34
36
  * travels to child checks). The release path passes it: a rule that
35
37
  * rewrites package.json between the gate and `npm publish` packs a tree
@@ -76,7 +78,11 @@ export async function runCi(root: string, o: CiOpts = {}): Promise<number> {
76
78
  if (o.fix !== false)
77
79
  fixFailed = (await runFixPass(root, c, { rules: o.fixRules !== false, print: sink })).failed
78
80
  else sink('skip: fix pass (--no-fix)')
79
- const { tasks, disabled, unfilled } = allTasks(c, root)
81
+ const all = allTasks(c, root)
82
+ const { disabled, unfilled } = all
83
+ const skipIds = new Set(o.skipTaskIds ?? [])
84
+ const tasks = all.tasks.filter((t) => !skipIds.has(t.id))
85
+ for (const t of all.tasks) if (skipIds.has(t.id)) sink(`skip: ${t.id} (not part of this release, the unit's own gate covers it)`)
80
86
  for (const d of disabled) sink(`skip: ${d} (skipped-by-config)`)
81
87
  // a floor slot nothing could fill is VISIBLE — a thin gate must never
82
88
  // look like a full one (the fresh-repo floor gap)
@@ -96,7 +102,7 @@ export async function runCi(root: string, o: CiOpts = {}): Promise<number> {
96
102
  // declares whether it blocks, and that is the only question about it.
97
103
  const checks = o.skipChecks === true
98
104
  ? { lines: [], blocked: false, advisoryFailures: [], undeclared: [] }
99
- : await collectChecks(root, { status: false, config: c, frozen: o.frozen, fresh: o.fresh })
105
+ : await collectChecks(root, { status: false, config: c, frozen: o.frozen, onlyBlocking: o.onlyBlockingChecks })
100
106
  const color = process.stdout.isTTY && process.env.NO_COLOR == null
101
107
  for (const l of checks.lines) { collected.push(formatLine(l, false)); print(formatLine(l, color)) }
102
108
  for (const u of checks.undeclared) {
@@ -39,6 +39,11 @@ export type ReleaseConfig = {
39
39
  /** repo-relative tsconfig for the ROOT unit's own typecheck (default
40
40
  * '<root>/tsconfig.json' when it exists) */
41
41
  tsconfig?: string
42
+ /** task ids the release gate skips. A monorepo's repo-wide task (every
43
+ * package's suite, a full matrix build) re-proves for six units what the
44
+ * released unit's own gate already proved for one. Naming them here is
45
+ * the repo saying which tasks are about the REPO rather than the release. */
46
+ skipTasks?: string[]
42
47
  /** `shipkit bump` run from a feature branch merges it into the release
43
48
  * branch first, with a merge commit. Default true. A branch is a GATE, so
44
49
  * this is deliberately reachable ONLY by running bump by hand. */
@@ -92,11 +97,13 @@ export async function runReleaseGate(root: string, config: RepoConfig, print: (l
92
97
  fix: o.fix ?? false,
93
98
  frozen: true,
94
99
  status: false,
95
- // a release must not gate on a clean pass cached hours ago: SK005's
96
- // blocking branches are local facts (a link: that stopped resolving, a
97
- // dep nobody installed) and a TTL replays them as green
98
- fresh: true,
99
100
  config,
101
+ // a check that cannot block cannot change the outcome, so a release
102
+ // never probes the world: publishing a lib does not depend on whether
103
+ // a DNS provider answers, and asking spends an API call per publish.
104
+ // requireHealthy is the repo saying it wants them fatal, so then they run.
105
+ onlyBlockingChecks: config.release?.requireHealthy !== true,
106
+ skipTaskIds: config.release?.skipTasks ?? [],
100
107
  failOnAdvisory: config.release?.requireHealthy === true,
101
108
  print: (l) => print(` ${l}`),
102
109
  })