rvlib-shipkit 0.35.0 → 0.35.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.35.1
|
|
4
|
+
|
|
5
|
+
- **SK013's green line claimed a replacement that could not be named.** With the guard declared but not yet installed, the rule WARNed correctly and then printed `closes 14/14 raw runner(s), with the replacement named` directly underneath it. Both lines were about the same file and only one of them was true. The summary now reports what the file actually delivers: the guard is `declared, not yet runnable` until its script resolves.
|
|
6
|
+
|
|
3
7
|
## 0.35.0
|
|
4
8
|
|
|
5
9
|
- **`shipkit pb collection` mints imports of a package that exists.** The pocketbase data layer and the Mantine shell are one package now, `rvlib-pb`: the generated `columns.tsx`, `grid.tsx` and `index.ts` import `Column`, `LiveGrid` and `defineCollectionUI` from `rvlib-pb/ui`, and `LiveList` from `rvlib-pb`. The two tiers are different subpaths on purpose, so a generated grid that took its type from the ui subpath would typecheck and would put `@mantine/core` in the tsconfig of anything that reads it. This generator ships inside the tarball and writes into repos it never sees, so a stale specifier here keeps producing broken scaffolds long after a rename lands: the suite now asserts every import string the generator emits, which nothing did before.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rvlib-shipkit",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.1",
|
|
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",
|
|
@@ -32,6 +32,12 @@ export type DiscoveredCheck = {
|
|
|
32
32
|
/** `export const never = 'why'`, read statically — a DECIDED never
|
|
33
33
|
* cacheable, which takes the check off the burn-down list. */
|
|
34
34
|
never: string | null
|
|
35
|
+
/** `export const gate = false`, read statically — this check is NOT part of
|
|
36
|
+
* `shipkit ci`. `ci` answers "is my change good"; a check that answers
|
|
37
|
+
* something else, and that can sit red for months without anything being
|
|
38
|
+
* wrong, does not belong in the answer. `shipkit check` still runs it.
|
|
39
|
+
* Default true: a check is in the gate unless it opts out by name. */
|
|
40
|
+
gate: boolean
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
// read without importing: checks run as subprocesses, and discovery must stay
|
|
@@ -44,6 +50,10 @@ const BLOCKING_RE = /^\s*export\s+const\s+blocking\s*(?::\s*boolean\s*)?=\s*(tru
|
|
|
44
50
|
const INPUTS_RE = /^\s*export\s+const\s+inputs\s*(?::[^=]*)?=\s*\[([^\]]*)\]/m
|
|
45
51
|
const TTL_RE = /^\s*export\s+const\s+ttl\s*(?::\s*string\s*)?=\s*['"]([^'"]+)['"]/m
|
|
46
52
|
const NEVER_RE = /^\s*export\s+const\s+never\s*(?::\s*string\s*)?=\s*['"]([^'"]+)['"]/m
|
|
53
|
+
// `export const gate = false` takes a check out of `shipkit ci`. Only the
|
|
54
|
+
// literal false opts out: anything unreadable stays in the gate, because a
|
|
55
|
+
// scanner that guesses its way to SKIPPING a check is how coverage disappears
|
|
56
|
+
const GATE_RE = /^\s*export\s+const\s+gate\s*(?::\s*boolean\s*)?=\s*false\s*$/m
|
|
47
57
|
const STRING_ITEM_RE = /['"]([^'"]+)['"]/g
|
|
48
58
|
|
|
49
59
|
export function discoverChecks(root: string): DiscoveredCheck[] {
|
|
@@ -69,6 +79,7 @@ export function discoverChecks(root: string): DiscoveredCheck[] {
|
|
|
69
79
|
let inputs: string[] | null = null
|
|
70
80
|
let ttl: string | null = null
|
|
71
81
|
let never: string | null = null
|
|
82
|
+
let gate = true
|
|
72
83
|
try {
|
|
73
84
|
const src = readFileSync(f, 'utf8')
|
|
74
85
|
const m = src.match(BLOCKING_RE)
|
|
@@ -80,7 +91,8 @@ export function discoverChecks(root: string): DiscoveredCheck[] {
|
|
|
80
91
|
}
|
|
81
92
|
ttl = src.match(TTL_RE)?.[1] ?? null
|
|
82
93
|
never = src.match(NEVER_RE)?.[1] ?? null
|
|
94
|
+
gate = src.match(GATE_RE) == null
|
|
83
95
|
} catch {}
|
|
84
|
-
return { path: f, name, blocking, inputs, ttl, never }
|
|
96
|
+
return { path: f, name, blocking, inputs, ttl, never, gate }
|
|
85
97
|
})
|
|
86
98
|
}
|
package/src/core/run-check.ts
CHANGED
|
@@ -50,6 +50,11 @@ export type CheckOpts = {
|
|
|
50
50
|
* spends a third-party API call per publish. Undeclared checks are still
|
|
51
51
|
* collected, because an unclassified check stays fatal. */
|
|
52
52
|
onlyBlocking?: boolean
|
|
53
|
+
/** the caller is the GATE (`shipkit ci`), so checks that declared
|
|
54
|
+
* `export const gate = false` are skipped. `shipkit check` leaves it unset
|
|
55
|
+
* and runs everything: the health surface is where a human todo list
|
|
56
|
+
* belongs, and the gate is not. */
|
|
57
|
+
gateOnly?: boolean
|
|
53
58
|
/** the caller narrowed the run to these ids (`ci --only`). Empty or absent
|
|
54
59
|
* means everything. A `--only` that narrowed TASKS while every check still
|
|
55
60
|
* ran made the flag actively harmful: `ci --only cycles`, a 0.03s task,
|
|
@@ -201,6 +206,12 @@ export async function collectChecks(root: string, o: CheckOpts = {}): Promise<Ch
|
|
|
201
206
|
// file wins, then the repo default, then nothing: an unclassified
|
|
202
207
|
// check is an error, never a guess
|
|
203
208
|
if (!matchesOnly(o.only, d.name, rel)) continue
|
|
209
|
+
// `ci` answers "is my change good". A check that answers something else,
|
|
210
|
+
// and that can sit red for months with nothing wrong, is not part of
|
|
211
|
+
// that answer at any interval. `shipkit check` still runs it, and
|
|
212
|
+
// naming it in `--only` still runs it, so opting out is never a way to
|
|
213
|
+
// make a check unreachable.
|
|
214
|
+
if (o.gateOnly === true && !d.gate && o.only == null) continue
|
|
204
215
|
const blocking = d.blocking ?? c.checkDefaults?.blocking
|
|
205
216
|
if (blocking == null) undeclared.push(rel)
|
|
206
217
|
if (o.onlyBlocking === true && blocking !== true) continue
|
package/src/core/run-ci.ts
CHANGED
|
@@ -271,7 +271,7 @@ async function runCiInScope(root: string, o: CiOpts = {}): Promise<number> {
|
|
|
271
271
|
if (survey != null) await survey
|
|
272
272
|
const checks = o.skipChecks === true
|
|
273
273
|
? { lines: [], blocked: false, advisoryFailures: [], undeclared: [] }
|
|
274
|
-
: await collectChecks(root, { status: false, config: c, frozen: o.frozen, onlyBlocking: o.onlyBlockingChecks, only: o.onlyIds, fresh: o.fresh, ledger, reportHome: false })
|
|
274
|
+
: await collectChecks(root, { status: false, config: c, frozen: o.frozen, onlyBlocking: o.onlyBlockingChecks, only: o.onlyIds, fresh: o.fresh, ledger, reportHome: false, gateOnly: true })
|
|
275
275
|
if (priorShare == null) delete process.env[SHARE_ENV]; else process.env[SHARE_ENV] = priorShare
|
|
276
276
|
for (const f of [sharePath, `${sharePath}.lock`]) { try { rmSync(f, { force: true }) } catch {} }
|
|
277
277
|
const color = process.stdout.isTTY && process.env.NO_COLOR == null
|
|
@@ -103,10 +103,12 @@ export const oneWayInPolicy: Rule = {
|
|
|
103
103
|
if (missing.length > 0)
|
|
104
104
|
t.fail(`${SETTINGS_PATH}: ${missing.length} raw runner(s) still permitted (${missing.slice(0, 3).join(', ')}${missing.length > 3 ? ', …' : ''}) — those calls never reach shipkit, so nothing is cached and no run can be attached to`)
|
|
105
105
|
const guard = guardHookCommand(s)
|
|
106
|
+
let guardRuns = false
|
|
106
107
|
if (guard == null)
|
|
107
108
|
t.fail(`${SETTINGS_PATH}: no PreToolUse guard — a bare deny tells the model only that it was denied, so it goes looking for another runner instead of the shipkit command`)
|
|
108
109
|
else {
|
|
109
110
|
const missing = guardScriptMissing(env.root, guard)
|
|
111
|
+
guardRuns = missing == null
|
|
110
112
|
// the hook is DECLARED either way, so it starts working the moment
|
|
111
113
|
// the dependency lands: no second fix run, nothing to remember. An
|
|
112
114
|
// uninstalled dependency is therefore a WARN naming the command
|
|
@@ -124,7 +126,11 @@ export const oneWayInPolicy: Rule = {
|
|
|
124
126
|
`point it at this repo's own copy of hooks/one-way-in-guard.ts, or delete the hook and let the deny list stand alone`,
|
|
125
127
|
)
|
|
126
128
|
}
|
|
127
|
-
|
|
129
|
+
// the summary states only what the file actually delivers: claiming the
|
|
130
|
+
// replacement is named while the guard cannot run is the green that
|
|
131
|
+
// measured nothing, one line under the WARN that says otherwise
|
|
132
|
+
if (!t.failed)
|
|
133
|
+
t.ok(`${SETTINGS_PATH} closes ${want.length}/${RAW_RUNNERS.length} raw runner(s)${guardRuns ? ', with the replacement named' : ' — the guard that names the replacement is declared, not yet runnable'}`)
|
|
128
134
|
},
|
|
129
135
|
// MERGES, never rewrites: this file also carries the attribution silencer,
|
|
130
136
|
// a repo's own allow list and its own hooks, and clobbering those to fix a
|