@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.
- package/drizzle/0005_module_operations.sql +12 -0
- package/drizzle/0006_base_module_aspects.sql +15 -0
- package/drizzle/0007_module_systems.sql +17 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +6 -5
- package/schemas/system_config.json +14 -28
- package/src/ansible/inventory.test.ts +46 -62
- package/src/ansible/inventory.ts +48 -25
- package/src/capabilities/registration.ts +25 -7
- package/src/capabilities/validation.test.ts +30 -0
- package/src/capabilities/validation.ts +8 -0
- package/src/cli/backup-rename.test.ts +95 -0
- package/src/cli/cli.test.ts +17 -23
- package/src/cli/command-registry.ts +199 -0
- package/src/cli/commands/backup-list.ts +1 -1
- package/src/cli/commands/events.ts +96 -0
- package/src/cli/commands/machine-add.ts +103 -59
- package/src/cli/commands/module-import.ts +153 -4
- package/src/cli/commands/module-remove.ts +86 -17
- package/src/cli/commands/module-status.ts +6 -2
- package/src/cli/commands/publish/alpha.test.ts +185 -0
- package/src/cli/commands/publish/alpha.ts +226 -0
- package/src/cli/commands/publish/changesets.test.ts +89 -0
- package/src/cli/commands/publish/changesets.ts +144 -0
- package/src/cli/commands/publish/consumer-pins.test.ts +155 -0
- package/src/cli/commands/publish/consumer-pins.ts +149 -0
- package/src/cli/commands/publish/execute.ts +131 -0
- package/src/cli/commands/publish/global-install.test.ts +154 -0
- package/src/cli/commands/publish/global-install.ts +171 -0
- package/src/cli/commands/publish/helpers.ts +227 -0
- package/src/cli/commands/publish/index.ts +365 -0
- package/src/cli/commands/publish/module-registry.test.ts +40 -0
- package/src/cli/commands/publish/module-registry.ts +64 -0
- package/src/cli/commands/publish/plan.ts +107 -0
- package/src/cli/commands/publish/preflight.ts +238 -0
- package/src/cli/commands/publish/types.ts +264 -0
- package/src/cli/commands/publish/workspace.test.ts +323 -0
- package/src/cli/commands/publish/workspace.ts +596 -0
- package/src/cli/commands/restore.ts +126 -0
- package/src/cli/commands/storage-add-local.ts +1 -1
- package/src/cli/commands/storage-add-s3.ts +1 -1
- package/src/cli/commands/subscribers-add.ts +68 -0
- package/src/cli/commands/subscribers-list.ts +48 -0
- package/src/cli/commands/subscribers-remove.ts +38 -0
- package/src/cli/commands/subscribers-serve.ts +77 -0
- package/src/cli/commands/subscribers-status.ts +33 -0
- package/src/cli/commands/subscribers-test.ts +71 -0
- package/src/cli/commands/system-apply-config-equivalence.test.ts +108 -0
- package/src/cli/commands/system-apply-config.test.ts +70 -0
- package/src/cli/commands/system-apply-config.ts +130 -0
- package/src/cli/commands/system-audit.ts +2 -1
- package/src/cli/commands/system-init-deprecation.test.ts +90 -0
- package/src/cli/commands/system-init.ts +36 -70
- package/src/cli/commands/system-update.ts +3 -2
- package/src/cli/completion.ts +22 -1
- package/src/cli/index.ts +214 -6
- package/src/cli/interactive-config.test.ts +19 -0
- package/src/cli/restore-command.test.ts +131 -0
- package/src/db/client.ts +42 -0
- package/src/db/schema.test.ts +13 -16
- package/src/db/schema.ts +161 -9
- package/src/hooks/capability-loader-firewall.test.ts +6 -15
- package/src/hooks/capability-loader.test.ts +2 -3
- package/src/hooks/capability-loader.ts +36 -2
- package/src/hooks/define-hook.test.ts +4 -0
- package/src/hooks/executor.test.ts +18 -0
- package/src/hooks/executor.ts +21 -2
- package/src/hooks/load-hook-config.test.ts +26 -24
- package/src/hooks/load-hook-config.ts +11 -2
- package/src/hooks/run-named-hook.ts +16 -0
- package/src/hooks/types.ts +9 -1
- package/src/manifest/contracts/v1.ts +70 -0
- package/src/manifest/schema.ts +262 -16
- package/src/manifest/validate-privileged.test.ts +84 -0
- package/src/manifest/validate.test.ts +156 -0
- package/src/manifest/validate.ts +69 -0
- package/src/module/import.ts +12 -0
- package/src/services/aspect-approvals.test.ts +231 -0
- package/src/services/aspect-approvals.ts +120 -0
- package/src/services/aspect-runner.test.ts +493 -0
- package/src/services/aspect-runner.ts +438 -0
- package/src/services/aspect-template-resolver.test.ts +101 -0
- package/src/services/aspect-template-resolver.ts +122 -0
- package/src/services/backup-create.ts +104 -25
- package/src/services/backup-envelope-roundtrip.test.ts +199 -0
- package/src/services/backup-in-flight-refusal.test.ts +163 -0
- package/src/services/backup-manifest.test.ts +115 -0
- package/src/services/backup-manifest.ts +163 -0
- package/src/services/backup-restore.ts +154 -19
- package/src/services/build-bus/delivery-events.ts +92 -0
- package/src/services/build-bus/event-factory.ts +54 -0
- package/src/services/build-bus/fan-out.test.ts +279 -0
- package/src/services/build-bus/fan-out.ts +161 -0
- package/src/services/build-bus/hook-dispatch-mgmt.test.ts +157 -0
- package/src/services/build-bus/hook-dispatch.test.ts +207 -0
- package/src/services/build-bus/hook-dispatch.ts +198 -0
- package/src/services/build-bus/hook-dispatcher.ts +115 -0
- package/src/services/build-bus/index.ts +41 -0
- package/src/services/build-bus/receiver-server.test.ts +179 -0
- package/src/services/build-bus/receiver-server.ts +159 -0
- package/src/services/build-bus/status.test.ts +212 -0
- package/src/services/build-bus/status.ts +213 -0
- package/src/services/build-bus/subscriber-store.ts +113 -0
- package/src/services/celilo-events.test.ts +70 -0
- package/src/services/celilo-events.ts +92 -0
- package/src/services/celilo-mgmt-hooks.test.ts +296 -0
- package/src/services/config-interview.ts +13 -95
- package/src/services/cross-module-data-manager.ts +2 -31
- package/src/services/cross-module-read.test.ts +250 -0
- package/src/services/cross-module-read.ts +232 -0
- package/src/services/deploy-validation.ts +7 -0
- package/src/services/deployed-systems.test.ts +235 -0
- package/src/services/deployed-systems.ts +308 -0
- package/src/services/dns-provider-backfill.ts +75 -0
- package/src/services/health-runner.ts +19 -3
- package/src/services/infrastructure-variable-resolver.test.ts +6 -32
- package/src/services/infrastructure-variable-resolver.ts +3 -13
- package/src/services/machine-detector.ts +104 -48
- package/src/services/machine-pool.ts +145 -2
- package/src/services/module-config.ts +78 -120
- package/src/services/module-deploy.ts +113 -40
- package/src/services/module-operations.test.ts +154 -0
- package/src/services/module-operations.ts +154 -0
- package/src/services/module-subscriptions.test.ts +58 -0
- package/src/services/module-subscriptions.ts +24 -1
- package/src/services/module-types-generator.test.ts +3 -3
- package/src/services/module-types-generator.ts +7 -2
- package/src/services/proxmox-reconcile.test.ts +333 -0
- package/src/services/proxmox-reconcile.ts +156 -0
- package/src/services/proxmox-state-recovery.ts +3 -24
- package/src/services/restore-from-file.test.ts +177 -0
- package/src/services/restore-from-file.ts +355 -0
- package/src/services/restore-preflight.test.ts +127 -0
- package/src/services/restore-preflight.ts +118 -0
- package/src/services/storage-providers/s3.ts +10 -2
- package/src/services/system-identity.ts +30 -0
- package/src/services/system-init.test.ts +64 -21
- package/src/services/system-init.ts +28 -26
- package/src/templates/generator.test.ts +7 -16
- package/src/templates/generator.ts +28 -115
- package/src/test-utils/integration.ts +5 -2
- package/src/types/infrastructure.ts +8 -0
- package/src/variables/computed/computed-integration.test.ts +191 -0
- package/src/variables/computed/computed.test.ts +177 -0
- package/src/variables/computed/evaluate.ts +271 -0
- package/src/variables/computed/marker.ts +53 -0
- package/src/variables/computed/parse.ts +262 -0
- package/src/variables/computed/provider-lookup.ts +130 -0
- package/src/variables/context.test.ts +89 -28
- package/src/variables/context.ts +196 -191
- package/src/variables/parser.ts +3 -3
- package/src/variables/resolver.test.ts +61 -0
- package/src/variables/resolver.ts +81 -0
- package/src/variables/types.ts +23 -1
- package/src/services/dns-auto-register.ts +0 -211
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bun global install refresh — Phase 3 of `celilo publish`.
|
|
3
|
+
*
|
|
4
|
+
* Force-pins just-published packages into `~/.bun/install/global` so
|
|
5
|
+
* the operator's `cele2e` / `celilo` binaries end up on the version
|
|
6
|
+
* this run just shipped (rather than whatever `@latest` resolves to a
|
|
7
|
+
* moment later). For managed packages NOT published this run, runs
|
|
8
|
+
* `bun update -g <name>` to chase npm-latest (catches drift between
|
|
9
|
+
* publish events).
|
|
10
|
+
*
|
|
11
|
+
* Skipped entirely in --alpha mode unless --track-alpha is passed.
|
|
12
|
+
* When tracking alpha, only force-pins packages we just published —
|
|
13
|
+
* leaves other managed globals alone (they may already be on their
|
|
14
|
+
* own alpha streams).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { PACKAGES, readGlobalInstalledVersion, readPkg } from './helpers';
|
|
21
|
+
import type { GlobalUpdateItem, PackageJson } from './types';
|
|
22
|
+
|
|
23
|
+
// ─── Planner ───────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export interface PlanGlobalUpdateInput {
|
|
26
|
+
/** Versions we just published this run (force-pin targets). */
|
|
27
|
+
justPublished: Array<{ name: string; version: string }>;
|
|
28
|
+
/** True only when `--alpha --track-alpha` was passed. */
|
|
29
|
+
trackAlpha: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Inputs the global-update decision actually depends on. Lifted out
|
|
34
|
+
* as a type so `decideGlobalUpdates` is pure and unit-testable: the
|
|
35
|
+
* disk wrapper (`planGlobalUpdate`) reads bun's global package.json
|
|
36
|
+
* and ~/.bun/install/global/node_modules/<name>/package.json then
|
|
37
|
+
* delegates here.
|
|
38
|
+
*/
|
|
39
|
+
export interface GlobalUpdateDecisionInputs {
|
|
40
|
+
/** Names currently installed globally (intersected with `ourNames`). */
|
|
41
|
+
installed: string[];
|
|
42
|
+
/** Names this run will publish (already filtered for managed packages). */
|
|
43
|
+
justPublished: Array<{ name: string; version: string }>;
|
|
44
|
+
/** True only when `--alpha --track-alpha` was passed. */
|
|
45
|
+
trackAlpha: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Resolver from name → currently-installed version. Pure: caller
|
|
48
|
+
* fetches versions once and hands them in; we don't re-read disk
|
|
49
|
+
* per call. `null` means "couldn't read" (treated as unknown).
|
|
50
|
+
*/
|
|
51
|
+
installedVersion: (name: string) => string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Pure decision function: produce the per-package `GlobalUpdateItem`
|
|
56
|
+
* list from the gathered inputs. Encodes the gating semantics of
|
|
57
|
+
* --track-alpha (filter to just-published only) and the
|
|
58
|
+
* force-pin-vs-update-pull policy (force-pin when we know the target,
|
|
59
|
+
* `bun update -g` otherwise).
|
|
60
|
+
*/
|
|
61
|
+
export function decideGlobalUpdates(inputs: GlobalUpdateDecisionInputs): GlobalUpdateItem[] {
|
|
62
|
+
const publishedMap = new Map(inputs.justPublished.map((p) => [p.name, p.version] as const));
|
|
63
|
+
|
|
64
|
+
let toUpdate = inputs.installed;
|
|
65
|
+
if (inputs.trackAlpha) {
|
|
66
|
+
const justPublishedNames = new Set(inputs.justPublished.map((p) => p.name));
|
|
67
|
+
toUpdate = toUpdate.filter((n) => justPublishedNames.has(n));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const items: GlobalUpdateItem[] = [];
|
|
71
|
+
for (const name of toUpdate) {
|
|
72
|
+
const before = inputs.installedVersion(name);
|
|
73
|
+
const expected = publishedMap.get(name);
|
|
74
|
+
// Force-pin to a known target when we just published it; otherwise
|
|
75
|
+
// resolve to whatever the executor's `bun update -g` lands on.
|
|
76
|
+
items.push({
|
|
77
|
+
name,
|
|
78
|
+
installed: before,
|
|
79
|
+
target: expected ?? before ?? '?',
|
|
80
|
+
forcePin: !!expected,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return items;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function planGlobalUpdate(input: PlanGlobalUpdateInput): GlobalUpdateItem[] {
|
|
87
|
+
const globalPkgPath = join(process.env.HOME ?? '', '.bun', 'install', 'global', 'package.json');
|
|
88
|
+
if (!existsSync(globalPkgPath)) return [];
|
|
89
|
+
|
|
90
|
+
let globalPkg: PackageJson;
|
|
91
|
+
try {
|
|
92
|
+
globalPkg = JSON.parse(readFileSync(globalPkgPath, 'utf-8'));
|
|
93
|
+
} catch {
|
|
94
|
+
// Malformed global pkg — caller will surface this differently
|
|
95
|
+
// (or skip the phase entirely). Returning [] is consistent with
|
|
96
|
+
// "nothing planned".
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const ourNames = new Set(PACKAGES.map((p) => readPkg(p).name).filter((n): n is string => !!n));
|
|
101
|
+
const installed = Object.keys(globalPkg.dependencies ?? {}).filter((n) => ourNames.has(n));
|
|
102
|
+
|
|
103
|
+
return decideGlobalUpdates({
|
|
104
|
+
installed,
|
|
105
|
+
justPublished: input.justPublished,
|
|
106
|
+
trackAlpha: input.trackAlpha,
|
|
107
|
+
installedVersion: readGlobalInstalledVersion,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Executor ──────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
export function executeGlobalUpdate(items: GlobalUpdateItem[]): void {
|
|
114
|
+
if (items.length === 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const globalPkgPath = join(process.env.HOME ?? '', '.bun', 'install', 'global', 'package.json');
|
|
119
|
+
if (!existsSync(globalPkgPath)) {
|
|
120
|
+
console.log('\nNo bun global install found — skipping global update pass.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log('\n──────────────────────────────────────────────');
|
|
125
|
+
console.log(' Updating bun global install');
|
|
126
|
+
console.log('──────────────────────────────────────────────');
|
|
127
|
+
|
|
128
|
+
const mismatches: Array<{ name: string; expected: string; actual: string | null }> = [];
|
|
129
|
+
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
const before = readGlobalInstalledVersion(item.name);
|
|
132
|
+
const cmd = item.forcePin
|
|
133
|
+
? ['add', '-g', `${item.name}@${item.target}`]
|
|
134
|
+
: ['update', '-g', item.name];
|
|
135
|
+
|
|
136
|
+
const r = spawnSync('bun', cmd, { stdio: 'pipe', encoding: 'utf-8' });
|
|
137
|
+
const after = readGlobalInstalledVersion(item.name);
|
|
138
|
+
|
|
139
|
+
const delta =
|
|
140
|
+
before === after ? `unchanged at ${after ?? '?'}` : `${before ?? '?'} → ${after ?? '?'}`;
|
|
141
|
+
|
|
142
|
+
if (r.status !== 0) {
|
|
143
|
+
console.log(`✗ ${item.name}: ${delta}`);
|
|
144
|
+
console.log(` bun ${cmd.join(' ')} failed (exit ${r.status})`);
|
|
145
|
+
if (r.stderr) console.log(` ${r.stderr.trim().split('\n').join('\n ')}`);
|
|
146
|
+
if (item.forcePin && after !== item.target) {
|
|
147
|
+
mismatches.push({ name: item.name, expected: item.target, actual: after });
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (item.forcePin && after !== item.target) {
|
|
153
|
+
console.log(`⚠ ${item.name}: ${delta} (expected ${item.target})`);
|
|
154
|
+
mismatches.push({ name: item.name, expected: item.target, actual: after });
|
|
155
|
+
} else {
|
|
156
|
+
console.log(`✓ ${item.name}: ${delta}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (mismatches.length > 0) {
|
|
161
|
+
console.log('\n✗ Global install ended up on the wrong version for some packages:');
|
|
162
|
+
for (const m of mismatches) {
|
|
163
|
+
console.log(` ${m.name}: have ${m.actual ?? '(missing)'}, expected ${m.expected}`);
|
|
164
|
+
console.log(` fix: bun add -g ${m.name}@${m.expected}`);
|
|
165
|
+
}
|
|
166
|
+
console.log(
|
|
167
|
+
'\n Until this is resolved, your global cele2e/celilo binary is NOT the one this publish just shipped.',
|
|
168
|
+
);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-read utilities shared across the publish planner + executor.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here mutates state — these helpers query npm / git / fs to
|
|
5
|
+
* inform planning decisions. The planner is "logically pure" (same
|
|
6
|
+
* world state → same plan) even though it makes I/O calls, because
|
|
7
|
+
* everything in this file is a read.
|
|
8
|
+
*
|
|
9
|
+
* Write-side helpers (rewriteWorkspaceDeps, restorePackageJson, etc.)
|
|
10
|
+
* live alongside the executors in their respective phase files.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
14
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
16
|
+
import type { PackageJson, WorkspaceVersionMap } from './types';
|
|
17
|
+
|
|
18
|
+
// Publish in dependency order: leaves first, then consumers.
|
|
19
|
+
// Exported so tests can construct a synthetic packages list against the
|
|
20
|
+
// same shape; runtime always uses these in this exact order.
|
|
21
|
+
export const PACKAGES = [
|
|
22
|
+
'packages/cli-display',
|
|
23
|
+
'packages/capabilities',
|
|
24
|
+
'packages/event-bus',
|
|
25
|
+
'apps/celilo',
|
|
26
|
+
'packages/e2e',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
// REPO_ROOT discovery — this file lives at
|
|
30
|
+
// apps/celilo/src/cli/commands/publish/helpers.ts, six dirs deep.
|
|
31
|
+
// Kept as a relative path (matches existing convention in
|
|
32
|
+
// services/module-types-drift.test.ts and friends) rather than
|
|
33
|
+
// `git rev-parse` to avoid a subprocess on every publish startup.
|
|
34
|
+
export const REPO_ROOT = resolve(import.meta.dir, '../../../../../..');
|
|
35
|
+
|
|
36
|
+
export const ENV_FILE = join(REPO_ROOT, '.env');
|
|
37
|
+
|
|
38
|
+
export function readPkg(dir: string): PackageJson {
|
|
39
|
+
return JSON.parse(readFileSync(join(REPO_ROOT, dir, 'package.json'), 'utf-8'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isPublished(name: string, version: string): boolean {
|
|
43
|
+
const r = spawnSync('npm', ['view', `${name}@${version}`, 'version'], {
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
45
|
+
});
|
|
46
|
+
return r.status === 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function currentGitHead(): string {
|
|
50
|
+
return execSync('git rev-parse HEAD', { encoding: 'utf-8', cwd: REPO_ROOT }).trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Find the most recent commit touching any of the given pathspecs. Used
|
|
55
|
+
* by the stale-version check to compare "last src change" vs "last
|
|
56
|
+
* package.json change."
|
|
57
|
+
*/
|
|
58
|
+
export function lastCommitTouching(pathspec: string[]): string | null {
|
|
59
|
+
const r = spawnSync('git', ['log', '-1', '--format=%H', '--', ...pathspec], {
|
|
60
|
+
cwd: REPO_ROOT,
|
|
61
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
62
|
+
encoding: 'utf-8',
|
|
63
|
+
});
|
|
64
|
+
if (r.status !== 0) return null;
|
|
65
|
+
const sha = r.stdout.trim();
|
|
66
|
+
return sha || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isAncestor(maybeAncestor: string, descendant: string): boolean {
|
|
70
|
+
const r = spawnSync('git', ['merge-base', '--is-ancestor', maybeAncestor, descendant], {
|
|
71
|
+
cwd: REPO_ROOT,
|
|
72
|
+
stdio: 'ignore',
|
|
73
|
+
});
|
|
74
|
+
return r.status === 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildWorkspaceVersionMap(): WorkspaceVersionMap {
|
|
78
|
+
const m = new Map<string, string>();
|
|
79
|
+
for (const pkg of PACKAGES) {
|
|
80
|
+
const { name, version } = readPkg(pkg);
|
|
81
|
+
if (name && version) m.set(name, version);
|
|
82
|
+
}
|
|
83
|
+
return m;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* List every publishable module under modules/. Excludes `archive/`
|
|
88
|
+
* (operator-confirmed: not publishable) and any dir lacking a
|
|
89
|
+
* manifest.yml.
|
|
90
|
+
*/
|
|
91
|
+
export function listModuleDirs(): string[] {
|
|
92
|
+
const modulesRoot = join(REPO_ROOT, 'modules');
|
|
93
|
+
if (!existsSync(modulesRoot)) return [];
|
|
94
|
+
const out: string[] = [];
|
|
95
|
+
for (const name of readdirSync(modulesRoot)) {
|
|
96
|
+
if (name === 'archive') continue;
|
|
97
|
+
const dir = join(modulesRoot, name);
|
|
98
|
+
let st: ReturnType<typeof statSync>;
|
|
99
|
+
try {
|
|
100
|
+
st = statSync(dir);
|
|
101
|
+
} catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!st.isDirectory()) continue;
|
|
105
|
+
if (!existsSync(join(dir, 'manifest.yml'))) continue;
|
|
106
|
+
out.push(dir);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read `EXTERNAL_PROJECT_PATHS` from .env (space-separated absolute paths).
|
|
113
|
+
* Returns [] if the file is absent or the var is unset — bumping just
|
|
114
|
+
* the in-repo modules is still useful on its own.
|
|
115
|
+
*/
|
|
116
|
+
export function readExternalProjectPaths(): string[] {
|
|
117
|
+
if (!existsSync(ENV_FILE)) return [];
|
|
118
|
+
const content = readFileSync(ENV_FILE, 'utf-8');
|
|
119
|
+
for (const line of content.split('\n')) {
|
|
120
|
+
const m = line.match(/^\s*EXTERNAL_PROJECT_PATHS\s*=\s*(.+?)\s*$/);
|
|
121
|
+
if (!m) continue;
|
|
122
|
+
const raw = m[1].replace(/^["']|["']$/g, '');
|
|
123
|
+
return raw.split(/\s+/).filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Recursively find every package.json under a root, skipping
|
|
130
|
+
* node_modules and common build-output dirs (so we don't try to rewrite
|
|
131
|
+
* lock-installed copies — those get refreshed by `bun install`).
|
|
132
|
+
*/
|
|
133
|
+
export function findPackageJsons(root: string): string[] {
|
|
134
|
+
const out: string[] = [];
|
|
135
|
+
const SKIP_DIRS = new Set(['node_modules', '.bun', 'dist', '.nx', '.git']);
|
|
136
|
+
|
|
137
|
+
function walk(dir: string): void {
|
|
138
|
+
let entries: string[];
|
|
139
|
+
try {
|
|
140
|
+
entries = readdirSync(dir);
|
|
141
|
+
} catch {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
for (const name of entries) {
|
|
145
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
146
|
+
const full = join(dir, name);
|
|
147
|
+
let st: ReturnType<typeof statSync>;
|
|
148
|
+
try {
|
|
149
|
+
st = statSync(full);
|
|
150
|
+
} catch {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (st.isDirectory()) {
|
|
154
|
+
walk(full);
|
|
155
|
+
} else if (name === 'package.json') {
|
|
156
|
+
out.push(full);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
walk(root);
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Strip semver operators (^, ~, =, >=, etc.) to get the bare version. */
|
|
165
|
+
export function bareVersion(spec: string): string {
|
|
166
|
+
return spec.replace(/^[\s^~=><]+/, '').trim();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Re-apply the same operator that was on the old spec. An exact pin
|
|
171
|
+
* (`"0.1.9"`) stays exact (`"0.1.10"`). A caret pin (`"^0.1.9"`) stays
|
|
172
|
+
* caret (`"^0.1.10"`). Don't widen exact pins to caret silently —
|
|
173
|
+
* that masks a deliberate operator choice.
|
|
174
|
+
*/
|
|
175
|
+
export function withOperator(oldSpec: string, newVersion: string): string {
|
|
176
|
+
if (oldSpec.startsWith('workspace:')) return oldSpec;
|
|
177
|
+
if (/^[a-z]+:/.test(oldSpec) && !oldSpec.startsWith('npm:')) return oldSpec;
|
|
178
|
+
const m = oldSpec.match(/^(\^|~|>=|<=|=|>|<)?/);
|
|
179
|
+
const op = m?.[1] ?? '';
|
|
180
|
+
return `${op}${newVersion}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Read the installed version of `<name>` from bun's global node_modules.
|
|
185
|
+
* Returns null if the package isn't installed there (or its package.json
|
|
186
|
+
* is unreadable/malformed — we'd rather warn loudly later than crash here).
|
|
187
|
+
*/
|
|
188
|
+
export function readGlobalInstalledVersion(name: string): string | null {
|
|
189
|
+
const pkgJsonPath = join(
|
|
190
|
+
process.env.HOME ?? '',
|
|
191
|
+
'.bun',
|
|
192
|
+
'install',
|
|
193
|
+
'global',
|
|
194
|
+
'node_modules',
|
|
195
|
+
...name.split('/'),
|
|
196
|
+
'package.json',
|
|
197
|
+
);
|
|
198
|
+
if (!existsSync(pkgJsonPath)) return null;
|
|
199
|
+
try {
|
|
200
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as PackageJson;
|
|
201
|
+
return pkg.version ?? null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Look up the npm-latest version of every package we manage. Runs
|
|
209
|
+
* regardless of whether anything was published this session — the
|
|
210
|
+
* goal is to drag consumers up to current head, not just to apply
|
|
211
|
+
* the deltas from this run.
|
|
212
|
+
*/
|
|
213
|
+
export function fetchLatestVersions(): Map<string, string> {
|
|
214
|
+
const out = new Map<string, string>();
|
|
215
|
+
for (const pkg of PACKAGES) {
|
|
216
|
+
const { name } = readPkg(pkg);
|
|
217
|
+
if (!name) continue;
|
|
218
|
+
const r = spawnSync('npm', ['view', name, 'version'], {
|
|
219
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
220
|
+
encoding: 'utf-8',
|
|
221
|
+
});
|
|
222
|
+
if (r.status !== 0) continue;
|
|
223
|
+
const version = r.stdout.trim();
|
|
224
|
+
if (version) out.set(name, version);
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|