|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +import { describe, it, expect } from 'vitest'; |
| 4 | + |
| 5 | +import { |
| 6 | + EXPORT_ENTRY_POINTS, |
| 7 | + exportNamesOf, |
| 8 | + holdersOf, |
| 9 | +} from '../../scripts/lib/export-origins-testkit'; |
| 10 | + |
| 11 | +// ─── [#12007] kernel/ CLICommandContribution is RETIRED ───────────────────── |
| 12 | +// |
| 13 | +// ADR-0049 enforce-or-remove. `kernel/cli-extension.zod.ts` no longer declares |
| 14 | +// `CLICommandContributionSchema` / `CLICommandContribution` — 1 emitted def, |
| 15 | +// 2 exported names, 3 authorable-surface keys (`name`, `description`, |
| 16 | +// `module`), the reference page's section with them. |
| 17 | +// |
| 18 | +// The measurement that decided it (re-verified at this retirement's base |
| 19 | +// commit, 146f448a5, with positive controls — the full record lives in the |
| 20 | +// retirement block inside the zod module): |
| 21 | +// |
| 22 | +// 1. CARRIER — after #10724, `manifest.contributes.commands` is a |
| 23 | +// `retiredKey()` tombstone: no manifest surface could legally carry a |
| 24 | +// command-contribution entry, so the exported schema advertised a shape |
| 25 | +// whose only declared carrier rejects it. The manifest never referenced |
| 26 | +// this schema even before the tombstone — its inline `commands` item |
| 27 | +// schema was an independent duplicate. |
| 28 | +// 2. STATIC — zero readers outside `packages/spec`'s own test and generated |
| 29 | +// artifacts, in objectstack, objectui (at the pinned sha) and cloud |
| 30 | +// (controls: `OclifPluginConfigSchema` and `@objectstack/spec` both |
| 31 | +// resolve hits — the scans see real readers). |
| 32 | +// 3. DOORS — no metadata-type binding, no stack collection, no manifest |
| 33 | +// embed: no authored document could ever carry it. |
| 34 | +// |
| 35 | +// Route 3: with no carrier key there is nothing to tombstone and no seam for |
| 36 | +// a D2 conversion — `RETIRED_DEFS_BY_MAJOR[18]` plus the D3 semantic entry |
| 37 | +// `cli-command-contribution-retired` ARE the declaration. |
| 38 | +// |
| 39 | +// Form follows #11825 / #8715 / #4988: resolved symbol identity over every |
| 40 | +// public entry via the build-time `export-origins/` artifact. |
| 41 | +describe('[#12007] kernel/ CLICommandContribution retirement', () => { |
| 42 | + /** The 2 names the retired def exported (1 schema const + 1 type). */ |
| 43 | + const RETIRED_NAMES = [ |
| 44 | + 'CLICommandContributionSchema', |
| 45 | + 'CLICommandContribution', |
| 46 | + ] as const; |
| 47 | + |
| 48 | + /** |
| 49 | + * Names that must SURVIVE on `./kernel`: the LIVE half of the same module — |
| 50 | + * `OclifPluginConfigSchema` describes the `oclif` section of a plugin's own |
| 51 | + * `package.json`, the mechanism that actually registers CLI commands. |
| 52 | + * Exactly what a too-wide "tidy the cli-extension module" sweep would take |
| 53 | + * (full-file deletion was explicitly NOT the shape: the module docblock's |
| 54 | + * Commander.js migration prose is cited by the `contributes.commands` |
| 55 | + * tombstone). |
| 56 | + */ |
| 57 | + const MUST_SURVIVE_KERNEL = [ |
| 58 | + 'OclifPluginConfigSchema', |
| 59 | + 'OclifPluginConfig', |
| 60 | + ] as const; |
| 61 | + |
| 62 | + it('every retired name has ZERO holders on any public entry; the survivors still stand', () => { |
| 63 | + // Anti-vacuity: the baseline must cover the real surface. |
| 64 | + for (const needed of ['.', './kernel']) { |
| 65 | + expect(EXPORT_ENTRY_POINTS, `exports map must include ${needed}`).toContain(needed); |
| 66 | + } |
| 67 | + expect( |
| 68 | + exportNamesOf('./kernel').length, |
| 69 | + './kernel must export a non-trivial surface' |
| 70 | + ).toBeGreaterThan(50); |
| 71 | + |
| 72 | + // ── ABSENCE (every entry, not just ./kernel) ────────────────────────── |
| 73 | + for (const name of RETIRED_NAMES) { |
| 74 | + expect(holdersOf(name), `${name} must have zero holders after #12007`).toEqual([]); |
| 75 | + } |
| 76 | + |
| 77 | + // ── SURVIVAL ────────────────────────────────────────────────────────── |
| 78 | + const kernelNames = exportNamesOf('./kernel'); |
| 79 | + for (const name of MUST_SURVIVE_KERNEL) { |
| 80 | + expect(kernelNames, `${name} must SURVIVE this retirement`).toContain(name); |
| 81 | + } |
| 82 | + }); |
| 83 | + |
| 84 | + it('the runtime barrel resolves without the retired schema and keeps the survivor', async () => { |
| 85 | + const kernel = await import('./index'); |
| 86 | + expect(kernel, 'CLICommandContributionSchema must not be exported after #12007') |
| 87 | + .not.toHaveProperty('CLICommandContributionSchema'); |
| 88 | + // Anti-vacuity: the barrel really resolved and still exports the live |
| 89 | + // oclif surface plus an unrelated kernel anchor. |
| 90 | + expect(kernel).toHaveProperty('OclifPluginConfigSchema'); |
| 91 | + expect(kernel).toHaveProperty('ManifestSchema'); |
| 92 | + }); |
| 93 | +}); |
0 commit comments