@vantaloom/cli 0.5.3 → 0.13.6
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/package.json +2 -1
- package/src/cli.mjs +71 -1299
- package/src/lib/auth.mjs +155 -0
- package/src/lib/constants.mjs +29 -0
- package/src/lib/install.mjs +590 -0
- package/src/lib/legacy-cleanup.mjs +148 -0
- package/src/lib/lifecycle.mjs +189 -0
- package/src/lib/package.mjs +98 -0
- package/src/lib/platform.mjs +158 -0
- package/src/lib/registry.mjs +237 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs"
|
|
8
|
+
import { spawnSync } from "node:child_process"
|
|
9
|
+
import path from "node:path"
|
|
10
|
+
import { binaryName, psQuote } from "./platform.mjs"
|
|
11
|
+
|
|
12
|
+
// This module holds process-cleanup helpers that used to live in mesh.mjs
|
|
13
|
+
// (removed in 0.13 along with the EasyTier mesh sidecar and the CLI's
|
|
14
|
+
// install-time mesh-service registration). Neither function below is
|
|
15
|
+
// mesh-specific to its live callers:
|
|
16
|
+
// - killTrayProcess predates the mesh sidecar entirely (kills a leftover
|
|
17
|
+
// vantaloom-tray.exe — a separate, already-removed feature).
|
|
18
|
+
// - removeMeshService is now a ONE-TIME migration primitive: the elevated
|
|
19
|
+
// uninstall of the legacy privileged `VantaloomMesh` service, invoked
|
|
20
|
+
// exactly once per install by uninstallLegacyMeshOnce below.
|
|
21
|
+
|
|
22
|
+
export function killTrayProcess(prefix) {
|
|
23
|
+
if (process.platform === "win32") {
|
|
24
|
+
// Try PID file first (new tray versions write runtime/tray.pid).
|
|
25
|
+
const pidFile = path.join(prefix, "runtime", "tray.pid")
|
|
26
|
+
if (existsSync(pidFile)) {
|
|
27
|
+
const pid = readFileSync(pidFile, "utf8").trim()
|
|
28
|
+
if (pid) {
|
|
29
|
+
spawnSync("taskkill", ["/PID", pid, "/F"], { stdio: "ignore", windowsHide: true })
|
|
30
|
+
try { rmSync(pidFile, { force: true }) } catch {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Fallback: kill by image name if the binary is inside our prefix.
|
|
34
|
+
const result = spawnSync("tasklist", ["/FI", "IMAGENAME eq vantaloom-tray.exe", "/FO", "CSV", "/NH"], {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
})
|
|
38
|
+
if (result.stdout) {
|
|
39
|
+
for (const line of result.stdout.split("\n")) {
|
|
40
|
+
const match = line.match(/"vantaloom-tray\.exe","(\d+)"/)
|
|
41
|
+
if (match) {
|
|
42
|
+
spawnSync("taskkill", ["/PID", match[1], "/F"], { stdio: "ignore", windowsHide: true })
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Brief pause to let file handles release.
|
|
47
|
+
spawnSync("timeout", ["/t", "1", "/nobreak"], { stdio: "ignore", windowsHide: true })
|
|
48
|
+
} else {
|
|
49
|
+
// Unix: pkill by name (best-effort).
|
|
50
|
+
spawnSync("pkill", ["-f", "vantaloom-tray"], { stdio: "ignore", windowsHide: true })
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// meshServiceRunningOrInstalled reports whether the Windows service exists at
|
|
55
|
+
// all (running or stopped), so removeMeshService only prompts for elevation
|
|
56
|
+
// when there is actually something to remove.
|
|
57
|
+
function meshServiceRunningOrInstalled(binDir) {
|
|
58
|
+
const exe = path.join(binDir, "vantaloom-mesh.exe")
|
|
59
|
+
if (!existsSync(exe)) return false
|
|
60
|
+
const r = spawnSync(exe, ["status"], { encoding: "utf8", windowsHide: true })
|
|
61
|
+
if (r.error || typeof r.stdout !== "string") return false
|
|
62
|
+
return !r.stdout.toLowerCase().includes("not installed")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// removeMeshService elevates once to invoke the still-on-disk legacy
|
|
66
|
+
// vantaloom-mesh binary's own `uninstall` verb, which self-removes its SCM
|
|
67
|
+
// service (Windows) or LaunchDaemon (macOS). Linux never registered a service
|
|
68
|
+
// (it only used setcap) so there's nothing to remove there.
|
|
69
|
+
//
|
|
70
|
+
// Returns true when there was nothing to do, or removal succeeded; false when
|
|
71
|
+
// an elevated uninstall was attempted but declined/failed (never throws) — a
|
|
72
|
+
// caller that wants "retry next time" semantics acts on the return value (see
|
|
73
|
+
// uninstallLegacyMeshOnce).
|
|
74
|
+
export function removeMeshService(prefix, options) {
|
|
75
|
+
const platform = process.platform
|
|
76
|
+
if (platform !== "win32" && platform !== "darwin") return true // Linux: nothing registered
|
|
77
|
+
const exe = path.join(prefix, "bin", binaryName("vantaloom-mesh"))
|
|
78
|
+
if (!existsSync(exe)) return true
|
|
79
|
+
if (options.skipMesh) return true
|
|
80
|
+
|
|
81
|
+
if (platform === "win32") {
|
|
82
|
+
if (!meshServiceRunningOrInstalled(path.join(prefix, "bin"))) return true
|
|
83
|
+
console.log(" mesh: removing legacy privileged P2P service (UAC prompt) ...")
|
|
84
|
+
const ps = `$ErrorActionPreference='Stop'; $p = Start-Process -FilePath ${psQuote(exe)} -ArgumentList 'uninstall' -Verb RunAs -Wait -PassThru; exit $p.ExitCode`
|
|
85
|
+
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "inherit", windowsHide: true })
|
|
86
|
+
if (r.error || r.status !== 0) {
|
|
87
|
+
console.warn(` mesh: could not remove legacy service; run manually (as Administrator): "${exe}" uninstall`)
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(" mesh: removing legacy privileged P2P service (sudo) ...")
|
|
94
|
+
const r = spawnSync("sudo", [exe, "uninstall"], { stdio: "inherit", windowsHide: true })
|
|
95
|
+
if (r.error || r.status !== 0) {
|
|
96
|
+
console.warn(` mesh: could not remove legacy service; run manually: sudo "${exe}" uninstall`)
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The bin/ files a pre-0.13 install's EasyTier mesh sidecar left behind
|
|
103
|
+
// (Windows and Unix names both listed — deleting a name absent on this
|
|
104
|
+
// platform is a harmless no-op via rmSync's force:true).
|
|
105
|
+
const LEGACY_MESH_FILE_NAMES = [
|
|
106
|
+
"vantaloom-mesh.exe", "vantaloom-mesh",
|
|
107
|
+
"easytier-core.exe", "easytier-core",
|
|
108
|
+
"easytier-cli.exe", "easytier-cli",
|
|
109
|
+
"wintun.dll", "Packet.dll", "WinDivert64.sys",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
// uninstallLegacyMeshOnce is the one-time migration that removes a pre-0.13
|
|
113
|
+
// install's privileged `VantaloomMesh` service (Windows SCM service / macOS
|
|
114
|
+
// LaunchDaemon; Linux never had one) plus its stale bin/ binaries. It exists
|
|
115
|
+
// because applyPackage's bin/ copy only OVERLAYS files — it never deletes —
|
|
116
|
+
// so a 0.13+ package (which no longer bundles vantaloom-mesh/easytier-core/
|
|
117
|
+
// the support DLLs) would otherwise leave the old service + binaries in place
|
|
118
|
+
// forever, and the orphaned service could even restart-loop easytier-core.
|
|
119
|
+
//
|
|
120
|
+
// Idempotent via a done-marker file (<prefix>/.mesh-removed): once cleanup has
|
|
121
|
+
// run, every future install/update returns immediately. Never blocks
|
|
122
|
+
// install/update — a failed or user-declined elevation prompt just logs a
|
|
123
|
+
// hint and leaves the marker UNwritten, so the very next install/update
|
|
124
|
+
// retries (design doc §10: "拒绝则留置、下次再试").
|
|
125
|
+
export async function uninstallLegacyMeshOnce(prefix, options) {
|
|
126
|
+
const markerPath = path.join(prefix, ".mesh-removed")
|
|
127
|
+
if (existsSync(markerPath)) return
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const removed = removeMeshService(prefix, options)
|
|
131
|
+
if (!removed) {
|
|
132
|
+
console.warn(" mesh: legacy service removal was declined or failed; will retry on the next install/update")
|
|
133
|
+
return // leave the marker unwritten so this retries
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Best-effort: delete the stale mesh binaries so nothing can linger or
|
|
137
|
+
// restart-loop even after the service registration itself is gone.
|
|
138
|
+
const binDir = path.join(prefix, "bin")
|
|
139
|
+
for (const name of LEGACY_MESH_FILE_NAMES) {
|
|
140
|
+
try { rmSync(path.join(binDir, name), { force: true }) } catch {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
mkdirSync(prefix, { recursive: true })
|
|
144
|
+
writeFileSync(markerPath, `${new Date().toISOString()}\n`)
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.warn(` mesh: legacy cleanup failed (${error.message}); will retry on the next install/update`)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
rmSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
} from "node:fs"
|
|
7
|
+
import { spawnSync } from "node:child_process"
|
|
8
|
+
import os from "node:os"
|
|
9
|
+
import path from "node:path"
|
|
10
|
+
import {
|
|
11
|
+
AUTOSTART_LABEL,
|
|
12
|
+
WINDOWS_RUN_KEY,
|
|
13
|
+
WINDOWS_RUN_VALUE,
|
|
14
|
+
} from "./constants.mjs"
|
|
15
|
+
import {
|
|
16
|
+
binaryName,
|
|
17
|
+
safeDirectory,
|
|
18
|
+
defaultPrefix,
|
|
19
|
+
run,
|
|
20
|
+
removeKnownPath,
|
|
21
|
+
} from "./platform.mjs"
|
|
22
|
+
import {
|
|
23
|
+
removeMeshService,
|
|
24
|
+
killTrayProcess,
|
|
25
|
+
} from "./legacy-cleanup.mjs"
|
|
26
|
+
|
|
27
|
+
// ── Runtime login-autostart (no elevation) ──
|
|
28
|
+
// Start the local runtime (api/agent/web/tray) at login/boot using only
|
|
29
|
+
// per-user mechanisms — no UAC/sudo. This is separate from the privileged mesh
|
|
30
|
+
// service: the mesh sidecar autostarts via the OS service manager; this brings
|
|
31
|
+
// up the unprivileged runtime that joins the mesh and serves the local API.
|
|
32
|
+
|
|
33
|
+
export function enableRuntimeAutostart(prefix) {
|
|
34
|
+
try {
|
|
35
|
+
if (process.platform === "win32") {
|
|
36
|
+
// A .vbs launches the runtime with a hidden window (no console flash); the
|
|
37
|
+
// HKCU Run key runs it at login without elevation.
|
|
38
|
+
const launcher = path.join(prefix, "vantaloom.cmd")
|
|
39
|
+
const vbsPath = path.join(prefix, "autostart.vbs")
|
|
40
|
+
const vbs = `' Vantaloom runtime autostart (hidden)\r\nCreateObject("WScript.Shell").Run """${launcher}"" start", 0, False\r\n`
|
|
41
|
+
writeFileSync(vbsPath, vbs)
|
|
42
|
+
const result = spawnSync(
|
|
43
|
+
"powershell",
|
|
44
|
+
[
|
|
45
|
+
"-NoProfile",
|
|
46
|
+
"-NonInteractive",
|
|
47
|
+
"-Command",
|
|
48
|
+
`Set-ItemProperty -Path '${WINDOWS_RUN_KEY}' -Name '${WINDOWS_RUN_VALUE}' -Value 'wscript.exe "${vbsPath}"'`,
|
|
49
|
+
],
|
|
50
|
+
{ stdio: "ignore", windowsHide: true }
|
|
51
|
+
)
|
|
52
|
+
if (result.error || result.status !== 0) {
|
|
53
|
+
console.warn(" autostart: could not register login entry (HKCU Run)")
|
|
54
|
+
} else {
|
|
55
|
+
console.log(" autostart: enabled (login Run key)")
|
|
56
|
+
}
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if (process.platform === "darwin") {
|
|
60
|
+
const launcher = path.join(prefix, "vantaloom")
|
|
61
|
+
const dir = path.join(os.homedir(), "Library", "LaunchAgents")
|
|
62
|
+
mkdirSync(dir, { recursive: true })
|
|
63
|
+
const plistPath = path.join(dir, `${AUTOSTART_LABEL}.plist`)
|
|
64
|
+
// RunAtLoad only (no KeepAlive): `vantaloom start` spawns detached and exits.
|
|
65
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
66
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
67
|
+
<plist version="1.0"><dict>
|
|
68
|
+
<key>Label</key><string>${AUTOSTART_LABEL}</string>
|
|
69
|
+
<key>ProgramArguments</key><array><string>${launcher}</string><string>start</string></array>
|
|
70
|
+
<key>RunAtLoad</key><true/>
|
|
71
|
+
</dict></plist>
|
|
72
|
+
`
|
|
73
|
+
writeFileSync(plistPath, plist)
|
|
74
|
+
spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore", windowsHide: true })
|
|
75
|
+
spawnSync("launchctl", ["load", "-w", plistPath], { stdio: "ignore", windowsHide: true })
|
|
76
|
+
console.log(" autostart: enabled (LaunchAgent)")
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
// Linux: user systemd unit + linger (no root).
|
|
80
|
+
const launcher = path.join(prefix, "vantaloom")
|
|
81
|
+
const dir = path.join(os.homedir(), ".config", "systemd", "user")
|
|
82
|
+
mkdirSync(dir, { recursive: true })
|
|
83
|
+
const unit = `[Unit]
|
|
84
|
+
Description=Vantaloom local runtime
|
|
85
|
+
After=network-online.target
|
|
86
|
+
Wants=network-online.target
|
|
87
|
+
|
|
88
|
+
[Service]
|
|
89
|
+
Type=oneshot
|
|
90
|
+
RemainAfterExit=yes
|
|
91
|
+
ExecStart=${launcher} start
|
|
92
|
+
ExecStop=${launcher} stop
|
|
93
|
+
|
|
94
|
+
[Install]
|
|
95
|
+
WantedBy=default.target
|
|
96
|
+
`
|
|
97
|
+
writeFileSync(path.join(dir, "vantaloom-runtime.service"), unit)
|
|
98
|
+
spawnSync("loginctl", ["enable-linger"], { stdio: "ignore", windowsHide: true })
|
|
99
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore", windowsHide: true })
|
|
100
|
+
spawnSync("systemctl", ["--user", "enable", "vantaloom-runtime.service"], { stdio: "ignore", windowsHide: true })
|
|
101
|
+
console.log(" autostart: enabled (systemd user unit)")
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.warn(` autostart: could not enable (${error.message})`)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function disableRuntimeAutostart() {
|
|
108
|
+
try {
|
|
109
|
+
if (process.platform === "win32") {
|
|
110
|
+
// Remove the Run-key entry; the autostart.vbs lives in the install dir and
|
|
111
|
+
// is deleted with it.
|
|
112
|
+
spawnSync(
|
|
113
|
+
"powershell",
|
|
114
|
+
["-NoProfile", "-NonInteractive", "-Command", `Remove-ItemProperty -Path '${WINDOWS_RUN_KEY}' -Name '${WINDOWS_RUN_VALUE}' -ErrorAction SilentlyContinue`],
|
|
115
|
+
{ stdio: "ignore", windowsHide: true }
|
|
116
|
+
)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
if (process.platform === "darwin") {
|
|
120
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${AUTOSTART_LABEL}.plist`)
|
|
121
|
+
if (existsSync(plistPath)) {
|
|
122
|
+
spawnSync("launchctl", ["unload", "-w", plistPath], { stdio: "ignore", windowsHide: true })
|
|
123
|
+
rmSync(plistPath, { force: true })
|
|
124
|
+
}
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
const unit = path.join(os.homedir(), ".config", "systemd", "user", "vantaloom-runtime.service")
|
|
128
|
+
if (existsSync(unit)) {
|
|
129
|
+
spawnSync("systemctl", ["--user", "disable", "vantaloom-runtime.service"], { stdio: "ignore", windowsHide: true })
|
|
130
|
+
rmSync(unit, { force: true })
|
|
131
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore", windowsHide: true })
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn(` autostart: could not disable (${error.message})`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// uninstallRuntime tears down a local install: stop the runtime, remove any
|
|
139
|
+
// legacy privileged mesh service (elevated), then delete the install directory.
|
|
140
|
+
export async function uninstallRuntime(options) {
|
|
141
|
+
const prefix = safeDirectory(options.prefix ?? defaultPrefix())
|
|
142
|
+
if (!existsSync(prefix)) {
|
|
143
|
+
console.log(`Vantaloom is not installed at ${prefix}`)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
console.log(`Uninstalling Vantaloom from ${prefix} ...`)
|
|
147
|
+
|
|
148
|
+
// 1. Stop the runtime (api/agent/web/tray). Best-effort.
|
|
149
|
+
const ctlBin = path.join(prefix, "bin", binaryName("vantaloomctl"))
|
|
150
|
+
if (existsSync(ctlBin)) {
|
|
151
|
+
spawnSync(ctlBin, ["stop", "--prefix", prefix], { stdio: "inherit", windowsHide: true })
|
|
152
|
+
}
|
|
153
|
+
killTrayProcess(prefix)
|
|
154
|
+
|
|
155
|
+
// 2. Remove a legacy privileged mesh service if this install predates 0.13
|
|
156
|
+
// (releases the TUN adapter + file locks). A no-op on any install that
|
|
157
|
+
// never had one (see legacy-cleanup.mjs's removeMeshService).
|
|
158
|
+
removeMeshService(prefix, options)
|
|
159
|
+
|
|
160
|
+
// 2b. Remove the login-autostart entry (lives outside the install dir).
|
|
161
|
+
disableRuntimeAutostart()
|
|
162
|
+
|
|
163
|
+
// 3. Delete the install directory.
|
|
164
|
+
try {
|
|
165
|
+
const parent = path.dirname(prefix)
|
|
166
|
+
removeKnownPath(prefix, parent)
|
|
167
|
+
console.log(`removed ${prefix}`)
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.warn(` could not remove ${prefix}: ${error.message}`)
|
|
170
|
+
console.warn(" some files may still be locked; re-run after closing Vantaloom processes.")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log("Vantaloom uninstalled.")
|
|
174
|
+
console.log("note: the install dir was left out of PATH edits; remove the bin/ entry from your shell profile if you added it.")
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function runCtl(command, options) {
|
|
178
|
+
const prefix = safeDirectory(options.prefix ?? defaultPrefix())
|
|
179
|
+
const ctl = path.join(prefix, "bin", binaryName("vantaloomctl"))
|
|
180
|
+
if (!existsSync(ctl)) {
|
|
181
|
+
throw new Error(`missing installed runtime at ${prefix}; run "vantaloom install" first`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const args = [command, "--prefix", prefix]
|
|
185
|
+
if (options.component) {
|
|
186
|
+
args.push("--component", options.component)
|
|
187
|
+
}
|
|
188
|
+
run(ctl, args)
|
|
189
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs"
|
|
2
|
+
import { cp } from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { platformId, removeKnownPath, runPnpm } from "./platform.mjs"
|
|
5
|
+
import {
|
|
6
|
+
buildGo,
|
|
7
|
+
copyStaticWeb,
|
|
8
|
+
copyCliDirectory,
|
|
9
|
+
writeBuildManifest,
|
|
10
|
+
writeRuntimePackageMetadata,
|
|
11
|
+
runtimeConfigFromSource,
|
|
12
|
+
gitVersion,
|
|
13
|
+
npmPackageVersion,
|
|
14
|
+
} from "./install.mjs"
|
|
15
|
+
|
|
16
|
+
export async function buildRuntimePackage(sourceRoot, packageRoot, options) {
|
|
17
|
+
// Every build — npm release AND local/dev — stamps VERSION/manifest with the
|
|
18
|
+
// npm package version. There is exactly ONE product version (the npm semver);
|
|
19
|
+
// the git hash is recorded separately as manifest `commit` for diagnostics.
|
|
20
|
+
// History: pre-0.13.5 local builds stamped the git hash into VERSION, which
|
|
21
|
+
// split the version universe in two (the desktop shell read VERSION, the
|
|
22
|
+
// settings page read cli/package.json) and broke update prompts both ways.
|
|
23
|
+
const version = npmPackageVersion(sourceRoot)
|
|
24
|
+
const commit = gitVersion(sourceRoot)
|
|
25
|
+
const platform = options.target ?? platformId()
|
|
26
|
+
const buildBin = path.join(packageRoot, "bin")
|
|
27
|
+
const buildWeb = path.join(packageRoot, "web")
|
|
28
|
+
|
|
29
|
+
removeKnownPath(packageRoot, path.dirname(packageRoot))
|
|
30
|
+
mkdirSync(buildBin, { recursive: true })
|
|
31
|
+
|
|
32
|
+
buildGo(sourceRoot, buildBin, "vantaloom-api", platform)
|
|
33
|
+
buildGo(sourceRoot, buildBin, "vantaloom-agent", platform)
|
|
34
|
+
buildGo(sourceRoot, buildBin, "vantaloomctl", platform)
|
|
35
|
+
// Official "Vantaloom 浏览器" extension sidecar. Pure-Go (chromedp/CDP, no
|
|
36
|
+
// CGO), cross-compiles for every target. Ships dormant: it's only started by
|
|
37
|
+
// the api when the user installs+enables the browser extension (the heavy
|
|
38
|
+
// Chromium binary is downloaded on demand, NOT bundled here).
|
|
39
|
+
buildGo(sourceRoot, buildBin, "vantaloom-browser", platform)
|
|
40
|
+
// The Windows system-tray app (vantaloom-tray) was removed — it crash-looped
|
|
41
|
+
// on some Windows 11 builds and got respawned until OOM. No longer built.
|
|
42
|
+
|
|
43
|
+
// Bundle the Obscura headless browser (the official "Vantaloom 浏览器" engine,
|
|
44
|
+
// driven by the vantaloom-browser sidecar over CDP) for the target platform.
|
|
45
|
+
await copyObscura(sourceRoot, buildBin, platform)
|
|
46
|
+
|
|
47
|
+
if (options.buildWeb) {
|
|
48
|
+
runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await copyStaticWeb(sourceRoot, buildWeb)
|
|
52
|
+
await copyCliDirectory(path.join(packageRoot, "cli"), sourceRoot, runtimeConfigFromSource(sourceRoot))
|
|
53
|
+
writeBuildManifest(packageRoot, version, platform, commit)
|
|
54
|
+
|
|
55
|
+
return { platform, version }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// copyObscura bundles the vendored Obscura headless-browser binaries
|
|
59
|
+
// (obscura + obscura-worker, .exe on Windows) for the target platform into the
|
|
60
|
+
// package bin/ dir. Obscura is the official "Vantaloom 浏览器" engine: a
|
|
61
|
+
// CDP-compatible, V8-backed headless browser the vantaloom-browser sidecar
|
|
62
|
+
// launches via `obscura serve` and drives over CDP. Warns + skips if the
|
|
63
|
+
// vendor dir is absent (a cleaned vendor/ silently ships a runtime with no
|
|
64
|
+
// browser engine).
|
|
65
|
+
export async function copyObscura(sourceRoot, buildBin, platform) {
|
|
66
|
+
const vendorMap = {
|
|
67
|
+
"win32-x64": "windows-x86_64",
|
|
68
|
+
"darwin-arm64": "macos-aarch64",
|
|
69
|
+
"linux-x64": "linux-x86_64",
|
|
70
|
+
}
|
|
71
|
+
const vendorName = vendorMap[platform]
|
|
72
|
+
if (!vendorName) {
|
|
73
|
+
console.warn(` warning: no Obscura mapping for ${platform}; browser engine disabled for this platform`)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
const srcDir = path.join(sourceRoot, "vendor", "obscura", vendorName)
|
|
77
|
+
if (!existsSync(srcDir)) {
|
|
78
|
+
console.warn(` warning: Obscura binaries not found at ${srcDir}; run vendor download first`)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const isWin = platform.startsWith("win32")
|
|
82
|
+
const exe = isWin ? ".exe" : ""
|
|
83
|
+
// Both binaries MUST ship together: obscura is the CLI/CDP server, obscura-worker
|
|
84
|
+
// is the V8 render worker it spawns. Without the worker, `obscura serve` cannot
|
|
85
|
+
// open pages.
|
|
86
|
+
const files = [`obscura${exe}`, `obscura-worker${exe}`]
|
|
87
|
+
let copied = 0
|
|
88
|
+
for (const f of files) {
|
|
89
|
+
const src = path.join(srcDir, f)
|
|
90
|
+
if (!existsSync(src)) {
|
|
91
|
+
console.warn(` warning: Obscura file missing: ${f}`)
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
await cp(src, path.join(buildBin, f), { force: true })
|
|
95
|
+
copied++
|
|
96
|
+
}
|
|
97
|
+
console.log(` bundled Obscura ${vendorName} (${copied} files)`)
|
|
98
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
} from "node:fs"
|
|
7
|
+
import { writeFile } from "node:fs/promises"
|
|
8
|
+
import { execFileSync } from "node:child_process"
|
|
9
|
+
import os from "node:os"
|
|
10
|
+
import path from "node:path"
|
|
11
|
+
|
|
12
|
+
// binaryName returns the OS-appropriate binary file name.
|
|
13
|
+
// NOTE: mirrored in Go at apps/desktop/internal/runtime/
|
|
14
|
+
export function binaryName(name, targetPlatform) {
|
|
15
|
+
const isWindows = targetPlatform
|
|
16
|
+
? targetPlatform.startsWith("win32")
|
|
17
|
+
: process.platform === "win32"
|
|
18
|
+
return isWindows ? `${name}.exe` : name
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// platformId returns the current platform identifier string.
|
|
22
|
+
// NOTE: mirrored in Go at apps/desktop/internal/runtime/
|
|
23
|
+
export function platformId() {
|
|
24
|
+
return `${process.platform}-${process.arch}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// runtimePackageName returns the npm package name for a given platform.
|
|
28
|
+
// NOTE: mirrored in Go at apps/desktop/internal/runtime/
|
|
29
|
+
export function runtimePackageName(platform) {
|
|
30
|
+
return `@vantaloom/runtime-${platform}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function parsePlatformId(platform) {
|
|
34
|
+
const parts = platform.split("-")
|
|
35
|
+
const cpu = parts.pop()
|
|
36
|
+
const runtimeOS = parts.join("-")
|
|
37
|
+
if (!runtimeOS || !cpu) {
|
|
38
|
+
throw new Error(`invalid platform id: ${platform}`)
|
|
39
|
+
}
|
|
40
|
+
return { os: runtimeOS, cpu }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// psQuote wraps a value as a PowerShell single-quoted string literal.
|
|
44
|
+
// NOTE: mirrored in Go at apps/desktop/internal/runtime/
|
|
45
|
+
export function psQuote(value) {
|
|
46
|
+
return "'" + String(value).replace(/'/g, "''") + "'"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// defaultPrefix returns the default install prefix for the current platform.
|
|
50
|
+
// NOTE: mirrored in Go at apps/desktop/internal/runtime/
|
|
51
|
+
export function defaultPrefix() {
|
|
52
|
+
if (process.env.VANTALOOM_HOME) {
|
|
53
|
+
return process.env.VANTALOOM_HOME
|
|
54
|
+
}
|
|
55
|
+
if (process.platform === "win32") {
|
|
56
|
+
// Prefer D: (the historical default) but fall back to C: when this machine
|
|
57
|
+
// has no D: drive. Keep in sync with the desktop shell's DefaultPrefix().
|
|
58
|
+
return existsSync("D:\\") ? "D:\\Vantaloom" : "C:\\Vantaloom"
|
|
59
|
+
}
|
|
60
|
+
if (process.platform === "darwin") {
|
|
61
|
+
return path.join(os.homedir(), "Applications", "Vantaloom")
|
|
62
|
+
}
|
|
63
|
+
return path.join(os.homedir(), ".local", "vantaloom")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function displayCommand(prefix) {
|
|
67
|
+
if (process.platform === "win32") {
|
|
68
|
+
return path.join(prefix, "vantaloom.cmd")
|
|
69
|
+
}
|
|
70
|
+
return path.join(prefix, "vantaloom")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function readJSONIfExists(filePath) {
|
|
74
|
+
if (!existsSync(filePath)) {
|
|
75
|
+
return {}
|
|
76
|
+
}
|
|
77
|
+
return JSON.parse(readFileSync(filePath, "utf8"))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function packageBasename(name) {
|
|
81
|
+
return name.split("/").pop() ?? name
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function normalizeRegistry(value) {
|
|
85
|
+
return value.replace(/\/+$/, "")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function toCamel(value) {
|
|
89
|
+
return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function safeDirectory(value) {
|
|
93
|
+
const full = path.resolve(value)
|
|
94
|
+
const parsed = path.parse(full)
|
|
95
|
+
if (full === parsed.root) {
|
|
96
|
+
throw new Error(`refusing to operate on unsafe directory: ${value}`)
|
|
97
|
+
}
|
|
98
|
+
return full
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function removeKnownPath(target, expectedParent) {
|
|
102
|
+
if (!existsSync(target)) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
const full = path.resolve(target)
|
|
106
|
+
const parent = path.resolve(expectedParent)
|
|
107
|
+
const prefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`
|
|
108
|
+
if (!full.startsWith(prefix)) {
|
|
109
|
+
throw new Error(`refusing to remove path outside expected parent: ${full}`)
|
|
110
|
+
}
|
|
111
|
+
rmSync(full, { recursive: true, force: true })
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function writeText(filePath, content) {
|
|
115
|
+
mkdirSync(path.dirname(filePath), { recursive: true })
|
|
116
|
+
await writeFile(filePath, content)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function run(command, args, options = {}) {
|
|
120
|
+
try {
|
|
121
|
+
execFileSync(command, args, { stdio: "inherit", windowsHide: true, ...options })
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error && typeof error.status === "number") {
|
|
124
|
+
throw new Error(`${command} exited with ${error.status}`)
|
|
125
|
+
}
|
|
126
|
+
throw error
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function runPnpm(args, options = {}) {
|
|
131
|
+
if (process.platform === "win32") {
|
|
132
|
+
run("cmd.exe", ["/d", "/s", "/c", "pnpm", ...args], options)
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
run("pnpm", args, options)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// runNpm invokes npm cross-platform. On Windows npm is `npm.cmd`, which
|
|
139
|
+
// execFileSync (used by run) can't resolve directly (ENOENT) — so wrap via
|
|
140
|
+
// cmd.exe, mirroring runPnpm.
|
|
141
|
+
export function runNpm(args, options = {}) {
|
|
142
|
+
if (process.platform === "win32") {
|
|
143
|
+
run("cmd.exe", ["/d", "/s", "/c", "npm", ...args], options)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
run("npm", args, options)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function platformToGoEnv(platform) {
|
|
150
|
+
const { os: runtimeOS, cpu } = parsePlatformId(platform)
|
|
151
|
+
const goosMap = { win32: "windows", linux: "linux", darwin: "darwin" }
|
|
152
|
+
const goarchMap = { x64: "amd64", arm64: "arm64" }
|
|
153
|
+
return {
|
|
154
|
+
GOOS: goosMap[runtimeOS] ?? runtimeOS,
|
|
155
|
+
GOARCH: goarchMap[cpu] ?? cpu,
|
|
156
|
+
CGO_ENABLED: "0",
|
|
157
|
+
}
|
|
158
|
+
}
|