@vantaloom/cli 0.14.18 → 0.15.30
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/bin/vantaloom.mjs +5 -1
- package/package.json +1 -1
- package/src/cli.mjs +58 -57
- package/src/lib/constants.mjs +3 -2
- package/src/lib/install.mjs +41 -57
- package/src/lib/legacy-cleanup.mjs +48 -0
- package/src/lib/lifecycle.mjs +2 -0
- package/src/lib/package.mjs +56 -98
- package/src/lib/registry.mjs +33 -111
package/bin/vantaloom.mjs
CHANGED
|
@@ -5,5 +5,9 @@ import { main } from "../src/cli.mjs"
|
|
|
5
5
|
main(process.argv.slice(2)).catch((error) => {
|
|
6
6
|
const message = error instanceof Error ? error.message : String(error)
|
|
7
7
|
console.error(`vantaloom: ${message}`)
|
|
8
|
-
process.exit(
|
|
8
|
+
// Set the code and let the loop drain instead of process.exit(): after any
|
|
9
|
+
// registry fetch, undici still has sockets mid-close, and killing the process
|
|
10
|
+
// then trips a libuv assertion on Windows ("UV_HANDLE_CLOSING"), which buries
|
|
11
|
+
// the real error under a crash dump and returns 0xC0000409 instead of 1.
|
|
12
|
+
process.exitCode = 1
|
|
9
13
|
})
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -10,8 +10,6 @@ import path from "node:path"
|
|
|
10
10
|
import {
|
|
11
11
|
cliRoot,
|
|
12
12
|
defaultNpmRegistry,
|
|
13
|
-
defaultRepo,
|
|
14
|
-
defaultReleaseTag,
|
|
15
13
|
fallbackNpmRegistries,
|
|
16
14
|
} from "./lib/constants.mjs"
|
|
17
15
|
import {
|
|
@@ -32,20 +30,15 @@ import {
|
|
|
32
30
|
findSourceRoot,
|
|
33
31
|
tryFindSourceRoot,
|
|
34
32
|
tryAssertSourceRoot,
|
|
35
|
-
gitRemoteUrl,
|
|
36
|
-
inferGitHubRepo,
|
|
37
33
|
readInstalledConfig,
|
|
38
34
|
writeRuntimePackageMetadata,
|
|
39
35
|
ensureInPath,
|
|
40
36
|
} from "./lib/install.mjs"
|
|
41
37
|
import { buildRuntimePackage } from "./lib/package.mjs"
|
|
42
38
|
import {
|
|
43
|
-
detectNpmRegistry,
|
|
44
39
|
resolveNpmPackageWithFallback,
|
|
45
40
|
downloadNpmTarball,
|
|
46
|
-
downloadReleaseAsset,
|
|
47
41
|
findExtractedNpmPackage,
|
|
48
|
-
findExtractedPackage,
|
|
49
42
|
} from "./lib/registry.mjs"
|
|
50
43
|
import {
|
|
51
44
|
runCtl,
|
|
@@ -97,8 +90,6 @@ export async function main(argv) {
|
|
|
97
90
|
await installFromPackage({ ...options, update: false })
|
|
98
91
|
} else if (shouldUseSourceInstall(options)) {
|
|
99
92
|
await installFromSource(options)
|
|
100
|
-
} else if (shouldUseReleaseSync(options)) {
|
|
101
|
-
await syncFromRelease(options, "install")
|
|
102
93
|
} else {
|
|
103
94
|
await syncFromNpmRegistry(options, "install")
|
|
104
95
|
}
|
|
@@ -108,8 +99,6 @@ export async function main(argv) {
|
|
|
108
99
|
await installFromPackage({ ...options, update: true })
|
|
109
100
|
} else if (shouldUseSourceInstall(options)) {
|
|
110
101
|
await installFromSource({ ...options, update: true })
|
|
111
|
-
} else if (shouldUseReleaseSync(options)) {
|
|
112
|
-
await syncFromRelease(options, "update")
|
|
113
102
|
} else {
|
|
114
103
|
await syncFromNpmRegistry(options, "update")
|
|
115
104
|
}
|
|
@@ -227,7 +216,12 @@ async function syncFromNpmRegistry(options, action) {
|
|
|
227
216
|
const runtimePackage = options.runtimePackage || runtimePackageName(platformId())
|
|
228
217
|
const runtimeVersion = options.runtimeVersion || installedConfig.runtimeVersion || "latest"
|
|
229
218
|
const explicitRegistry = options.npmRegistry || installedConfig.npmRegistry
|
|
230
|
-
|
|
219
|
+
// Resolve against npmjs.org — the registry the runtime packages are published
|
|
220
|
+
// to. Ambient mirror config (npm_config_registry / .npmrc) is deliberately NOT
|
|
221
|
+
// adopted: mirrors lag on dist-tags (npmmirror still served "latest" = 0.6.12
|
|
222
|
+
// months after 0.15.x shipped), and trusting one turns an update into a silent
|
|
223
|
+
// rollback. Mirrors stay in the fallback chain for unreachable-network cases.
|
|
224
|
+
const registry = normalizeRegistry(explicitRegistry || defaultNpmRegistry)
|
|
231
225
|
const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-npm-"))
|
|
232
226
|
|
|
233
227
|
try {
|
|
@@ -240,6 +234,7 @@ async function syncFromNpmRegistry(options, action) {
|
|
|
240
234
|
name: runtimePackage,
|
|
241
235
|
version: runtimeVersion,
|
|
242
236
|
})
|
|
237
|
+
assertNotDowngrade({ prefix, resolved, runtimePackage, options, action })
|
|
243
238
|
await downloadNpmTarball({
|
|
244
239
|
tarballUrl: resolved.tarball,
|
|
245
240
|
target: archive,
|
|
@@ -268,56 +263,63 @@ async function syncFromNpmRegistry(options, action) {
|
|
|
268
263
|
}
|
|
269
264
|
}
|
|
270
265
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
|
|
266
|
+
// A registry mirror can answer "latest" with a version far older than the one
|
|
267
|
+
// already installed (npmmirror did exactly that: 0.6.12 long after 0.15.x
|
|
268
|
+
// shipped), turning an update into a silent rollback. Refuse it unless the
|
|
269
|
+
// caller asked for that exact version or opted in explicitly.
|
|
270
|
+
function assertNotDowngrade({ prefix, resolved, runtimePackage, options, action }) {
|
|
271
|
+
if (action !== "update" || options.runtimeVersion || options.allowDowngrade) {
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
const installed = readRuntimeVersion(prefix)
|
|
275
|
+
if (!isOlderVersion(resolved.version, installed)) {
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
throw new Error(
|
|
279
|
+
`${resolved.registry} offers ${runtimePackage}@${resolved.version}, older than the installed ${installed}.\n` +
|
|
280
|
+
` A mirror whose dist-tags lag behind npmjs.org is the usual cause.\n` +
|
|
281
|
+
` Retry with --npm-registry https://registry.npmjs.org/, or pass --allow-downgrade to install it anyway.`
|
|
282
|
+
)
|
|
283
|
+
}
|
|
281
284
|
|
|
285
|
+
// Installed runtime version ("" when absent/unreadable — never block on it).
|
|
286
|
+
function readRuntimeVersion(prefix) {
|
|
282
287
|
try {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
repo,
|
|
289
|
-
releaseTag,
|
|
290
|
-
assetName: `vantaloom-${platformId()}.tar.gz`,
|
|
291
|
-
target: archive,
|
|
292
|
-
token: options.githubToken,
|
|
293
|
-
})
|
|
294
|
-
run("tar", ["-xzf", archive, "-C", extractRoot])
|
|
295
|
-
|
|
296
|
-
const packageRoot = findExtractedPackage(extractRoot, platformId())
|
|
297
|
-
const version = await applyPackage(packageRoot, prefix, {
|
|
298
|
-
noStart: options.noStart,
|
|
299
|
-
sourceRoot: installedConfig.sourceRoot,
|
|
300
|
-
update: action === "update",
|
|
301
|
-
})
|
|
288
|
+
return readFileSync(path.join(prefix, "VERSION"), "utf8").trim()
|
|
289
|
+
} catch {
|
|
290
|
+
return ""
|
|
291
|
+
}
|
|
292
|
+
}
|
|
302
293
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
294
|
+
// candidate < installed on the X.Y.Z triple (a -/+ suffix on patch is ignored).
|
|
295
|
+
// Non-semver on either side → false: never block a locally built or unversioned
|
|
296
|
+
// install from being replaced.
|
|
297
|
+
function isOlderVersion(candidate, installed) {
|
|
298
|
+
const parse = (value) => {
|
|
299
|
+
const parts = String(value).split(".")
|
|
300
|
+
if (parts.length < 3) {
|
|
301
|
+
return null
|
|
302
|
+
}
|
|
303
|
+
const triple = [parts[0], parts[1], parts.slice(2).join(".").split(/[-+]/)[0]].map(Number)
|
|
304
|
+
return triple.every(Number.isInteger) ? triple : null
|
|
305
|
+
}
|
|
306
|
+
const found = parse(candidate)
|
|
307
|
+
const current = parse(installed)
|
|
308
|
+
if (!found || !current) {
|
|
309
|
+
return false
|
|
310
|
+
}
|
|
311
|
+
for (let index = 0; index < 3; index += 1) {
|
|
312
|
+
if (found[index] !== current[index]) {
|
|
313
|
+
return found[index] < current[index]
|
|
314
|
+
}
|
|
310
315
|
}
|
|
316
|
+
return false
|
|
311
317
|
}
|
|
312
318
|
|
|
313
319
|
function shouldUseSourceInstall(options) {
|
|
314
320
|
return Boolean(options.local || options.source || options.buildWeb)
|
|
315
321
|
}
|
|
316
322
|
|
|
317
|
-
function shouldUseReleaseSync(options) {
|
|
318
|
-
return Boolean(options.repo || options.remote || options.releaseTag || options.githubToken)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
323
|
function printPaths(options) {
|
|
322
324
|
const sourceRoot = findSourceRoot(options.source)
|
|
323
325
|
const prefix = safeDirectory(options.prefix ?? defaultPrefix())
|
|
@@ -338,10 +340,6 @@ function parseOptions(args) {
|
|
|
338
340
|
case "component":
|
|
339
341
|
case "package":
|
|
340
342
|
case "output":
|
|
341
|
-
case "repo":
|
|
342
|
-
case "remote":
|
|
343
|
-
case "release-tag":
|
|
344
|
-
case "github-token":
|
|
345
343
|
case "runtime-package":
|
|
346
344
|
case "runtime-version":
|
|
347
345
|
case "npm-registry":
|
|
@@ -380,6 +378,9 @@ function parseOptions(args) {
|
|
|
380
378
|
case "no-strict-ssl":
|
|
381
379
|
options.noStrictSsl = true
|
|
382
380
|
break
|
|
381
|
+
case "allow-downgrade":
|
|
382
|
+
options.allowDowngrade = true
|
|
383
|
+
break
|
|
383
384
|
default:
|
|
384
385
|
throw new Error(`unknown option --${key}`)
|
|
385
386
|
}
|
|
@@ -393,7 +394,7 @@ function printHelp() {
|
|
|
393
394
|
Usage:
|
|
394
395
|
vantaloom install [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--package <dir>] [--no-start]
|
|
395
396
|
vantaloom install --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
|
|
396
|
-
vantaloom update [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--no-start]
|
|
397
|
+
vantaloom update [--prefix <dir>] [--runtime-version <version>] [--npm-registry <url>] [--no-start] [--allow-downgrade]
|
|
397
398
|
vantaloom update --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
|
|
398
399
|
vantaloom uninstall [--prefix <dir>]
|
|
399
400
|
vantaloom package [--source <repo>] [--output <dir>] [--build-web] [--archive] [--npm-package] [--target <platform>]
|
package/src/lib/constants.mjs
CHANGED
|
@@ -4,8 +4,9 @@ import { fileURLToPath } from "node:url"
|
|
|
4
4
|
export const cliRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..", "..")
|
|
5
5
|
export const repoCandidate = path.resolve(cliRoot, "..", "..")
|
|
6
6
|
export const installedConfigPath = path.join(cliRoot, "config.json")
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Runtime packages are published to npmjs.org (see docs/deployment.md). The
|
|
8
|
+
// mirror is a LAST-RESORT network fallback only — its dist-tags lag, so it must
|
|
9
|
+
// never be the primary source for resolving "latest".
|
|
9
10
|
export const defaultNpmRegistry = "https://registry.npmjs.org"
|
|
10
11
|
export const fallbackNpmRegistries = [
|
|
11
12
|
"https://registry.npmmirror.com",
|
package/src/lib/install.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
rmSync,
|
|
11
11
|
writeFileSync,
|
|
12
12
|
} from "node:fs"
|
|
13
|
-
import { cp, writeFile } from "node:fs/promises"
|
|
13
|
+
import { cp, readdir, rm, writeFile } from "node:fs/promises"
|
|
14
14
|
import { spawnSync } from "node:child_process"
|
|
15
15
|
import os from "node:os"
|
|
16
16
|
import path from "node:path"
|
|
@@ -19,8 +19,6 @@ import {
|
|
|
19
19
|
cliRoot,
|
|
20
20
|
repoCandidate,
|
|
21
21
|
installedConfigPath,
|
|
22
|
-
defaultReleaseTag,
|
|
23
|
-
defaultRepo,
|
|
24
22
|
defaultNpmRegistry,
|
|
25
23
|
} from "./constants.mjs"
|
|
26
24
|
import {
|
|
@@ -36,6 +34,7 @@ import {
|
|
|
36
34
|
platformToGoEnv,
|
|
37
35
|
} from "./platform.mjs"
|
|
38
36
|
import {
|
|
37
|
+
killBrowserSidecarProcess,
|
|
39
38
|
killTrayProcess,
|
|
40
39
|
uninstallLegacyMeshOnce,
|
|
41
40
|
} from "./legacy-cleanup.mjs"
|
|
@@ -135,6 +134,10 @@ export async function applyPackage(packageRoot, prefix, options) {
|
|
|
135
134
|
// Kill lingering tray process that may hold locks on bin/ (older versions
|
|
136
135
|
// don't write tray.pid, so vantaloomctl stop won't find them).
|
|
137
136
|
killTrayProcess(prefix)
|
|
137
|
+
// 0.14.26: the browser moved into the official plugin — stop a pre-plugin
|
|
138
|
+
// install's orphaned vantaloom-browser sidecar + obscura engine and delete
|
|
139
|
+
// their stale bin/ binaries (nothing manages them anymore).
|
|
140
|
+
killBrowserSidecarProcess(prefix)
|
|
138
141
|
|
|
139
142
|
// Windows releases a stopped process's file handles asynchronously; copying
|
|
140
143
|
// bin/ the instant after `stop` can still hit the old exe's lock (EPERM). Give
|
|
@@ -262,7 +265,7 @@ export function readInstalledConfig(prefix) {
|
|
|
262
265
|
|
|
263
266
|
export function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
|
|
264
267
|
const merged = { ...packageConfig }
|
|
265
|
-
for (const key of ["sourceRoot", "
|
|
268
|
+
for (const key of ["sourceRoot", "runtimePackage", "runtimeVersion", "npmRegistry"]) {
|
|
266
269
|
if (!merged[key] && existingConfig[key]) {
|
|
267
270
|
merged[key] = existingConfig[key]
|
|
268
271
|
}
|
|
@@ -279,12 +282,6 @@ export function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
|
|
|
279
282
|
if (overrides.npmRegistry) {
|
|
280
283
|
merged.npmRegistry = overrides.npmRegistry
|
|
281
284
|
}
|
|
282
|
-
if (!merged.repo) {
|
|
283
|
-
merged.repo = defaultRepo
|
|
284
|
-
}
|
|
285
|
-
if (!merged.releaseTag) {
|
|
286
|
-
merged.releaseTag = defaultReleaseTag
|
|
287
|
-
}
|
|
288
285
|
// Always force runtimePackage to match the running platform — a cross-compiled
|
|
289
286
|
// package may carry a config for a different platform (e.g. win32 inside darwin).
|
|
290
287
|
merged.runtimePackage = runtimePackageName(platformId())
|
|
@@ -294,36 +291,25 @@ export function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
|
|
|
294
291
|
if (!merged.npmRegistry) {
|
|
295
292
|
merged.npmRegistry = defaultNpmRegistry
|
|
296
293
|
}
|
|
294
|
+
// Drop keys left by the removed GitHub-release channel (0.15.29). Nothing
|
|
295
|
+
// reads them any more; leaving them in config.json only invites the next
|
|
296
|
+
// reader to believe there is a second update channel.
|
|
297
|
+
for (const legacy of ["repo", "releaseTag", "remote"]) {
|
|
298
|
+
delete merged[legacy]
|
|
299
|
+
}
|
|
297
300
|
return merged
|
|
298
301
|
}
|
|
299
302
|
|
|
303
|
+
// npmRegistry is pinned to npmjs.org rather than sniffed from the ambient npm
|
|
304
|
+
// config: baking a mirror in here made every later `vantaloom update` treat it
|
|
305
|
+
// as an explicit choice (skipping the fallback chain) and resolve "latest" from
|
|
306
|
+
// dist-tags that lag months behind. Only --npm-registry should change it.
|
|
300
307
|
export function runtimeConfigFromSource(sourceRoot) {
|
|
301
|
-
const remote = gitRemoteUrl(sourceRoot)
|
|
302
|
-
const repo = inferGitHubRepo(remote) || defaultRepo
|
|
303
|
-
// Inline registry detection to avoid circular dep with registry.mjs
|
|
304
|
-
let npmRegistry = ""
|
|
305
|
-
if (process.env.NPM_CONFIG_REGISTRY) {
|
|
306
|
-
npmRegistry = process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY
|
|
307
|
-
} else if (process.env.npm_config_registry) {
|
|
308
|
-
npmRegistry = process.env.npm_config_registry
|
|
309
|
-
} else {
|
|
310
|
-
try {
|
|
311
|
-
const npmrcPath = path.join(os.homedir(), ".npmrc")
|
|
312
|
-
if (existsSync(npmrcPath)) {
|
|
313
|
-
const content = readFileSync(npmrcPath, "utf8")
|
|
314
|
-
const match = content.match(/^\s*registry\s*=\s*(.+)/m)
|
|
315
|
-
if (match) npmRegistry = match[1].trim()
|
|
316
|
-
}
|
|
317
|
-
} catch {}
|
|
318
|
-
}
|
|
319
308
|
return {
|
|
320
309
|
...(process.env.GITHUB_ACTIONS ? {} : { sourceRoot }),
|
|
321
|
-
...(remote ? { remote } : {}),
|
|
322
|
-
repo,
|
|
323
|
-
releaseTag: defaultReleaseTag,
|
|
324
310
|
runtimePackage: runtimePackageName(platformId()),
|
|
325
311
|
runtimeVersion: "latest",
|
|
326
|
-
npmRegistry:
|
|
312
|
+
npmRegistry: defaultNpmRegistry,
|
|
327
313
|
}
|
|
328
314
|
}
|
|
329
315
|
|
|
@@ -366,31 +352,6 @@ export function assertSourceRoot(sourceRoot) {
|
|
|
366
352
|
return sourceRoot
|
|
367
353
|
}
|
|
368
354
|
|
|
369
|
-
export function gitRemoteUrl(sourceRoot) {
|
|
370
|
-
const result = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
371
|
-
cwd: sourceRoot,
|
|
372
|
-
encoding: "utf8",
|
|
373
|
-
windowsHide: true,
|
|
374
|
-
})
|
|
375
|
-
if (result.status === 0) {
|
|
376
|
-
return result.stdout.trim()
|
|
377
|
-
}
|
|
378
|
-
return ""
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
export function inferGitHubRepo(remote) {
|
|
382
|
-
if (!remote) {
|
|
383
|
-
return ""
|
|
384
|
-
}
|
|
385
|
-
const normalized = remote.replace(/\.git$/, "")
|
|
386
|
-
const httpsMatch = normalized.match(/github\.com[:/]([^/]+\/[^/]+)$/)
|
|
387
|
-
if (httpsMatch) {
|
|
388
|
-
return httpsMatch[1]
|
|
389
|
-
}
|
|
390
|
-
const sshMatch = normalized.match(/^[^:]+:([^/]+\/[^/]+)$/)
|
|
391
|
-
return sshMatch?.[1] ?? ""
|
|
392
|
-
}
|
|
393
|
-
|
|
394
355
|
export async function copyStaticWeb(sourceRoot, buildWeb) {
|
|
395
356
|
const exportRoot = path.join(sourceRoot, "apps", "vantaloom", "out")
|
|
396
357
|
if (!existsSync(exportRoot)) {
|
|
@@ -401,6 +362,25 @@ export async function copyStaticWeb(sourceRoot, buildWeb) {
|
|
|
401
362
|
|
|
402
363
|
removeKnownPath(buildWeb, path.dirname(buildWeb))
|
|
403
364
|
await copyDir(exportRoot, buildWeb)
|
|
365
|
+
// Source maps never ship in a published runtime package. Next 不产浏览器
|
|
366
|
+
// source map,但它自带的 polyfill chunk 会漏一个 .map 进静态导出(0.15.16
|
|
367
|
+
// 实测:a6dad97d…js.map,内容是 Next 自己的 polyfill-nomodule)。那一个碰巧
|
|
368
|
+
// 只含第三方代码,但「碰巧无害」不是标准——公开 APK 的 web/ 门禁早就规定
|
|
369
|
+
// source map 一律不当无害编译产物(sourcesContent 能内嵌完整源码),npm 包
|
|
370
|
+
// 与 Docker 镜像理应同一条纪律。在打包出口统一剥掉,整类问题终结。
|
|
371
|
+
await removeFilesByExtension(buildWeb, ".map")
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function removeFilesByExtension(root, extension) {
|
|
375
|
+
const entries = await readdir(root, { withFileTypes: true })
|
|
376
|
+
for (const entry of entries) {
|
|
377
|
+
const full = path.join(root, entry.name)
|
|
378
|
+
if (entry.isDirectory()) {
|
|
379
|
+
await removeFilesByExtension(full, extension)
|
|
380
|
+
} else if (entry.name.endsWith(extension)) {
|
|
381
|
+
await rm(full, { force: true })
|
|
382
|
+
}
|
|
383
|
+
}
|
|
404
384
|
}
|
|
405
385
|
|
|
406
386
|
export async function copyCliDirectory(target, sourceRoot, config) {
|
|
@@ -528,6 +508,10 @@ export function buildGo(sourceRoot, buildBin, name, targetPlatform) {
|
|
|
528
508
|
let ldflags = "-s -w"
|
|
529
509
|
const args = ["build"]
|
|
530
510
|
args.push(
|
|
511
|
+
// -trimpath:不把构建机的绝对路径写进二进制。没有它,每个发布出去的
|
|
512
|
+
// 二进制里都是上千处 D:/Projects/…(0.15.16 实测 1954 处)——纯元数据
|
|
513
|
+
// 泄露(构建机目录结构),且路径逐台机器不同也破坏可复现构建。
|
|
514
|
+
"-trimpath",
|
|
531
515
|
"-ldflags", ldflags,
|
|
532
516
|
"-o", path.join(buildBin, `${name}${ext}`),
|
|
533
517
|
`./apps/api/cmd/${name}`
|
|
@@ -51,6 +51,54 @@ export function killTrayProcess(prefix) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// killBrowserSidecarProcess (0.14.26) stops a pre-plugin install's
|
|
55
|
+
// vantaloom-browser HTTP sidecar (port 8782) + its obscura engine processes,
|
|
56
|
+
// and deletes the stale bin/ binaries the runtime no longer ships. The browser
|
|
57
|
+
// moved into the official PLUGIN (@vantaloom/browser-plugin-*); without this,
|
|
58
|
+
// an updated runtime would leave the orphaned sidecar+engine running until
|
|
59
|
+
// reboot (nothing manages them anymore) and ~170MB of dead binaries in bin/.
|
|
60
|
+
// Best-effort + idempotent, mirrors killTrayProcess.
|
|
61
|
+
export function killBrowserSidecarProcess(prefix) {
|
|
62
|
+
if (process.platform === "win32") {
|
|
63
|
+
const pidFile = path.join(prefix, "runtime", "browser.pid")
|
|
64
|
+
if (existsSync(pidFile)) {
|
|
65
|
+
const pid = readFileSync(pidFile, "utf8").trim()
|
|
66
|
+
if (pid) {
|
|
67
|
+
spawnSync("taskkill", ["/T", "/F", "/PID", pid], { stdio: "ignore", windowsHide: true })
|
|
68
|
+
}
|
|
69
|
+
try { rmSync(pidFile, { force: true }) } catch {}
|
|
70
|
+
}
|
|
71
|
+
for (const image of ["vantaloom-browser.exe", "obscura.exe", "obscura-worker.exe"]) {
|
|
72
|
+
const result = spawnSync("tasklist", ["/FI", `IMAGENAME eq ${image}`, "/FO", "CSV", "/NH"], {
|
|
73
|
+
encoding: "utf8",
|
|
74
|
+
windowsHide: true,
|
|
75
|
+
})
|
|
76
|
+
if (result.stdout) {
|
|
77
|
+
for (const line of result.stdout.split("\n")) {
|
|
78
|
+
const match = line.match(/"[^"]+","(\d+)"/)
|
|
79
|
+
if (match) {
|
|
80
|
+
spawnSync("taskkill", ["/T", "/F", "/PID", match[1]], { stdio: "ignore", windowsHide: true })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
spawnSync("pkill", ["-f", "vantaloom-browser"], { stdio: "ignore", windowsHide: true })
|
|
87
|
+
spawnSync("pkill", ["-f", "obscura"], { stdio: "ignore", windowsHide: true })
|
|
88
|
+
}
|
|
89
|
+
// Stale files: the sidecar/engine binaries + its port file (bin/ copies are
|
|
90
|
+
// overlay-only on update, they'd linger forever otherwise).
|
|
91
|
+
const binDir = path.join(prefix, "bin")
|
|
92
|
+
for (const name of [
|
|
93
|
+
"vantaloom-browser.exe", "vantaloom-browser",
|
|
94
|
+
"obscura.exe", "obscura",
|
|
95
|
+
"obscura-worker.exe", "obscura-worker",
|
|
96
|
+
]) {
|
|
97
|
+
try { rmSync(path.join(binDir, name), { force: true }) } catch {}
|
|
98
|
+
}
|
|
99
|
+
try { rmSync(path.join(prefix, "runtime", "browser.port"), { force: true }) } catch {}
|
|
100
|
+
}
|
|
101
|
+
|
|
54
102
|
// meshServiceRunningOrInstalled reports whether the Windows service exists at
|
|
55
103
|
// all (running or stopped), so removeMeshService only prompts for elevation
|
|
56
104
|
// when there is actually something to remove.
|
package/src/lib/lifecycle.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "./platform.mjs"
|
|
22
22
|
import {
|
|
23
23
|
removeMeshService,
|
|
24
|
+
killBrowserSidecarProcess,
|
|
24
25
|
killTrayProcess,
|
|
25
26
|
} from "./legacy-cleanup.mjs"
|
|
26
27
|
|
|
@@ -151,6 +152,7 @@ export async function uninstallRuntime(options) {
|
|
|
151
152
|
spawnSync(ctlBin, ["stop", "--prefix", prefix], { stdio: "inherit", windowsHide: true })
|
|
152
153
|
}
|
|
153
154
|
killTrayProcess(prefix)
|
|
155
|
+
killBrowserSidecarProcess(prefix)
|
|
154
156
|
|
|
155
157
|
// 2. Remove a legacy privileged mesh service if this install predates 0.13
|
|
156
158
|
// (releases the TUN adapter + file locks). A no-op on any install that
|
package/src/lib/package.mjs
CHANGED
|
@@ -1,98 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
buildGo(sourceRoot, buildBin, "vantaloom-
|
|
33
|
-
buildGo(sourceRoot, buildBin, "
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
// the
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
// The Windows system-tray app (vantaloom-tray) was removed
|
|
41
|
-
// on some Windows 11 builds
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
}
|
|
1
|
+
import { mkdirSync } from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { platformId, removeKnownPath, runPnpm } from "./platform.mjs"
|
|
4
|
+
import {
|
|
5
|
+
buildGo,
|
|
6
|
+
copyStaticWeb,
|
|
7
|
+
copyCliDirectory,
|
|
8
|
+
writeBuildManifest,
|
|
9
|
+
writeRuntimePackageMetadata,
|
|
10
|
+
runtimeConfigFromSource,
|
|
11
|
+
gitVersion,
|
|
12
|
+
npmPackageVersion,
|
|
13
|
+
} from "./install.mjs"
|
|
14
|
+
|
|
15
|
+
export async function buildRuntimePackage(sourceRoot, packageRoot, options) {
|
|
16
|
+
// Every build — npm release AND local/dev — stamps VERSION/manifest with the
|
|
17
|
+
// npm package version. There is exactly ONE product version (the npm semver);
|
|
18
|
+
// the git hash is recorded separately as manifest `commit` for diagnostics.
|
|
19
|
+
// History: pre-0.13.5 local builds stamped the git hash into VERSION, which
|
|
20
|
+
// split the version universe in two (the desktop shell read VERSION, the
|
|
21
|
+
// settings page read cli/package.json) and broke update prompts both ways.
|
|
22
|
+
const version = npmPackageVersion(sourceRoot)
|
|
23
|
+
const commit = gitVersion(sourceRoot)
|
|
24
|
+
const platform = options.target ?? platformId()
|
|
25
|
+
const buildBin = path.join(packageRoot, "bin")
|
|
26
|
+
const buildWeb = path.join(packageRoot, "web")
|
|
27
|
+
|
|
28
|
+
removeKnownPath(packageRoot, path.dirname(packageRoot))
|
|
29
|
+
mkdirSync(buildBin, { recursive: true })
|
|
30
|
+
|
|
31
|
+
buildGo(sourceRoot, buildBin, "vantaloom-api", platform)
|
|
32
|
+
buildGo(sourceRoot, buildBin, "vantaloom-agent", platform)
|
|
33
|
+
buildGo(sourceRoot, buildBin, "vantaloomctl", platform)
|
|
34
|
+
// vantaloom-mcp:跨机执行信道的 MCP 服务端(agent-bridge)。随 runtime bin 分发,
|
|
35
|
+
// 外部 agent(Claude Code / Codex / Cursor)配置指向它即可驱动整个组网。
|
|
36
|
+
buildGo(sourceRoot, buildBin, "vantaloom-mcp", platform)
|
|
37
|
+
// 0.14.26: the browser moved into the official optional PLUGIN
|
|
38
|
+
// (@vantaloom/browser-plugin-<platform>, built by scripts/build-browser-plugin.ps1) —
|
|
39
|
+
// the runtime no longer builds the vantaloom-browser sidecar nor bundles the
|
|
40
|
+
// Obscura engine. The Windows system-tray app (vantaloom-tray) was removed
|
|
41
|
+
// earlier — it crash-looped on some Windows 11 builds. Neither is built here.
|
|
42
|
+
|
|
43
|
+
if (options.buildWeb) {
|
|
44
|
+
runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await copyStaticWeb(sourceRoot, buildWeb)
|
|
48
|
+
await copyCliDirectory(path.join(packageRoot, "cli"), sourceRoot, runtimeConfigFromSource(sourceRoot))
|
|
49
|
+
writeBuildManifest(packageRoot, version, platform, commit)
|
|
50
|
+
|
|
51
|
+
return { platform, version }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// copyObscura was removed in 0.14.26: the Obscura engine ships inside the
|
|
55
|
+
// official browser PLUGIN packages (scripts/build-browser-plugin.ps1), not the
|
|
56
|
+
// runtime.
|
package/src/lib/registry.mjs
CHANGED
|
@@ -1,48 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
existsSync,
|
|
3
|
-
readFileSync,
|
|
4
3
|
readdirSync,
|
|
5
4
|
} from "node:fs"
|
|
6
5
|
import { writeFile } from "node:fs/promises"
|
|
7
|
-
import os from "node:os"
|
|
8
6
|
import path from "node:path"
|
|
9
7
|
|
|
10
|
-
export function detectNpmRegistry() {
|
|
11
|
-
// 1. NPM_CONFIG_REGISTRY env var (highest priority, set by npm/npx when running)
|
|
12
|
-
if (process.env.NPM_CONFIG_REGISTRY) {
|
|
13
|
-
return process.env.npm_config_registry || process.env.NPM_CONFIG_REGISTRY
|
|
14
|
-
}
|
|
15
|
-
// npm also sets the lowercase variant
|
|
16
|
-
if (process.env.npm_config_registry) {
|
|
17
|
-
return process.env.npm_config_registry
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 2. Read user .npmrc
|
|
21
|
-
try {
|
|
22
|
-
const npmrcPaths = [
|
|
23
|
-
path.join(os.homedir(), ".npmrc"),
|
|
24
|
-
]
|
|
25
|
-
// Also check project-level .npmrc
|
|
26
|
-
const localNpmrc = path.resolve(".npmrc")
|
|
27
|
-
if (localNpmrc !== npmrcPaths[0]) {
|
|
28
|
-
npmrcPaths.unshift(localNpmrc)
|
|
29
|
-
}
|
|
30
|
-
for (const npmrcPath of npmrcPaths) {
|
|
31
|
-
if (existsSync(npmrcPath)) {
|
|
32
|
-
const content = readFileSync(npmrcPath, "utf8")
|
|
33
|
-
const match = content.match(/^\s*registry\s*=\s*(.+)/m)
|
|
34
|
-
if (match) {
|
|
35
|
-
return match[1].trim()
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
} catch {
|
|
40
|
-
// Ignore .npmrc read errors
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return ""
|
|
44
|
-
}
|
|
45
|
-
|
|
46
8
|
export async function resolveNpmPackageWithFallback({ registries, name, version }) {
|
|
47
9
|
const errors = []
|
|
48
10
|
for (const registry of registries) {
|
|
@@ -133,91 +95,51 @@ export async function resolveNpmPackage({ registry, name, version }) {
|
|
|
133
95
|
}
|
|
134
96
|
}
|
|
135
97
|
|
|
98
|
+
// The runtime tarball is tens of MB (Go binaries + the exported web bundle), so
|
|
99
|
+
// the budget has to cover a slow link end to end — the old 120s cap aborted
|
|
100
|
+
// mid-body on ordinary connections and made updates fail for anyone far from
|
|
101
|
+
// npmjs.org.
|
|
102
|
+
const TARBALL_DOWNLOAD_TIMEOUT_MS = 15 * 60 * 1000
|
|
103
|
+
|
|
136
104
|
export async function downloadNpmTarball({ tarballUrl, target, packageName, version }) {
|
|
137
105
|
console.log(` downloading ${packageName}@${version}...`)
|
|
138
|
-
|
|
106
|
+
// One deadline for headers AND body: the abort fires during arrayBuffer() far
|
|
107
|
+
// more often than during fetch(), so both live inside the same try — otherwise
|
|
108
|
+
// the failure escapes as a bare "The operation was aborted due to timeout"
|
|
109
|
+
// with no hint of what was being downloaded or what to do about it.
|
|
139
110
|
try {
|
|
140
|
-
response = await fetch(tarballUrl, {
|
|
111
|
+
const response = await fetch(tarballUrl, {
|
|
141
112
|
headers: {
|
|
142
113
|
"User-Agent": "vantaloom-cli",
|
|
143
114
|
},
|
|
144
|
-
signal: AbortSignal.timeout(
|
|
115
|
+
signal: AbortSignal.timeout(TARBALL_DOWNLOAD_TIMEOUT_MS),
|
|
145
116
|
})
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const detail = await response.text().catch(() => "")
|
|
119
|
+
throw new Error(`failed to download ${packageName}@${version}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
|
|
120
|
+
}
|
|
121
|
+
const buffer = Buffer.from(await response.arrayBuffer())
|
|
122
|
+
await writeFile(target, buffer)
|
|
123
|
+
console.log(` downloaded ${(buffer.length / 1024 / 1024).toFixed(1)} MB`)
|
|
146
124
|
} catch (error) {
|
|
125
|
+
if (error instanceof Error && error.message.startsWith("failed to download")) {
|
|
126
|
+
throw error
|
|
127
|
+
}
|
|
147
128
|
const causeCode = error?.cause?.code || ""
|
|
148
|
-
const causeMsg = causeCode || error?.cause?.message || error
|
|
129
|
+
const causeMsg = causeCode || error?.cause?.message || error?.message || ""
|
|
149
130
|
const isSsl = causeCode.includes("CERT") || causeCode === "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
|
|
150
|
-
const
|
|
131
|
+
const isTimeout = error?.name === "TimeoutError" || /aborted due to timeout/i.test(String(error?.message))
|
|
132
|
+
let hint = ""
|
|
133
|
+
if (isSsl) {
|
|
134
|
+
hint = `\n Fix: run with --no-strict-ssl`
|
|
135
|
+
} else if (isTimeout) {
|
|
136
|
+
hint =
|
|
137
|
+
`\n The transfer exceeded ${TARBALL_DOWNLOAD_TIMEOUT_MS / 60000} minutes.` +
|
|
138
|
+
`\n Retry, or fetch the same version from a closer mirror:` +
|
|
139
|
+
`\n vantaloom update --runtime-version ${version} --npm-registry https://registry.npmmirror.com/`
|
|
140
|
+
}
|
|
151
141
|
throw new Error(`failed to download ${packageName}@${version} from ${tarballUrl}: ${causeMsg}${hint}`)
|
|
152
142
|
}
|
|
153
|
-
if (!response.ok) {
|
|
154
|
-
const detail = await response.text().catch(() => "")
|
|
155
|
-
throw new Error(`failed to download ${packageName}@${version}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const buffer = Buffer.from(await response.arrayBuffer())
|
|
159
|
-
await writeFile(target, buffer)
|
|
160
|
-
console.log(` downloaded ${(buffer.length / 1024 / 1024).toFixed(1)} MB`)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export async function downloadReleaseAsset({ repo, releaseTag, assetName, target, token }) {
|
|
164
|
-
const releaseUrl = `https://api.github.com/repos/${repo}/releases/tags/${releaseTag}`
|
|
165
|
-
const headers = {
|
|
166
|
-
"User-Agent": "vantaloom-cli",
|
|
167
|
-
}
|
|
168
|
-
const githubToken = token || process.env.VANTALOOM_GITHUB_TOKEN || process.env.GITHUB_TOKEN
|
|
169
|
-
if (githubToken) {
|
|
170
|
-
headers.Authorization = `Bearer ${githubToken}`
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const releaseResponse = await fetch(releaseUrl, { headers })
|
|
174
|
-
if (!releaseResponse.ok) {
|
|
175
|
-
const detail = await releaseResponse.text().catch(() => "")
|
|
176
|
-
const authHint = releaseResponse.status === 404 || releaseResponse.status === 403
|
|
177
|
-
? " If the repository is private, set VANTALOOM_GITHUB_TOKEN to a GitHub token that can read releases."
|
|
178
|
-
: ""
|
|
179
|
-
throw new Error(`failed to inspect ${repo}@${releaseTag}: HTTP ${releaseResponse.status}.${authHint}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const release = await releaseResponse.json()
|
|
183
|
-
const asset = release.assets?.find((asset) => asset.name === assetName)
|
|
184
|
-
if (!asset) {
|
|
185
|
-
const names = release.assets?.map((asset) => asset.name).join(", ") || "none"
|
|
186
|
-
throw new Error(`missing release asset ${assetName} in ${repo}@${releaseTag}; available assets: ${names}`)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const response = await fetch(asset.url, {
|
|
190
|
-
headers: {
|
|
191
|
-
...headers,
|
|
192
|
-
Accept: "application/octet-stream",
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
if (!response.ok) {
|
|
196
|
-
const detail = await response.text().catch(() => "")
|
|
197
|
-
const authHint = response.status === 404 || response.status === 403
|
|
198
|
-
? " If the repository is private, set VANTALOOM_GITHUB_TOKEN to a GitHub token that can read releases."
|
|
199
|
-
: ""
|
|
200
|
-
throw new Error(`failed to download ${assetName} from ${repo}@${releaseTag}: HTTP ${response.status}.${authHint}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const buffer = Buffer.from(await response.arrayBuffer())
|
|
204
|
-
await writeFile(target, buffer)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function findExtractedPackage(extractRoot, platform) {
|
|
208
|
-
const expected = path.join(extractRoot, `vantaloom-${platform}`)
|
|
209
|
-
if (existsSync(expected)) {
|
|
210
|
-
return expected
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const directories = readdirSync(extractRoot, { withFileTypes: true })
|
|
214
|
-
.filter((entry) => entry.isDirectory())
|
|
215
|
-
.map((entry) => path.join(extractRoot, entry.name))
|
|
216
|
-
if (directories.length === 1) {
|
|
217
|
-
return directories[0]
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
throw new Error(`could not find extracted Vantaloom package in ${extractRoot}`)
|
|
221
143
|
}
|
|
222
144
|
|
|
223
145
|
export function findExtractedNpmPackage(extractRoot) {
|