rvlib-shipkit 0.3.1 → 0.4.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/CHANGELOG.md +22 -0
- package/package.json +1 -1
- package/src/core/package-json-check.ts +87 -0
- package/src/core/run-check.ts +7 -0
- package/src/init-repo.ts +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-07-22
|
|
4
|
+
|
|
5
|
+
New (wave 1 of the shipkit expansion — uniform repo setup):
|
|
6
|
+
|
|
7
|
+
- built-in `package.json` probe in `shipkit check`
|
|
8
|
+
(`core/package-json-check.ts`): FAILs on `license` present / `private` not
|
|
9
|
+
true / missing capability-required scripts (repo.config → ci check
|
|
10
|
+
contracts tui hub; `.rv-assets/kit.ts` → assets; `.rv-identity/brand.ts`
|
|
11
|
+
→ assets:identity; `.rv-comfy/` → comfy:up; `deploy.sh` → up; ask dir →
|
|
12
|
+
asks); WARNs on banned aliases (`serve`→`hub`, `identity`→
|
|
13
|
+
`assets:identity`, `pretty`→`fmt`, `check-types`→`typecheck`; body-matched
|
|
14
|
+
so an unrelated `serve` is left alone) and a missing `test`/`lint`/`fmt`
|
|
15
|
+
baseline. rvlib-* roots invert (MIT required, private banned). Spec:
|
|
16
|
+
wiki `meta/rv-conventions/package-json.md`. Doctor surface only — not in
|
|
17
|
+
the ci gate.
|
|
18
|
+
- `init` scaffolds the full canonical script set: `ci check contracts tui
|
|
19
|
+
hub` (hub runs `shipkit serve`) + a `test: bun test` baseline — was
|
|
20
|
+
ci/check/contracts only.
|
|
21
|
+
- upgraders from 0.3.x: the probe FAILs on the missing `tui`/`hub` scripts —
|
|
22
|
+
re-run `shipkit init` (non-clobber, it only adds the gaps) and rename any
|
|
23
|
+
`serve` hub-script to `hub`.
|
|
24
|
+
|
|
3
25
|
## 0.3.1 — 2026-07-22
|
|
4
26
|
|
|
5
27
|
- fix: `react-dom` was missing from dependencies AND the CLI entry loaded
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rvlib-shipkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// built-in `package.json` probe for `shipkit check` — enforces the canonical
|
|
2
|
+
// shape (meta/rv-conventions/package-json.md): private:true, no license,
|
|
3
|
+
// capability-required script names FAIL, banned aliases + missing baseline
|
|
4
|
+
// trio WARN. Doctor surface only — never wired into the ci gate. Pure local
|
|
5
|
+
// fs, no TTL.
|
|
6
|
+
import { existsSync } from 'fs'
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import type { CheckCtx } from './check-ctx.ts'
|
|
9
|
+
import type { RepoConfig } from '../repo-config.ts'
|
|
10
|
+
|
|
11
|
+
type Pkg = { name?: string; private?: boolean; license?: string; scripts?: Record<string, string> }
|
|
12
|
+
|
|
13
|
+
// alias → { canonical, bodyMatch } — WARN only when the body proves the
|
|
14
|
+
// alias really is the canonical capability (a `serve` that serves a dist/
|
|
15
|
+
// folder must not be told to rename)
|
|
16
|
+
const ALIASES: Record<string, { canonical: string; bodyMatch: RegExp }> = {
|
|
17
|
+
'serve': { canonical: 'hub', bodyMatch: /(shipkit|cli\.ts) serve\b/ },
|
|
18
|
+
'identity': { canonical: 'assets:identity', bodyMatch: /assets(:| )identity\b/ },
|
|
19
|
+
'pretty': { canonical: 'fmt', bodyMatch: /./ },
|
|
20
|
+
'check-types': { canonical: 'typecheck', bodyMatch: /./ },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const BASELINE = ['test', 'lint', 'fmt'] as const
|
|
24
|
+
|
|
25
|
+
export async function evaluatePackageJson(t: CheckCtx, root: string, c: RepoConfig): Promise<void> {
|
|
26
|
+
const spec = `${c.docsBase ?? ''}meta/rv-conventions/package-json.md`
|
|
27
|
+
let pkg: Pkg
|
|
28
|
+
try {
|
|
29
|
+
pkg = (await Bun.file(join(root, 'package.json')).json()) as Pkg
|
|
30
|
+
} catch (e) {
|
|
31
|
+
t.fail(`package.json unreadable (${String(e).slice(0, 120)})`, spec)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const scripts = pkg.scripts ?? {}
|
|
35
|
+
// rvlib-* as a probe ROOT is theoretical (libs live as sub-packages and
|
|
36
|
+
// the probe only runs where repo.config.ts loads) — field inversion is
|
|
37
|
+
// still handled so a future lib-root repo fails correctly, not absurdly
|
|
38
|
+
const isLib = pkg.name?.startsWith('rvlib-') === true
|
|
39
|
+
|
|
40
|
+
// published rvlib-* roots invert the field rules (distribute/npm owns them)
|
|
41
|
+
if (isLib) {
|
|
42
|
+
if (pkg.license !== 'MIT') t.fail(`package.json: rvlib-* package must carry license MIT`, spec)
|
|
43
|
+
if (pkg.private === true) t.fail(`package.json: rvlib-* package must not be private`, spec)
|
|
44
|
+
} else {
|
|
45
|
+
if (pkg.private !== true) t.fail(`package.json: "private": true is required`, spec)
|
|
46
|
+
if (pkg.license != null) t.fail(`package.json: license field is banned on project repos (rvlib-* sub-packages excepted)`, spec)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// aliases first — an alias satisfies the requirement but earns a WARN,
|
|
50
|
+
// never a double report (alias WARN + missing-canonical FAIL). A banned
|
|
51
|
+
// alias lingering NEXT TO its canonical still WARNs (sweep on sight),
|
|
52
|
+
// it just doesn't satisfy anything.
|
|
53
|
+
const aliased = new Set<string>()
|
|
54
|
+
for (const [name, a] of Object.entries(ALIASES)) {
|
|
55
|
+
const body = scripts[name]
|
|
56
|
+
if (body == null || !a.bodyMatch.test(body)) continue
|
|
57
|
+
if (scripts[a.canonical] == null) aliased.add(a.canonical)
|
|
58
|
+
t.warn(`package.json: script '${name}' is the house '${a.canonical}' — ${scripts[a.canonical] == null ? 'rename it' : `delete it ('${a.canonical}' exists)`}`, spec)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const has = (name: string): boolean => scripts[name] != null || aliased.has(name)
|
|
62
|
+
|
|
63
|
+
// capability → required script names (FAIL); repo.config.ts existence is
|
|
64
|
+
// implied — this probe only runs under `shipkit check`
|
|
65
|
+
const required: Array<{ name: string; why: string }> = [
|
|
66
|
+
{ name: 'ci', why: 'shipkit repo' },
|
|
67
|
+
{ name: 'check', why: 'shipkit repo' },
|
|
68
|
+
{ name: 'contracts', why: 'shipkit repo' },
|
|
69
|
+
{ name: 'tui', why: 'shipkit repo' },
|
|
70
|
+
{ name: 'hub', why: 'shipkit repo' },
|
|
71
|
+
]
|
|
72
|
+
if (existsSync(join(root, '.rv-assets/kit.ts'))) required.push({ name: 'assets', why: '.rv-assets/kit.ts exists' })
|
|
73
|
+
if (existsSync(join(root, '.rv-identity/brand.ts'))) required.push({ name: 'assets:identity', why: '.rv-identity/brand.ts exists' })
|
|
74
|
+
if (existsSync(join(root, '.rv-comfy'))) required.push({ name: 'comfy:up', why: '.rv-comfy/ exists' })
|
|
75
|
+
if (existsSync(join(root, 'deploy.sh'))) required.push({ name: 'up', why: 'deploy.sh exists' })
|
|
76
|
+
if (existsSync(join(root, c.asks?.dir ?? 'asks'))) required.push({ name: 'asks', why: 'ask queue exists' })
|
|
77
|
+
|
|
78
|
+
const missing = required.filter((r) => !has(r.name))
|
|
79
|
+
for (const m of missing) t.fail(`package.json: missing script '${m.name}' (${m.why})`, spec)
|
|
80
|
+
|
|
81
|
+
const missingBaseline = BASELINE.filter((n) => !has(n))
|
|
82
|
+
if (missingBaseline.length > 0)
|
|
83
|
+
t.warn(`package.json: missing baseline script(s): ${missingBaseline.join(', ')}`, spec)
|
|
84
|
+
|
|
85
|
+
if (!t.failed && !t.warned)
|
|
86
|
+
t.ok(`package.json shape ok (private, no license, ${required.length} required scripts present)`)
|
|
87
|
+
}
|
package/src/core/run-check.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { CheckSuite, type CheckOutcome } from './check-suite.ts'
|
|
|
9
9
|
import { discoverChecks } from './discover-checks.ts'
|
|
10
10
|
import { spawnWithTimeout } from './spawn-timeout.ts'
|
|
11
11
|
import { activeContracts, applyContractsResult, contractsConfigFrom, loadRepoConfig } from '../repo-config.ts'
|
|
12
|
+
import { evaluatePackageJson } from './package-json-check.ts'
|
|
12
13
|
import { evaluateContracts } from '../contracts/contracts-engine.ts'
|
|
13
14
|
import { formatLine, parseWireOutput } from './wire.ts'
|
|
14
15
|
import { writeStatus } from './write-status.ts'
|
|
@@ -40,6 +41,12 @@ export async function runCheck(root: string, o: CheckOpts = {}): Promise<number>
|
|
|
40
41
|
applyContractsResult(t, await evaluateContracts(activeContracts(c), contractsConfigFrom(c, root)))
|
|
41
42
|
},
|
|
42
43
|
})
|
|
44
|
+
// built-in shape probe (meta/rv-conventions/package-json.md) — doctor
|
|
45
|
+
// surface only, deliberately absent from the ci gate
|
|
46
|
+
suite.addCheck({
|
|
47
|
+
name: 'package.json',
|
|
48
|
+
logic: (t) => evaluatePackageJson(t, root, c),
|
|
49
|
+
})
|
|
43
50
|
const childEnv: Record<string, string | undefined> = {
|
|
44
51
|
...process.env,
|
|
45
52
|
...(o.fresh === true ? { SHIPKIT_FRESH: '1', RV_CHECK_FRESH: '1' } : {}),
|
package/src/init-repo.ts
CHANGED
|
@@ -40,11 +40,17 @@ export async function initRepo(root: string, opts: InitOpts = {}): Promise<numbe
|
|
|
40
40
|
pkg.scripts ??= {}
|
|
41
41
|
const run = (cmd: string): string => (opts.bin != null ? `bun ${opts.bin} ${cmd}` : `shipkit ${cmd}`)
|
|
42
42
|
let added = 0
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// canonical script NAME → shipkit command (hub runs `shipkit serve`) —
|
|
44
|
+
// vocabulary owned by meta/rv-conventions/package-json.md
|
|
45
|
+
const CANONICAL = { ci: 'ci', check: 'check', contracts: 'contracts', tui: 'tui', hub: 'serve' } as const
|
|
46
|
+
for (const [k, cmd] of Object.entries(CANONICAL)) {
|
|
47
|
+
const v = run(cmd)
|
|
45
48
|
if (pkg.scripts[k] == null) { pkg.scripts[k] = v; added++ }
|
|
46
49
|
else if (pkg.scripts[k] !== v) console.log(`skip: package.json script '${k}' exists — left untouched`)
|
|
47
50
|
}
|
|
51
|
+
// baseline `test` is universal (bun test) — lint/fmt bodies are
|
|
52
|
+
// stack-specific, the scaffold pages own those
|
|
53
|
+
if (pkg.scripts['test'] == null) { pkg.scripts['test'] = 'bun test'; added++ }
|
|
48
54
|
if (added > 0) {
|
|
49
55
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, indent) + '\n')
|
|
50
56
|
console.log(`ok: ${added} package.json script(s) added`)
|