@celilo/cli 0.3.30 → 0.4.0-alpha.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.
Files changed (155) hide show
  1. package/drizzle/0005_module_operations.sql +12 -0
  2. package/drizzle/0006_base_module_aspects.sql +15 -0
  3. package/drizzle/0007_module_systems.sql +17 -0
  4. package/drizzle/meta/_journal.json +21 -0
  5. package/package.json +6 -5
  6. package/schemas/system_config.json +14 -28
  7. package/src/ansible/inventory.test.ts +46 -62
  8. package/src/ansible/inventory.ts +48 -25
  9. package/src/capabilities/registration.ts +25 -7
  10. package/src/capabilities/validation.test.ts +30 -0
  11. package/src/capabilities/validation.ts +8 -0
  12. package/src/cli/backup-rename.test.ts +95 -0
  13. package/src/cli/cli.test.ts +17 -23
  14. package/src/cli/command-registry.ts +199 -0
  15. package/src/cli/commands/backup-list.ts +1 -1
  16. package/src/cli/commands/events.ts +96 -0
  17. package/src/cli/commands/machine-add.ts +103 -59
  18. package/src/cli/commands/module-import.ts +153 -4
  19. package/src/cli/commands/module-remove.ts +86 -17
  20. package/src/cli/commands/module-status.ts +6 -2
  21. package/src/cli/commands/publish/alpha.test.ts +185 -0
  22. package/src/cli/commands/publish/alpha.ts +226 -0
  23. package/src/cli/commands/publish/changesets.test.ts +89 -0
  24. package/src/cli/commands/publish/changesets.ts +144 -0
  25. package/src/cli/commands/publish/consumer-pins.test.ts +155 -0
  26. package/src/cli/commands/publish/consumer-pins.ts +149 -0
  27. package/src/cli/commands/publish/execute.ts +131 -0
  28. package/src/cli/commands/publish/global-install.test.ts +154 -0
  29. package/src/cli/commands/publish/global-install.ts +171 -0
  30. package/src/cli/commands/publish/helpers.ts +227 -0
  31. package/src/cli/commands/publish/index.ts +365 -0
  32. package/src/cli/commands/publish/module-registry.test.ts +40 -0
  33. package/src/cli/commands/publish/module-registry.ts +64 -0
  34. package/src/cli/commands/publish/plan.ts +107 -0
  35. package/src/cli/commands/publish/preflight.ts +238 -0
  36. package/src/cli/commands/publish/types.ts +264 -0
  37. package/src/cli/commands/publish/workspace.test.ts +323 -0
  38. package/src/cli/commands/publish/workspace.ts +596 -0
  39. package/src/cli/commands/restore.ts +126 -0
  40. package/src/cli/commands/storage-add-local.ts +1 -1
  41. package/src/cli/commands/storage-add-s3.ts +1 -1
  42. package/src/cli/commands/subscribers-add.ts +68 -0
  43. package/src/cli/commands/subscribers-list.ts +48 -0
  44. package/src/cli/commands/subscribers-remove.ts +38 -0
  45. package/src/cli/commands/subscribers-serve.ts +77 -0
  46. package/src/cli/commands/subscribers-status.ts +33 -0
  47. package/src/cli/commands/subscribers-test.ts +71 -0
  48. package/src/cli/commands/system-apply-config-equivalence.test.ts +108 -0
  49. package/src/cli/commands/system-apply-config.test.ts +70 -0
  50. package/src/cli/commands/system-apply-config.ts +130 -0
  51. package/src/cli/commands/system-audit.ts +2 -1
  52. package/src/cli/commands/system-init-deprecation.test.ts +90 -0
  53. package/src/cli/commands/system-init.ts +36 -70
  54. package/src/cli/commands/system-update.ts +3 -2
  55. package/src/cli/completion.ts +22 -1
  56. package/src/cli/index.ts +214 -6
  57. package/src/cli/interactive-config.test.ts +19 -0
  58. package/src/cli/restore-command.test.ts +131 -0
  59. package/src/db/client.ts +42 -0
  60. package/src/db/schema.test.ts +13 -16
  61. package/src/db/schema.ts +161 -9
  62. package/src/hooks/capability-loader-firewall.test.ts +6 -15
  63. package/src/hooks/capability-loader.test.ts +2 -3
  64. package/src/hooks/capability-loader.ts +36 -2
  65. package/src/hooks/define-hook.test.ts +4 -0
  66. package/src/hooks/executor.test.ts +18 -0
  67. package/src/hooks/executor.ts +21 -2
  68. package/src/hooks/load-hook-config.test.ts +26 -24
  69. package/src/hooks/load-hook-config.ts +11 -2
  70. package/src/hooks/run-named-hook.ts +16 -0
  71. package/src/hooks/types.ts +9 -1
  72. package/src/manifest/contracts/v1.ts +70 -0
  73. package/src/manifest/schema.ts +262 -16
  74. package/src/manifest/validate-privileged.test.ts +84 -0
  75. package/src/manifest/validate.test.ts +156 -0
  76. package/src/manifest/validate.ts +69 -0
  77. package/src/module/import.ts +12 -0
  78. package/src/services/aspect-approvals.test.ts +231 -0
  79. package/src/services/aspect-approvals.ts +120 -0
  80. package/src/services/aspect-runner.test.ts +493 -0
  81. package/src/services/aspect-runner.ts +438 -0
  82. package/src/services/aspect-template-resolver.test.ts +101 -0
  83. package/src/services/aspect-template-resolver.ts +122 -0
  84. package/src/services/backup-create.ts +104 -25
  85. package/src/services/backup-envelope-roundtrip.test.ts +199 -0
  86. package/src/services/backup-in-flight-refusal.test.ts +163 -0
  87. package/src/services/backup-manifest.test.ts +115 -0
  88. package/src/services/backup-manifest.ts +163 -0
  89. package/src/services/backup-restore.ts +154 -19
  90. package/src/services/build-bus/delivery-events.ts +92 -0
  91. package/src/services/build-bus/event-factory.ts +54 -0
  92. package/src/services/build-bus/fan-out.test.ts +279 -0
  93. package/src/services/build-bus/fan-out.ts +161 -0
  94. package/src/services/build-bus/hook-dispatch-mgmt.test.ts +157 -0
  95. package/src/services/build-bus/hook-dispatch.test.ts +207 -0
  96. package/src/services/build-bus/hook-dispatch.ts +198 -0
  97. package/src/services/build-bus/hook-dispatcher.ts +115 -0
  98. package/src/services/build-bus/index.ts +41 -0
  99. package/src/services/build-bus/receiver-server.test.ts +179 -0
  100. package/src/services/build-bus/receiver-server.ts +159 -0
  101. package/src/services/build-bus/status.test.ts +212 -0
  102. package/src/services/build-bus/status.ts +213 -0
  103. package/src/services/build-bus/subscriber-store.ts +113 -0
  104. package/src/services/celilo-events.test.ts +70 -0
  105. package/src/services/celilo-events.ts +92 -0
  106. package/src/services/celilo-mgmt-hooks.test.ts +296 -0
  107. package/src/services/config-interview.ts +13 -95
  108. package/src/services/cross-module-data-manager.ts +2 -31
  109. package/src/services/cross-module-read.test.ts +250 -0
  110. package/src/services/cross-module-read.ts +232 -0
  111. package/src/services/deploy-validation.ts +7 -0
  112. package/src/services/deployed-systems.test.ts +235 -0
  113. package/src/services/deployed-systems.ts +308 -0
  114. package/src/services/dns-provider-backfill.ts +75 -0
  115. package/src/services/health-runner.ts +19 -3
  116. package/src/services/infrastructure-variable-resolver.test.ts +6 -32
  117. package/src/services/infrastructure-variable-resolver.ts +3 -13
  118. package/src/services/machine-detector.ts +104 -48
  119. package/src/services/machine-pool.ts +145 -2
  120. package/src/services/module-config.ts +78 -120
  121. package/src/services/module-deploy.ts +113 -40
  122. package/src/services/module-operations.test.ts +154 -0
  123. package/src/services/module-operations.ts +154 -0
  124. package/src/services/module-subscriptions.test.ts +58 -0
  125. package/src/services/module-subscriptions.ts +24 -1
  126. package/src/services/module-types-generator.test.ts +3 -3
  127. package/src/services/module-types-generator.ts +7 -2
  128. package/src/services/proxmox-reconcile.test.ts +333 -0
  129. package/src/services/proxmox-reconcile.ts +156 -0
  130. package/src/services/proxmox-state-recovery.ts +3 -24
  131. package/src/services/restore-from-file.test.ts +177 -0
  132. package/src/services/restore-from-file.ts +355 -0
  133. package/src/services/restore-preflight.test.ts +127 -0
  134. package/src/services/restore-preflight.ts +118 -0
  135. package/src/services/storage-providers/s3.ts +10 -2
  136. package/src/services/system-identity.ts +30 -0
  137. package/src/services/system-init.test.ts +64 -21
  138. package/src/services/system-init.ts +28 -26
  139. package/src/templates/generator.test.ts +7 -16
  140. package/src/templates/generator.ts +28 -115
  141. package/src/test-utils/integration.ts +5 -2
  142. package/src/types/infrastructure.ts +8 -0
  143. package/src/variables/computed/computed-integration.test.ts +191 -0
  144. package/src/variables/computed/computed.test.ts +177 -0
  145. package/src/variables/computed/evaluate.ts +271 -0
  146. package/src/variables/computed/marker.ts +53 -0
  147. package/src/variables/computed/parse.ts +262 -0
  148. package/src/variables/computed/provider-lookup.ts +130 -0
  149. package/src/variables/context.test.ts +89 -28
  150. package/src/variables/context.ts +196 -191
  151. package/src/variables/parser.ts +3 -3
  152. package/src/variables/resolver.test.ts +61 -0
  153. package/src/variables/resolver.ts +81 -0
  154. package/src/variables/types.ts +23 -1
  155. package/src/services/dns-auto-register.ts +0 -211
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Preflight checks for `celilo publish` — dirty-tree, workspace
3
+ * stale-version, module stale-manifest. Builds a single
4
+ * `PreflightReport` so the planner can decide which gates apply for
5
+ * the current mode (alpha skips workspace + module stale; promote
6
+ * skips workspace stale; normal enforces all three).
7
+ *
8
+ * Also owns `--release-touch`: the auto-fix path for the
9
+ * release-only-drift case where source changed but manifest.yml
10
+ * didn't. Touch + commit + continue is automated when the publisher
11
+ * just hits Enter through it; otherwise --release-touch by itself
12
+ * touches and exits.
13
+ */
14
+
15
+ import { execSync, spawnSync } from 'node:child_process';
16
+ import { readFileSync, writeFileSync } from 'node:fs';
17
+ import { join, relative } from 'node:path';
18
+ import { checkModuleStale } from '../../../services/module-validator/git-hygiene';
19
+ import {
20
+ PACKAGES,
21
+ REPO_ROOT,
22
+ isAncestor,
23
+ isPublished,
24
+ lastCommitTouching,
25
+ listModuleDirs,
26
+ readPkg,
27
+ } from './helpers';
28
+ import type { ModuleStaleIssue, PreflightReport, StalenessIssue } from './types';
29
+
30
+ /**
31
+ * Detect "I bumped source but forgot to bump the version." If a package's
32
+ * current version is already on npm AND there are commits touching the
33
+ * package's source after the last commit that touched its package.json,
34
+ * the publish would silently skip — which is exactly how a feature add
35
+ * never makes it to npm.
36
+ *
37
+ * False positives: a commit that bumps a dependency in package.json
38
+ * (without changing the "version" field) resets staleness even though the
39
+ * version is still stale relative to the last bump. Acceptable for v1 —
40
+ * the common case (forget-to-bump after a feature commit) is caught.
41
+ */
42
+ export function checkStaleVersion(pkg: string): StalenessIssue | null {
43
+ const { name, version } = readPkg(pkg);
44
+ if (!name || !version) return null;
45
+ if (!isPublished(name, version)) return null;
46
+ const lastPkgJson = lastCommitTouching([`${pkg}/package.json`]);
47
+ const lastSrc = lastCommitTouching([
48
+ pkg,
49
+ `:(exclude)${pkg}/package.json`,
50
+ `:(exclude)${pkg}/node_modules`,
51
+ ]);
52
+ if (!lastPkgJson || !lastSrc) return null;
53
+ if (lastSrc === lastPkgJson) return null;
54
+ if (!isAncestor(lastPkgJson, lastSrc)) return null;
55
+ return {
56
+ name,
57
+ pkg,
58
+ version,
59
+ lastSrcCommit: lastSrc,
60
+ lastPkgJsonCommit: lastPkgJson,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Run every precondition that can fail a publish, accumulating all
66
+ * issues into a single report. Used both by --dry-run (report-only) and
67
+ * by the normal publish flow (where it's the gate before any side
68
+ * effects run).
69
+ *
70
+ * Replaces the old "abort-on-first-failure per phase" pattern that made
71
+ * fresh releases require N round-trips when N modules drifted.
72
+ */
73
+ export function runPreflight(): PreflightReport {
74
+ const dirtyOutput = execSync('git status --porcelain', {
75
+ encoding: 'utf-8',
76
+ cwd: REPO_ROOT,
77
+ }).trim();
78
+
79
+ const workspaceStale: StalenessIssue[] = [];
80
+ for (const pkg of PACKAGES) {
81
+ const issue = checkStaleVersion(pkg);
82
+ if (issue) workspaceStale.push(issue);
83
+ }
84
+
85
+ const moduleStale: ModuleStaleIssue[] = [];
86
+ for (const dir of listModuleDirs()) {
87
+ const issue = checkModuleStale(dir);
88
+ if (issue) {
89
+ moduleStale.push({
90
+ moduleDir: issue.moduleDir,
91
+ lastSrcCommit: issue.lastSrcCommit,
92
+ lastManifestCommit: issue.lastManifestCommit,
93
+ });
94
+ }
95
+ }
96
+
97
+ return {
98
+ dirty: dirtyOutput.length > 0,
99
+ dirtyOutput,
100
+ workspaceStale,
101
+ moduleStale,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Print a human-readable preflight report. Returns true if any issue is
107
+ * blocking (i.e. publish would fail without --allow-stale).
108
+ */
109
+ export function printPreflightReport(report: PreflightReport): boolean {
110
+ let anyIssue = false;
111
+
112
+ if (report.dirty) {
113
+ anyIssue = true;
114
+ console.error('\n✗ Working tree has uncommitted changes:\n');
115
+ console.error(execSync('git status --short', { encoding: 'utf-8', cwd: REPO_ROOT }));
116
+ console.error(' fix: commit or stash, then re-run.');
117
+ }
118
+
119
+ if (report.workspaceStale.length > 0) {
120
+ anyIssue = true;
121
+ console.error('\n✗ Stale-version drift on workspace packages:\n');
122
+ for (const i of report.workspaceStale) {
123
+ console.error(` ${i.name}@${i.version} is on npm, but commits touching ${i.pkg} landed`);
124
+ console.error(` after the last ${i.pkg}/package.json change.`);
125
+ console.error(` src commit: ${i.lastSrcCommit.slice(0, 12)}`);
126
+ console.error(` package.json commit: ${i.lastPkgJsonCommit.slice(0, 12)}`);
127
+ console.error(` fix: bump ${i.pkg}/package.json#version, commit, re-run.`);
128
+ console.error();
129
+ }
130
+ }
131
+
132
+ if (report.moduleStale.length > 0) {
133
+ anyIssue = true;
134
+ console.error('\n✗ Stale-manifest drift on celilo modules:\n');
135
+ for (const i of report.moduleStale) {
136
+ const rel = relative(REPO_ROOT, i.moduleDir);
137
+ console.error(` ${rel}: src committed after manifest.yml`);
138
+ console.error(` src commit: ${i.lastSrcCommit.slice(0, 12)}`);
139
+ console.error(` manifest.yml commit: ${i.lastManifestCommit.slice(0, 12)}`);
140
+ }
141
+ console.error(
142
+ ' fix: bump manifest.yml#version (semver change), or run\n' +
143
+ ' `bun run publish --release-touch` to auto-touch all drifted\n' +
144
+ " manifests with today's date (release-only — auto-revision\n" +
145
+ ' picks the next +N).',
146
+ );
147
+ console.error();
148
+ }
149
+
150
+ if (!anyIssue) {
151
+ console.log('✓ Preflight clean: no dirty tree, no stale workspace versions, no stale modules.');
152
+ } else {
153
+ console.error('Pass --allow-stale to publish anyway (the prior versions stay on npm).');
154
+ }
155
+
156
+ return anyIssue;
157
+ }
158
+
159
+ /**
160
+ * Append (or refresh) a `# released: <ISO timestamp>` trailing comment on
161
+ * the given manifest.yml. Always produces a fresh diff (timestamps are
162
+ * unique per second), so the resulting commit always advances past any
163
+ * source commit on the same day.
164
+ */
165
+ export function touchManifestRelease(manifestPath: string): void {
166
+ const now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
167
+ const original = readFileSync(manifestPath, 'utf-8');
168
+ const lines = original.split('\n');
169
+ const cleaned: string[] = [];
170
+ for (const line of lines) {
171
+ if (line.match(/^#\s*released:/)) continue;
172
+ cleaned.push(line);
173
+ }
174
+ while (cleaned.length > 0 && cleaned[cleaned.length - 1].trim() === '') {
175
+ cleaned.pop();
176
+ }
177
+ cleaned.push(`# released: ${now}`);
178
+ cleaned.push('');
179
+ writeFileSync(manifestPath, cleaned.join('\n'));
180
+ }
181
+
182
+ /**
183
+ * Apply --release-touch: rewrite every drifted manifest.yml with today's
184
+ * release marker. Doesn't commit — operator reviews `git diff` and
185
+ * commits explicitly so the touch lands as a documented release event,
186
+ * not as a silent script side-effect.
187
+ */
188
+ export function applyReleaseTouch(issues: ModuleStaleIssue[]): void {
189
+ if (issues.length === 0) {
190
+ console.log('No module drift to touch — nothing to do.');
191
+ return;
192
+ }
193
+ console.log(`\nTouching ${issues.length} drifted manifest(s):`);
194
+ for (const i of issues) {
195
+ const manifestPath = join(i.moduleDir, 'manifest.yml');
196
+ touchManifestRelease(manifestPath);
197
+ console.log(` ✎ ${relative(REPO_ROOT, manifestPath)}`);
198
+ }
199
+ console.log('\nReview with `git diff modules/`, commit, then re-run `bun run publish`.');
200
+ }
201
+
202
+ /**
203
+ * Auto-cascade for the common case: only modules drifted. Touches +
204
+ * commits + returns true if the operator approved continuing. Returns
205
+ * false if they aborted (caller should exit). Throws on git failure.
206
+ *
207
+ * Confirmation is gated by `autoYes` — when -y is set, the cascade
208
+ * runs without prompting. When false, the caller already prompted.
209
+ */
210
+ export function autoTouchAndCommit(
211
+ report: PreflightReport,
212
+ spawnSyncFn: typeof spawnSync = spawnSync,
213
+ ): { ok: boolean; recheck: PreflightReport | null } {
214
+ applyReleaseTouch(report.moduleStale);
215
+ const manifestPaths = report.moduleStale.map((i) => join(i.moduleDir, 'manifest.yml'));
216
+ const stageResult = spawnSyncFn('git', ['add', '--', ...manifestPaths], {
217
+ cwd: REPO_ROOT,
218
+ stdio: 'inherit',
219
+ });
220
+ if (stageResult.status !== 0) {
221
+ console.error('git add failed; resolve manually and re-run.');
222
+ return { ok: false, recheck: null };
223
+ }
224
+ const commitMessage = `modules: release-touch drifted manifests (${report.moduleStale
225
+ .map((i) => relative(REPO_ROOT, i.moduleDir).replace('modules/', ''))
226
+ .join(', ')})`;
227
+ const commitResult = spawnSyncFn('git', ['commit', '-m', commitMessage], {
228
+ cwd: REPO_ROOT,
229
+ stdio: 'inherit',
230
+ });
231
+ if (commitResult.status !== 0) {
232
+ console.error('git commit failed; resolve manually and re-run.');
233
+ return { ok: false, recheck: null };
234
+ }
235
+
236
+ const recheck = runPreflight();
237
+ return { ok: true, recheck };
238
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Type definitions for `celilo publish` — the planner/executor split per
3
+ * v2/PUBLILO_CLI.md Phase 2.
4
+ *
5
+ * The umbrella `PublishPlan` enumerates everything a publish run intends
6
+ * to do. It's produced by the pure planner (`plan.ts`) and consumed by
7
+ * the executor (`execute.ts`). The planner takes its inputs from
8
+ * discovery + flag parsing; the executor only mutates state described in
9
+ * the plan. Dry-run is "build plan, print it, exit"; a real publish is
10
+ * "build plan, confirm, execute."
11
+ */
12
+
13
+ /**
14
+ * Subset of the npm package.json shape we touch. Kept narrow so the
15
+ * planner can't accidentally rely on fields not part of the contract.
16
+ */
17
+ export interface PackageJson {
18
+ name?: string;
19
+ version?: string;
20
+ dependencies?: Record<string, string>;
21
+ devDependencies?: Record<string, string>;
22
+ peerDependencies?: Record<string, string>;
23
+ optionalDependencies?: Record<string, string>;
24
+ }
25
+
26
+ /**
27
+ * Single workspace package → npm-version mapping. Built once at the top
28
+ * of a publish run from each package's package.json#version. In alpha
29
+ * mode it gets tightened to the alpha versions before being threaded
30
+ * through to the workspace-dep rewriter, so cross-package alpha pins
31
+ * resolve to the alpha line.
32
+ */
33
+ export type WorkspaceVersionMap = Map<string, string>;
34
+
35
+ /**
36
+ * What kind of publish run is this? Most per-package decisions
37
+ * (preflight gating, version rewrite, bun publish flags, post-publish
38
+ * verification expectations) flow from this single discriminator.
39
+ *
40
+ * - `normal`: bump real semver, publish to `@latest`, run Phase 2/3/4.
41
+ * - `alpha`: publish `X.Y.Z-alpha.N` to the `@alpha` dist-tag. Skip
42
+ * Phase 2 (consumer pins opt in via `@alpha` manually).
43
+ * Phase 3 only runs when `trackAlpha`. Phase 4 only when
44
+ * `alphaModules`.
45
+ * - `promote`: graduate a specific alpha to its base real version (e.g.
46
+ * `@celilo/e2e@0.7.14-alpha.3` → `@celilo/e2e@0.7.14`).
47
+ * Single package; Phase 2/3/4 run normally — this IS a
48
+ * real release.
49
+ */
50
+ export type PublishMode =
51
+ | { kind: 'normal' }
52
+ | { kind: 'alpha'; trackAlpha: boolean; alphaModules: boolean }
53
+ | { kind: 'promote'; target: { name: string; version: string } };
54
+
55
+ /**
56
+ * Parsed top-level flag bundle. Produced by `parseOptions` and threaded
57
+ * into the planner. Doesn't include flag-validation errors — those bail
58
+ * out before reaching here.
59
+ */
60
+ export interface PublishOptions {
61
+ allowStale: boolean;
62
+ autoYes: boolean;
63
+ mode: PublishMode;
64
+ }
65
+
66
+ /**
67
+ * One workspace package source committing past its package.json version.
68
+ * Surfaced by `checkStaleVersion` during preflight; blocks a normal
69
+ * publish unless --allow-stale.
70
+ */
71
+ export interface StalenessIssue {
72
+ name: string;
73
+ pkg: string;
74
+ version: string;
75
+ lastSrcCommit: string;
76
+ lastPkgJsonCommit: string;
77
+ }
78
+
79
+ /**
80
+ * Module source committed past its manifest.yml. Surfaced by
81
+ * `checkModuleStale` during preflight; blocks a normal publish unless
82
+ * --allow-stale or `bun run publish --release-touch`.
83
+ */
84
+ export interface ModuleStaleIssue {
85
+ moduleDir: string;
86
+ lastSrcCommit: string;
87
+ lastManifestCommit: string;
88
+ }
89
+
90
+ /**
91
+ * Snapshot of everything preflight learned about the working tree.
92
+ * Built once at the top of the run and threaded into the planner.
93
+ */
94
+ export interface PreflightReport {
95
+ dirty: boolean;
96
+ dirtyOutput: string;
97
+ workspaceStale: StalenessIssue[];
98
+ moduleStale: ModuleStaleIssue[];
99
+ }
100
+
101
+ /**
102
+ * Single workspace:^ → explicit-pin rewrite recorded by
103
+ * `rewriteWorkspaceDeps`. Used by the executor for both the actual
104
+ * package.json mutation and the post-publish verification step.
105
+ */
106
+ export interface WorkspaceRewrite {
107
+ depName: string;
108
+ bucket: string;
109
+ oldSpec: string;
110
+ newSpec: string;
111
+ }
112
+
113
+ /**
114
+ * Optional knobs for the package.json rewriter. Lifecycle: the planner
115
+ * decides which to set per package (per-mode), the executor applies them
116
+ * to the live file just before `bun publish` and restores from the
117
+ * captured `original` after.
118
+ */
119
+ export interface RewriteOptions {
120
+ /**
121
+ * Force the package's `version` field to this value before publish.
122
+ * Used by --alpha (sets `X.Y.Z-alpha.N`) and --promote (forces the
123
+ * base `X.Y.Z` even if source disagrees).
124
+ */
125
+ targetVersion?: string;
126
+ /**
127
+ * Pin workspace deps to exact versions instead of `^`/`~`. Required for
128
+ * --alpha mode: pre-release semver excludes pre-release versions from
129
+ * caret ranges, so the only way to keep alpha consumers resolvable is
130
+ * to pin to the exact alpha that was just published.
131
+ */
132
+ exactPins?: boolean;
133
+ /**
134
+ * Stamp a `gitHead` field on the published package.json so future
135
+ * `--alpha` runs can detect "no source change since prior alpha" and
136
+ * skip. bun publish doesn't populate gitHead automatically.
137
+ */
138
+ gitHead?: string;
139
+ }
140
+
141
+ /**
142
+ * Per-package pre-publish work. Currently only `@celilo/e2e` triggers
143
+ * any of these (it ships netapps + registry server + caches inside its
144
+ * tarball), but the plan still enumerates them so dry-run can show
145
+ * what extra work the publish entails.
146
+ */
147
+ export type PrePublishHookKind =
148
+ | 'registryServerBundle'
149
+ | 'rebuildE2eNetapps'
150
+ | 'stageE2ePublishCaches';
151
+
152
+ /**
153
+ * Single workspace package planned to publish (or explicitly skip) in
154
+ * this run. The plan is built before any side effects so the dry-run
155
+ * can faithfully describe what `bun publish` would do.
156
+ */
157
+ export interface WorkspaceItem {
158
+ /** Workspace path relative to REPO_ROOT (e.g. 'packages/e2e'). */
159
+ pkg: string;
160
+ /** npm package name (e.g. '@celilo/e2e'). */
161
+ name: string;
162
+ /** Version currently in this package's package.json. */
163
+ baseVersion: string;
164
+ /**
165
+ * What this run will publish. Same as `baseVersion` for normal mode,
166
+ * `X.Y.Z-alpha.N` for alpha mode, base-stripped for promote mode.
167
+ */
168
+ versionToPublish: string;
169
+ /** npm dist-tag override (only `'alpha'` for alpha mode; undefined → `@latest`). */
170
+ tag?: 'alpha';
171
+ /**
172
+ * Rewrite recipe applied to the package's package.json immediately
173
+ * before `bun publish`. Restored from `originalPackageJson` after.
174
+ */
175
+ rewriteOptions: RewriteOptions;
176
+ /** Pre-publish work to do (netapp rebuild, cache staging, etc.). */
177
+ hooks: PrePublishHookKind[];
178
+ /**
179
+ * If set, this package is explicitly NOT publishing this run (already
180
+ * on npm, source unchanged since prior alpha, etc.). The reason
181
+ * appears in the dry-run plan and the post-run summary.
182
+ */
183
+ skipReason?: string;
184
+ }
185
+
186
+ /**
187
+ * Pin update planned for a single consumer package.json. Generated by
188
+ * walking the in-repo modules/, apps/, packages/, and any
189
+ * EXTERNAL_PROJECT_PATHS, matching `@celilo/*` deps to current npm
190
+ * latests. Produced in `--normal` and `--promote` modes; skipped in
191
+ * `--alpha` (consumers opt into the @alpha tag manually).
192
+ */
193
+ export interface ConsumerPinItem {
194
+ /** Absolute path to the package.json. */
195
+ filePath: string;
196
+ /** What this run will rewrite in that file. */
197
+ updates: Array<{
198
+ bucket: 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
199
+ depName: string;
200
+ oldSpec: string;
201
+ newSpec: string;
202
+ }>;
203
+ }
204
+
205
+ /**
206
+ * Single managed package planned to be force-pinned (or update-pulled)
207
+ * in bun's global install. `target` is the version we expect after the
208
+ * operation; `installed` is what's there now.
209
+ */
210
+ export interface GlobalUpdateItem {
211
+ name: string;
212
+ installed: string | null;
213
+ target: string;
214
+ /**
215
+ * If true, the executor will run `bun add -g <name>@<target>` — force-
216
+ * pin to an exact version. Used for packages we just published this
217
+ * run (so the global ends up on the precise version, not whatever
218
+ * @latest resolves to a moment later).
219
+ *
220
+ * If false, the executor runs `bun update -g <name>` — chase whatever
221
+ * `@latest` is now. Used for managed packages that we didn't publish
222
+ * this run but might still be drifting in the global install.
223
+ */
224
+ forcePin: boolean;
225
+ }
226
+
227
+ /**
228
+ * Single module planned to publish to the celilo registry. Phase 4 is
229
+ * the slimmest phase: revision selection and tarball construction live
230
+ * inside `celilo module publish` itself, so the plan only needs to
231
+ * enumerate the directories.
232
+ */
233
+ export interface ModuleItem {
234
+ moduleDir: string;
235
+ }
236
+
237
+ /**
238
+ * Umbrella plan — the central data structure of Phase 2 of the publish
239
+ * CLI refactor (v2/PUBLILO_CLI.md). One plan covers all four phases of
240
+ * a publish run; the executor consumes it top-to-bottom.
241
+ */
242
+ export interface PublishPlan {
243
+ mode: PublishMode;
244
+ options: PublishOptions;
245
+ preflight: PreflightReport;
246
+ /** Workspace npm publishes — Phase 1 of the publish flow. */
247
+ workspace: WorkspaceItem[];
248
+ /** Consumer package.json pin bumps — Phase 2. */
249
+ consumerPins: ConsumerPinItem[];
250
+ /** bun global install force-pins / refreshes — Phase 3. */
251
+ globalUpdate: GlobalUpdateItem[];
252
+ /** Modules to publish to the celilo registry — Phase 4. */
253
+ modulePublish: ModuleItem[];
254
+ }
255
+
256
+ /**
257
+ * Aggregated outcome of a publish run. Mirrors the prior
258
+ * `runPublishPhase` return shape so the post-run summary can keep
259
+ * reporting the same way (published list + skipped list).
260
+ */
261
+ export interface PublishResult {
262
+ published: Array<{ name: string; version: string }>;
263
+ skipped: string[];
264
+ }