@rightkit/release 0.2.118 → 0.2.119

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/development.mjs CHANGED
@@ -34,6 +34,10 @@ export function developmentEnvironment(env = process.env) {
34
34
  const pathKey = Object.keys(result).find((key) => key.toLowerCase() === "path") ?? "PATH";
35
35
  result[pathKey] = [agentBin, result[pathKey]].filter(Boolean).join(path.delimiter);
36
36
  }
37
+ // Same default as the agent-bin `rightkit` shim: children that run the managed client directly
38
+ // (resolveCommand("rightkit"), build scripts) must not lose the broker endpoint.
39
+ const socket = managedRoot && path.join(managedRoot, "broker.sock");
40
+ if (!result.RIGHTKIT_BUILD_BROKER_SOCKET && socket && existsSync(socket)) result.RIGHTKIT_BUILD_BROKER_SOCKET = socket;
37
41
  }
38
42
  return result;
39
43
  }
@@ -196,7 +200,7 @@ function execute(command, { root, env, dryRun, run, label }) {
196
200
  const args = (command.args ?? []).map(String);
197
201
  if (dryRun) return { cmd: command.cmd, args, cwd };
198
202
  const launch = resolveCommand(command.cmd, args, { root, env });
199
- const result = run(launch.command, launch.args, { cwd, env, stdio: "inherit", windowsHide: true, shell: false, timeout: command.timeoutMs });
203
+ const result = run(launch.command, launch.args, { cwd, env: launch.env ?? env, stdio: "inherit", windowsHide: true, shell: false, timeout: command.timeoutMs });
200
204
  if (result.status !== 0) throw new Error(`development ${label} command failed (${result.status}): ${command.name ?? command.cmd}`);
201
205
  }
202
206
  export function resolveCommand(command, args, { root, env = process.env } = {}) {
@@ -205,7 +209,9 @@ export function resolveCommand(command, args, { root, env = process.env } = {})
205
209
  if (command === "rightkit") {
206
210
  const client = managedRuntimeClient(env);
207
211
  if (!client) throw new Error("installed RightKit runtime client is unavailable for development command");
208
- return { command: process.execPath, args: [client, ...args] };
212
+ const socket = path.join(path.dirname(path.dirname(path.dirname(path.dirname(client)))), "broker.sock");
213
+ const childEnv = !env.RIGHTKIT_BUILD_BROKER_SOCKET && existsSync(socket) ? { ...env, RIGHTKIT_BUILD_BROKER_SOCKET: socket } : undefined;
214
+ return { command: process.execPath, args: [client, ...args], ...(childEnv ? { env: childEnv } : {}) };
209
215
  }
210
216
  return { command, args };
211
217
  }
@@ -353,6 +353,10 @@ function Invoke-BoundedExe($Path, [string[]]$Arguments, [int]$TimeoutSeconds) {
353
353
  "$ReleaseBase = if ($Version) { " + psQuote("https://github.com/" + repository + "/releases/download/v") + " + $Version } else { $null }",
354
354
  "$Work = Join-Path ([IO.Path]::GetTempPath()) (" + psQuote(product + "-install-") + " + [guid]::NewGuid())",
355
355
  "New-Item -ItemType Directory -Force -Path $Work | Out-Null",
356
+ // Early failures (download, catalog, checksum, preflight) throw before the activation
357
+ // try/finally; the trap removes $Work for them, and stale dirs from older bootstraps are swept.
358
+ "trap { try { if ($Work -and (Test-Path -LiteralPath $Work)) { Remove-Item -LiteralPath $Work -Recurse -Force -ErrorAction SilentlyContinue } } catch {}; break }",
359
+ "Get-ChildItem ([IO.Path]::GetTempPath()) -Directory -Filter " + psQuote(product + "-install-*") + " -ErrorAction SilentlyContinue | Where-Object { $_.FullName -ne $Work -and $_.LastWriteTime -lt (Get-Date).AddDays(-1) } | ForEach-Object { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue }",
356
360
  "$ManifestPath = Join-Path $Work 'release-manifest.json'; $SignaturePath = Join-Path $Work 'release-manifest.cat'; $ChecksumsPath = Join-Path $Work 'checksums.json'",
357
361
  "function Receive-ReleaseFile([string]$Name, [string]$Destination) { if ($LocalReleaseRoot) { $RootPrefix = $LocalReleaseRoot.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar; $Source = [IO.Path]::GetFullPath((Join-Path $LocalReleaseRoot $Name)); if (-not $Source.StartsWith($RootPrefix, [StringComparison]::OrdinalIgnoreCase) -or -not (Test-Path -LiteralPath $Source -PathType Leaf)) { throw ('Local release asset is missing or escapes root: ' + $Name) }; Copy-Item -LiteralPath $Source -Destination $Destination } else { Invoke-WebRequest ($ReleaseBase + '/' + $Name) -OutFile $Destination } }",
358
362
  "Receive-ReleaseFile 'release-manifest.json' $ManifestPath",
@@ -367,6 +371,10 @@ function Invoke-BoundedExe($Path, [string[]]$Arguments, [int]$TimeoutSeconds) {
367
371
  "$ReleaseBase = " + psQuote("https://github.com/" + repository + "/releases/download/v") + " + $Version",
368
372
  "$Work = Join-Path ([IO.Path]::GetTempPath()) (" + psQuote(product + "-install-") + " + [guid]::NewGuid())",
369
373
  "New-Item -ItemType Directory -Force -Path $Work | Out-Null",
374
+ // Early failures (download, catalog, checksum, preflight) throw before the activation
375
+ // try/finally; the trap removes $Work for them, and stale dirs from older bootstraps are swept.
376
+ "trap { try { if ($Work -and (Test-Path -LiteralPath $Work)) { Remove-Item -LiteralPath $Work -Recurse -Force -ErrorAction SilentlyContinue } } catch {}; break }",
377
+ "Get-ChildItem ([IO.Path]::GetTempPath()) -Directory -Filter " + psQuote(product + "-install-*") + " -ErrorAction SilentlyContinue | Where-Object { $_.FullName -ne $Work -and $_.LastWriteTime -lt (Get-Date).AddDays(-1) } | ForEach-Object { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue }",
370
378
  "$ManifestPath = Join-Path $Work 'release-manifest.json'; $SignaturePath = Join-Path $Work 'release-manifest.cat'; $ChecksumsPath = Join-Path $Work 'checksums.json'",
371
379
  "Invoke-WebRequest ($ReleaseBase + '/release-manifest.json') -OutFile $ManifestPath",
372
380
  "Invoke-WebRequest ($ReleaseBase + '/release-manifest.cat') -OutFile $SignaturePath",
@@ -440,6 +448,10 @@ function Invoke-BoundedExe($Path, [string[]]$Arguments, [int]$TimeoutSeconds) {
440
448
  " $StableBin = Split-Path $StableExe -Parent; $InstallRootResolved = [IO.Path]::GetFullPath($InstallRoot).TrimEnd([IO.Path]::DirectorySeparatorChar); $UserPath = [Environment]::GetEnvironmentVariable('Path','User'); $Kept = @($UserPath -split ';' | Where-Object { $Entry = $_.Trim(); $Entry -ne '' -and -not ($Entry.TrimEnd([IO.Path]::DirectorySeparatorChar).StartsWith(($InstallRootResolved + [IO.Path]::DirectorySeparatorChar), [StringComparison]::OrdinalIgnoreCase) -and -not $Entry.TrimEnd([IO.Path]::DirectorySeparatorChar).Equals($StableBin.TrimEnd([IO.Path]::DirectorySeparatorChar), [StringComparison]::OrdinalIgnoreCase)) }); $NewPath = ((@($StableBin) + $Kept) -join ';'); if ($NewPath -ne $UserPath) { [Environment]::SetEnvironmentVariable('Path', $NewPath, 'User') }",
441
449
  // Journal cleanup must succeed before discarding the prior pointer. If
442
450
  // cleanup fails, catch still has a rollback anchor to restore.
451
+ // Retention: after a healthy activation keep only the new version and the prior target (the
452
+ // app's first-start probation may still roll back to it, then deletes it). Every other version
453
+ // is removed; one a running process still holds fails the rename and is retried next install.
454
+ " $KeepVersions = @([IO.Path]::GetFullPath($VersionRoot).TrimEnd([IO.Path]::DirectorySeparatorChar)); if ($PriorTarget) { $KeepVersions += [IO.Path]::GetFullPath([string]$PriorTarget).TrimEnd([IO.Path]::DirectorySeparatorChar) }; Get-ChildItem -LiteralPath $VersionsRoot -Directory -Force -ErrorAction SilentlyContinue | Where-Object { $Candidate = $_.FullName.TrimEnd([IO.Path]::DirectorySeparatorChar); -not ($KeepVersions | Where-Object { $_.Equals($Candidate, [StringComparison]::OrdinalIgnoreCase) }) } | ForEach-Object { try { $Doomed = Join-Path $VersionsRoot ('.prune-' + [guid]::NewGuid()); Rename-Item -LiteralPath $_.FullName -NewName (Split-Path -Leaf $Doomed) -ErrorAction Stop; Remove-Item -LiteralPath $Doomed -Recurse -Force -ErrorAction SilentlyContinue } catch { } }",
443
455
  " Remove-Item -Force $Journal; Remove-StableJunction $PreviousLink; Write-Output (" + psQuote(product + " ") + " + $Version + ' installed')",
444
456
  "} catch {",
445
457
  " $InstallError = $_; $RollbackErrors = New-Object 'System.Collections.Generic.List[string]'",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.118",
3
+ "version": "0.2.119",
4
4
  "description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { createHash } from "node:crypto";
4
+ import { spawnSync } from "./background-process.mjs";
4
5
 
5
6
  const CHUNK_BYTES = 1024 * 1024;
6
7
 
@@ -62,6 +63,7 @@ export function installPortableDevelopment(descriptor, { root, platform = proces
62
63
  const entryDigest = hashFile(entrypoint);
63
64
  if (entryDigest !== hashFile(path.join(source, descriptor.entrypoint))) throw new Error("development entrypoint readback did not match source");
64
65
  if (backup) removeIfExists(backup);
66
+ emit("PRUNE", pruneDevelopmentVersions(installRoot, versionRoot, { platform }));
65
67
  emit("SUCCEEDED", { installRoot, current, versionRoot, digest, entrypoint: descriptor.entrypoint, entrypointDigest: entryDigest });
66
68
  return { installRoot, current, versionRoot, digest, entrypoint: path.join(current, descriptor.entrypoint), entrypointDigest: entryDigest };
67
69
  } catch (error) {
@@ -75,6 +77,60 @@ export function installPortableDevelopment(descriptor, { root, platform = proces
75
77
  }
76
78
  }
77
79
 
80
+ /**
81
+ * Development installs keep only the active version: dev builds are rebuilt from source, so there is
82
+ * no rollback copy. A version a running process still uses is skipped and removed by the next
83
+ * install. Never throws: a prune problem must not fail an install that already activated.
84
+ */
85
+ export function pruneDevelopmentVersions(installRoot, keepRoot, { platform = process.platform, runningExecutables = null } = {}) {
86
+ const versionsRoot = path.join(installRoot, "versions");
87
+ const removed = []; const skipped = []; let reclaimedBytes = 0;
88
+ let running = null;
89
+ const inUse = (dir) => {
90
+ if (platform === "win32") return false; // Windows: a loaded image or DLL makes the rename below fail.
91
+ running ??= runningExecutables ?? listRunningExecutables();
92
+ return running.some((exe) => isWithin(dir, exe, platform));
93
+ };
94
+ const candidates = [];
95
+ for (const [dir, pattern] of [[versionsRoot, /^(dev-[0-9a-f]+|\.stage-.+|\.prune-.+)$/], [installRoot, /^\.(current-backup|prune)-.+$/]]) {
96
+ let names = [];
97
+ try { names = fs.readdirSync(dir); } catch { continue; }
98
+ for (const name of names) if (pattern.test(name)) candidates.push(path.join(dir, name));
99
+ }
100
+ for (const candidate of candidates) {
101
+ if (samePath(candidate, keepRoot, platform)) continue;
102
+ if (path.basename(candidate).includes(`-${process.pid}-`)) continue; // this process's own stage/backup
103
+ try {
104
+ if (inUse(candidate)) { skipped.push({ path: candidate, reason: "in-use" }); continue; }
105
+ const bytes = treeBytes(candidate);
106
+ // Rename first: on Windows this fails while any file inside is loaded, so a running version is skipped whole.
107
+ const doomed = path.basename(candidate).startsWith(".prune-") ? candidate : path.join(path.dirname(candidate), `.prune-${randomSuffix()}`);
108
+ if (doomed !== candidate) fs.renameSync(candidate, doomed);
109
+ fs.rmSync(doomed, { recursive: true, force: true });
110
+ removed.push(candidate); reclaimedBytes += bytes;
111
+ } catch (error) {
112
+ skipped.push({ path: candidate, reason: error?.code ?? error?.message ?? String(error) });
113
+ }
114
+ }
115
+ return { kept: keepRoot, removed: removed.length, skipped, reclaimedBytes };
116
+ }
117
+
118
+ function listRunningExecutables() {
119
+ const result = spawnSync("ps", ["-axo", "comm="], { encoding: "utf8" });
120
+ return result.status === 0 ? result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => path.isAbsolute(line)) : [];
121
+ }
122
+
123
+ function treeBytes(target) {
124
+ let total = 0;
125
+ const stat = fs.lstatSync(target);
126
+ if (!stat.isDirectory()) return stat.size;
127
+ for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
128
+ const child = path.join(target, entry.name);
129
+ total += entry.isDirectory() && !entry.isSymbolicLink() ? treeBytes(child) : fs.lstatSync(child).size;
130
+ }
131
+ return total;
132
+ }
133
+
78
134
  function validateDescriptor(descriptor, root, platform) {
79
135
  if (!descriptor || descriptor.kind !== "portable" || descriptor.activation?.kind !== "current") throw new Error("unsupported portable development descriptor");
80
136
  if (typeof root !== "string" || !path.isAbsolute(root)) throw new Error("repository root must be absolute");
@@ -24,12 +24,12 @@
24
24
  "@rightkit/license": "0.1.8",
25
25
  "@rightkit/logs": "0.1.4",
26
26
  "@rightkit/media-ui": "0.2.5",
27
- "@rightkit/onboarding": "0.2.1",
27
+ "@rightkit/onboarding": "0.2.2",
28
28
  "@rightkit/pdf-ui": "0.1.1",
29
29
  "@rightkit/platform-ui": "0.1.2",
30
30
  "@rightkit/qa": "0.2.1",
31
- "@rightkit/release": "0.2.118",
32
- "@rightkit/settings-ui": "0.2.2",
31
+ "@rightkit/release": "0.2.119",
32
+ "@rightkit/settings-ui": "0.2.3",
33
33
  "@rightkit/shell": "0.1.0",
34
34
  "@rightkit/tauri": "0.1.1",
35
35
  "@rightkit/theme": "0.2.0",
@@ -139,7 +139,8 @@
139
139
  "0.2.114",
140
140
  "0.2.115",
141
141
  "0.2.116",
142
- "0.2.117"
142
+ "0.2.117",
143
+ "0.2.118"
143
144
  ],
144
145
  "@rightkit/qa": [
145
146
  "0.1.0",
@@ -161,11 +162,13 @@
161
162
  ],
162
163
  "@rightkit/settings-ui": [
163
164
  "0.1.0",
164
- "0.2.0"
165
+ "0.2.0",
166
+ "0.2.2"
165
167
  ],
166
168
  "@rightkit/onboarding": [
167
169
  "0.1.0",
168
- "0.2.0"
170
+ "0.2.0",
171
+ "0.2.1"
169
172
  ],
170
173
  "@rightkit/doc-render": [
171
174
  "0.1.0",
@@ -200,14 +203,14 @@
200
203
  "rightkit-audio": "0.1.0",
201
204
  "rightkit-browser": "0.1.1",
202
205
  "rightkit-cache": "0.1.0",
203
- "rightkit-control": "0.1.1",
206
+ "rightkit-control": "0.1.2",
204
207
  "rightkit-crash": "0.1.0",
205
- "rightkit-db": "0.2.0",
208
+ "rightkit-db": "0.2.1",
206
209
  "rightkit-framed-sidecar": "0.1.0",
207
210
  "rightkit-fs": "0.1.0",
208
- "rightkit-jobs": "0.1.0",
211
+ "rightkit-jobs": "0.2.0",
209
212
  "rightkit-logs": "0.2.0",
210
- "rightkit-media": "0.1.0",
213
+ "rightkit-media": "0.2.0",
211
214
  "rightkit-models": "0.1.0",
212
215
  "rightkit-pdf": "0.1.0",
213
216
  "rightkit-playback": "0.1.0",