@vantaloom/cli 0.1.0

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/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # Vantaloom CLI
2
+
3
+ Command-line manager for installing, updating, starting, stopping, and inspecting a local Vantaloom runtime.
4
+
5
+ ```sh
6
+ npm install -g @vantaloom/cli
7
+ vantaloom install
8
+ vantaloom status
9
+ vantaloom update
10
+ ```
11
+
12
+ The CLI downloads platform-specific runtime packages produced by the Vantaloom GitHub CI pipeline.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../src/cli.mjs"
4
+
5
+ main(process.argv.slice(2)).catch((error) => {
6
+ const message = error instanceof Error ? error.message : String(error)
7
+ console.error(`vantaloom: ${message}`)
8
+ process.exit(1)
9
+ })
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@vantaloom/cli",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Vantaloom local runtime manager.",
7
+ "bin": {
8
+ "vantaloom": "bin/vantaloom.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "package.json",
14
+ "README.md"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "engines": {
20
+ "node": ">=20"
21
+ }
22
+ }
package/src/cli.mjs ADDED
@@ -0,0 +1,677 @@
1
+ import { execFileSync, spawnSync } from "node:child_process"
2
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
3
+ import { cp, writeFile } from "node:fs/promises"
4
+ import os from "node:os"
5
+ import path from "node:path"
6
+ import { fileURLToPath } from "node:url"
7
+
8
+ const cliRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..")
9
+ const repoCandidate = path.resolve(cliRoot, "..", "..")
10
+ const installedConfigPath = path.join(cliRoot, "config.json")
11
+ const defaultDistBranch = "vantaloom-dist"
12
+ const defaultRepo = "Timefiles404/Vantaloom-next"
13
+
14
+ const runtimePackages = [
15
+ "@next/env",
16
+ "@swc/helpers",
17
+ "baseline-browser-mapping",
18
+ "caniuse-lite",
19
+ "client-only",
20
+ "postcss",
21
+ "react",
22
+ "react-dom",
23
+ "scheduler",
24
+ "styled-jsx",
25
+ ]
26
+
27
+ export async function main(argv) {
28
+ const command = argv[0] ?? "help"
29
+ const options = parseOptions(argv.slice(1))
30
+
31
+ switch (command) {
32
+ case "install":
33
+ if (options.package) {
34
+ await installFromPackage({ ...options, update: false })
35
+ } else {
36
+ await installFromSource(options)
37
+ }
38
+ return
39
+ case "update":
40
+ if (options.package) {
41
+ await installFromPackage({ ...options, update: true })
42
+ } else if (shouldUseDistUpdate(options)) {
43
+ await updateFromDist(options)
44
+ } else {
45
+ await installFromSource({ ...options, update: true })
46
+ }
47
+ return
48
+ case "package":
49
+ await packageRuntime(options)
50
+ return
51
+ case "platform":
52
+ console.log(platformId())
53
+ return
54
+ case "start":
55
+ case "stop":
56
+ case "restart":
57
+ case "status":
58
+ case "ports":
59
+ runCtl(command, options)
60
+ return
61
+ case "path":
62
+ printPaths(options)
63
+ return
64
+ case "help":
65
+ case "-h":
66
+ case "--help":
67
+ printHelp()
68
+ return
69
+ default:
70
+ throw new Error(`unknown command "${command}"`)
71
+ }
72
+ }
73
+
74
+ async function installFromSource(options) {
75
+ const sourceRoot = findSourceRoot(options.source)
76
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
77
+ const buildRoot = path.join(sourceRoot, "artifacts", "local-install", platformId())
78
+
79
+ const { version } = await buildRuntimePackage(sourceRoot, buildRoot, {
80
+ buildWeb: options.buildWeb,
81
+ })
82
+
83
+ await applyPackage(buildRoot, prefix, {
84
+ noStart: options.noStart,
85
+ sourceRoot,
86
+ update: options.update,
87
+ })
88
+
89
+ console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
90
+ console.log(`version: ${version}`)
91
+ console.log(`run: ${displayCommand(prefix)} status`)
92
+ }
93
+
94
+ async function installFromPackage(options) {
95
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
96
+ const packageRoot = safeDirectory(options.package)
97
+ const version = await applyPackage(packageRoot, prefix, {
98
+ noStart: options.noStart,
99
+ update: options.update,
100
+ })
101
+
102
+ console.log(`${options.update ? "updated" : "installed"} Vantaloom: ${prefix}`)
103
+ console.log(`version: ${version}`)
104
+ console.log(`run: ${displayCommand(prefix)} status`)
105
+ }
106
+
107
+ async function packageRuntime(options) {
108
+ const sourceRoot = findSourceRoot(options.source)
109
+ const packageRoot = safeDirectory(
110
+ options.output ?? path.join(sourceRoot, "artifacts", "packages", `vantaloom-${platformId()}`)
111
+ )
112
+ const { platform: builtPlatform, version } = await buildRuntimePackage(sourceRoot, packageRoot, {
113
+ buildWeb: options.buildWeb,
114
+ })
115
+
116
+ if (options.archive) {
117
+ const archivePath = `${packageRoot}.tar.gz`
118
+ removeKnownPath(archivePath, path.dirname(archivePath))
119
+ run("tar", [
120
+ "-czf",
121
+ archivePath,
122
+ "-C",
123
+ path.dirname(packageRoot),
124
+ path.basename(packageRoot),
125
+ ])
126
+ console.log(`archive: ${archivePath}`)
127
+ }
128
+
129
+ console.log(`packaged Vantaloom: ${packageRoot}`)
130
+ console.log(`platform: ${builtPlatform}`)
131
+ console.log(`version: ${version}`)
132
+ }
133
+
134
+ async function buildRuntimePackage(sourceRoot, packageRoot, options) {
135
+ const version = gitVersion(sourceRoot)
136
+ const platform = platformId()
137
+ const buildBin = path.join(packageRoot, "bin")
138
+ const buildWeb = path.join(packageRoot, "web")
139
+
140
+ removeKnownPath(packageRoot, path.dirname(packageRoot))
141
+ mkdirSync(buildBin, { recursive: true })
142
+
143
+ buildGo(sourceRoot, buildBin, "vantaloom-api")
144
+ buildGo(sourceRoot, buildBin, "vantaloom-agent")
145
+ buildGo(sourceRoot, buildBin, "vantaloomctl")
146
+
147
+ if (options.buildWeb) {
148
+ run("pnpm", ["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
149
+ }
150
+
151
+ await copyStandaloneWeb(sourceRoot, buildWeb)
152
+ await copyCliDirectory(path.join(packageRoot, "cli"), sourceRoot, runtimeConfigFromSource(sourceRoot))
153
+ writeBuildManifest(packageRoot, version, platform)
154
+
155
+ return { platform, version }
156
+ }
157
+
158
+ async function applyPackage(packageRoot, prefix, options) {
159
+ assertRuntimePackage(packageRoot)
160
+
161
+ const existingConfig = readInstalledConfig(prefix)
162
+ const packageConfig = readJSONIfExists(path.join(packageRoot, "cli", "config.json"))
163
+ const mergedConfig = mergeRuntimeConfig(packageConfig, existingConfig, {
164
+ sourceRoot: options.sourceRoot,
165
+ })
166
+ const version = readVersion(packageRoot)
167
+
168
+ mkdirSync(prefix, { recursive: true })
169
+ const existingCtl = path.join(prefix, "bin", binaryName("vantaloomctl"))
170
+ if (existsSync(existingCtl)) {
171
+ spawnSync(existingCtl, ["stop", "--prefix", prefix], { stdio: "inherit" })
172
+ }
173
+
174
+ for (const name of ["bin", "web", "cli"]) {
175
+ removeKnownPath(path.join(prefix, name), prefix)
176
+ await cp(path.join(packageRoot, name), path.join(prefix, name), {
177
+ recursive: true,
178
+ force: true,
179
+ dereference: true,
180
+ })
181
+ }
182
+
183
+ await writeLauncher(prefix)
184
+ await writeText(path.join(prefix, "cli", "config.json"), `${JSON.stringify(mergedConfig, null, 2)}\n`)
185
+ await writeText(path.join(prefix, "VERSION"), `${version}\n`)
186
+ await cp(path.join(packageRoot, "manifest.json"), path.join(prefix, "manifest.json"), {
187
+ force: true,
188
+ })
189
+
190
+ run(path.join(prefix, "bin", binaryName("vantaloomctl")), [
191
+ "install",
192
+ "--prefix",
193
+ prefix,
194
+ "--version",
195
+ version,
196
+ ])
197
+
198
+ if (!options.noStart) {
199
+ run(path.join(prefix, "bin", binaryName("vantaloomctl")), [
200
+ "start",
201
+ "--prefix",
202
+ prefix,
203
+ ])
204
+ }
205
+
206
+ return version
207
+ }
208
+
209
+ async function updateFromDist(options) {
210
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
211
+ const installedConfig = readInstalledConfig(prefix)
212
+ const sourceRoot = installedConfig.sourceRoot
213
+ ? tryAssertSourceRoot(installedConfig.sourceRoot)
214
+ : tryFindSourceRoot()
215
+ const sourceRemote = sourceRoot ? gitRemoteUrl(sourceRoot) : ""
216
+ const remote = options.remote ?? installedConfig.remote ?? sourceRemote
217
+ const repo = options.repo || installedConfig.repo || inferGitHubRepo(remote) || defaultRepo
218
+ const distBranch = options.distBranch ?? installedConfig.distBranch ?? defaultDistBranch
219
+ const cloneUrl = remote || sshRemoteFromRepo(repo)
220
+ const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-update-"))
221
+
222
+ try {
223
+ const distRoot = path.join(tempRoot, "dist")
224
+ const extractRoot = path.join(tempRoot, "extract")
225
+ mkdirSync(extractRoot, { recursive: true })
226
+
227
+ run("git", [
228
+ "clone",
229
+ "--depth",
230
+ "1",
231
+ "--branch",
232
+ distBranch,
233
+ cloneUrl,
234
+ distRoot,
235
+ ])
236
+
237
+ const archive = findPackageArchive(distRoot, platformId())
238
+ run("tar", ["-xzf", archive, "-C", extractRoot])
239
+
240
+ const packageRoot = findExtractedPackage(extractRoot, platformId())
241
+ const version = await applyPackage(packageRoot, prefix, {
242
+ noStart: options.noStart,
243
+ sourceRoot: installedConfig.sourceRoot,
244
+ update: true,
245
+ })
246
+
247
+ console.log(`updated Vantaloom: ${prefix}`)
248
+ console.log(`version: ${version}`)
249
+ console.log(`source: ${repo}#${distBranch}`)
250
+ console.log(`run: ${displayCommand(prefix)} status`)
251
+ } finally {
252
+ removeKnownPath(tempRoot, os.tmpdir())
253
+ }
254
+ }
255
+
256
+ function shouldUseDistUpdate(options) {
257
+ return !options.local && !options.source && !options.buildWeb
258
+ }
259
+
260
+ function assertRuntimePackage(packageRoot) {
261
+ for (const name of ["bin", "web", "cli", "manifest.json"]) {
262
+ if (!existsSync(path.join(packageRoot, name))) {
263
+ throw new Error(`invalid Vantaloom package, missing ${name}: ${packageRoot}`)
264
+ }
265
+ }
266
+ }
267
+
268
+ function readVersion(packageRoot) {
269
+ const versionPath = path.join(packageRoot, "VERSION")
270
+ if (existsSync(versionPath)) {
271
+ return readFileSync(versionPath, "utf8").trim() || "dev"
272
+ }
273
+ const manifest = readJSONIfExists(path.join(packageRoot, "manifest.json"))
274
+ return manifest.version ?? "dev"
275
+ }
276
+
277
+ function readInstalledConfig(prefix) {
278
+ return readJSONIfExists(path.join(prefix, "cli", "config.json"))
279
+ }
280
+
281
+ function readJSONIfExists(filePath) {
282
+ if (!existsSync(filePath)) {
283
+ return {}
284
+ }
285
+ return JSON.parse(readFileSync(filePath, "utf8"))
286
+ }
287
+
288
+ function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
289
+ const merged = { ...packageConfig }
290
+ for (const key of ["sourceRoot", "remote", "repo", "distBranch"]) {
291
+ if (!merged[key] && existingConfig[key]) {
292
+ merged[key] = existingConfig[key]
293
+ }
294
+ }
295
+ if (overrides.sourceRoot) {
296
+ merged.sourceRoot = overrides.sourceRoot
297
+ }
298
+ if (!merged.repo) {
299
+ merged.repo = defaultRepo
300
+ }
301
+ if (!merged.distBranch) {
302
+ merged.distBranch = defaultDistBranch
303
+ }
304
+ return merged
305
+ }
306
+
307
+ function runtimeConfigFromSource(sourceRoot) {
308
+ const remote = gitRemoteUrl(sourceRoot)
309
+ const repo = inferGitHubRepo(remote) || defaultRepo
310
+ return {
311
+ ...(process.env.GITHUB_ACTIONS ? {} : { sourceRoot }),
312
+ ...(remote ? { remote } : {}),
313
+ repo,
314
+ distBranch: defaultDistBranch,
315
+ }
316
+ }
317
+
318
+ function findPackageArchive(distRoot, platform) {
319
+ const candidates = [
320
+ path.join(distRoot, "packages", `vantaloom-${platform}.tar.gz`),
321
+ path.join(distRoot, `vantaloom-${platform}.tar.gz`),
322
+ ]
323
+ for (const candidate of candidates) {
324
+ if (existsSync(candidate)) {
325
+ return candidate
326
+ }
327
+ }
328
+ throw new Error(`missing CI package for ${platform} in ${distRoot}`)
329
+ }
330
+
331
+ function findExtractedPackage(extractRoot, platform) {
332
+ const expected = path.join(extractRoot, `vantaloom-${platform}`)
333
+ if (existsSync(expected)) {
334
+ return expected
335
+ }
336
+
337
+ const directories = readdirSync(extractRoot, { withFileTypes: true })
338
+ .filter((entry) => entry.isDirectory())
339
+ .map((entry) => path.join(extractRoot, entry.name))
340
+ if (directories.length === 1) {
341
+ return directories[0]
342
+ }
343
+
344
+ throw new Error(`could not find extracted Vantaloom package in ${extractRoot}`)
345
+ }
346
+
347
+ function tryFindSourceRoot() {
348
+ try {
349
+ return findSourceRoot()
350
+ } catch {
351
+ return ""
352
+ }
353
+ }
354
+
355
+ function tryAssertSourceRoot(sourceRoot) {
356
+ try {
357
+ return assertSourceRoot(sourceRoot)
358
+ } catch {
359
+ return ""
360
+ }
361
+ }
362
+
363
+ function gitRemoteUrl(sourceRoot) {
364
+ const result = spawnSync("git", ["remote", "get-url", "origin"], {
365
+ cwd: sourceRoot,
366
+ encoding: "utf8",
367
+ })
368
+ if (result.status === 0) {
369
+ return result.stdout.trim()
370
+ }
371
+ return ""
372
+ }
373
+
374
+ function inferGitHubRepo(remote) {
375
+ if (!remote) {
376
+ return ""
377
+ }
378
+ const normalized = remote.replace(/\.git$/, "")
379
+ const httpsMatch = normalized.match(/github\.com[:/]([^/]+\/[^/]+)$/)
380
+ if (httpsMatch) {
381
+ return httpsMatch[1]
382
+ }
383
+ const sshMatch = normalized.match(/^[^:]+:([^/]+\/[^/]+)$/)
384
+ return sshMatch?.[1] ?? ""
385
+ }
386
+
387
+ function sshRemoteFromRepo(repo) {
388
+ return `git@github.com:${repo}.git`
389
+ }
390
+
391
+ async function copyStandaloneWeb(sourceRoot, buildWeb) {
392
+ const standalone = path.join(sourceRoot, "apps", "vantaloom", ".next", "standalone")
393
+ const staticSource = path.join(sourceRoot, "apps", "vantaloom", ".next", "static")
394
+ const publicSource = path.join(sourceRoot, "apps", "vantaloom", "public")
395
+ if (!existsSync(standalone)) {
396
+ throw new Error(
397
+ "missing Next standalone output; let GitHub CI run production build, or pass --build-web for a local one-off build"
398
+ )
399
+ }
400
+
401
+ removeKnownPath(buildWeb, path.dirname(buildWeb))
402
+ await copyDir(standalone, buildWeb)
403
+ await copyDir(staticSource, path.join(buildWeb, "apps", "vantaloom", ".next", "static"))
404
+ await copyDir(publicSource, path.join(buildWeb, "apps", "vantaloom", "public"))
405
+
406
+ const appNodeModules = path.join(buildWeb, "apps", "vantaloom", "node_modules")
407
+ for (const packageName of runtimePackages) {
408
+ await copyPnpmRuntimePackage(sourceRoot, packageName, appNodeModules)
409
+ }
410
+ }
411
+
412
+ async function copyCliDirectory(target, sourceRoot, config) {
413
+ const sourceCliRoot = path.join(sourceRoot, "packages", "cli")
414
+ if (!existsSync(path.join(sourceCliRoot, "bin", "vantaloom.mjs"))) {
415
+ throw new Error(`missing source CLI package: ${sourceCliRoot}`)
416
+ }
417
+ removeKnownPath(target, path.dirname(target))
418
+ mkdirSync(target, { recursive: true })
419
+ await copyDir(sourceCliRoot, target)
420
+ await writeText(
421
+ path.join(target, "config.json"),
422
+ `${JSON.stringify(config, null, 2)}\n`
423
+ )
424
+ }
425
+
426
+ async function writeLauncher(prefix) {
427
+ if (process.platform === "win32") {
428
+ await writeText(
429
+ path.join(prefix, "vantaloom.cmd"),
430
+ `@echo off\r\nnode "%~dp0cli\\bin\\vantaloom.mjs" %*\r\n`
431
+ )
432
+ } else {
433
+ const launcher = `#!/usr/bin/env sh\nexec node "$(dirname "$0")/cli/bin/vantaloom.mjs" "$@"\n`
434
+ const launcherPath = path.join(prefix, "vantaloom")
435
+ await writeText(launcherPath, launcher)
436
+ chmodSync(launcherPath, 0o755)
437
+ }
438
+ }
439
+
440
+ function runCtl(command, options) {
441
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
442
+ const ctl = path.join(prefix, "bin", binaryName("vantaloomctl"))
443
+ if (!existsSync(ctl)) {
444
+ throw new Error(`missing installed runtime at ${prefix}; run "vantaloom install" first`)
445
+ }
446
+
447
+ const args = [command, "--prefix", prefix]
448
+ if (options.component) {
449
+ args.push("--component", options.component)
450
+ }
451
+ run(ctl, args)
452
+ }
453
+
454
+ function printPaths(options) {
455
+ const sourceRoot = findSourceRoot(options.source)
456
+ const prefix = safeDirectory(options.prefix ?? defaultPrefix())
457
+ console.log(JSON.stringify({ sourceRoot, prefix }, null, 2))
458
+ }
459
+
460
+ function buildGo(sourceRoot, buildBin, name) {
461
+ run("go", [
462
+ "build",
463
+ "-o",
464
+ path.join(buildBin, binaryName(name)),
465
+ `./apps/api/cmd/${name}`,
466
+ ], { cwd: sourceRoot })
467
+ }
468
+
469
+ async function copyPnpmRuntimePackage(sourceRoot, packageName, destinationNodeModules) {
470
+ const pnpmRoot = path.join(sourceRoot, "node_modules", ".pnpm")
471
+ const storeName = packageName.replace("/", "+")
472
+ const packageRoot = readdirSync(pnpmRoot, { withFileTypes: true })
473
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith(`${storeName}@`))
474
+ .map((entry) => path.join(pnpmRoot, entry.name))
475
+ .sort()
476
+ .reverse()[0]
477
+ if (!packageRoot) {
478
+ throw new Error(`missing pnpm runtime package: ${packageName}`)
479
+ }
480
+
481
+ const source = path.join(packageRoot, "node_modules", ...packageName.split("/"))
482
+ if (!existsSync(source)) {
483
+ throw new Error(`missing pnpm runtime package source: ${source}`)
484
+ }
485
+
486
+ await copyDir(source, path.join(destinationNodeModules, ...packageName.split("/")))
487
+ }
488
+
489
+ async function copyDir(source, destination) {
490
+ if (!existsSync(source)) {
491
+ throw new Error(`missing source directory: ${source}`)
492
+ }
493
+ removeKnownPath(destination, path.dirname(destination))
494
+ mkdirSync(destination, { recursive: true })
495
+ await cp(source, destination, { recursive: true, force: true, dereference: true })
496
+ }
497
+
498
+ function writeBuildManifest(buildRoot, version, platform) {
499
+ writeFileSync(path.join(buildRoot, "VERSION"), `${version}\n`)
500
+ writeFileSync(
501
+ path.join(buildRoot, "manifest.json"),
502
+ `${JSON.stringify(
503
+ {
504
+ name: "Vantaloom Local Runtime",
505
+ version,
506
+ platform,
507
+ updatedAt: new Date().toISOString(),
508
+ components: ["api", "agent", "web", "ctl"],
509
+ },
510
+ null,
511
+ 2
512
+ )}\n`
513
+ )
514
+ }
515
+
516
+ function findSourceRoot(sourceOption) {
517
+ if (sourceOption) {
518
+ return assertSourceRoot(path.resolve(sourceOption))
519
+ }
520
+ if (process.env.VANTALOOM_SOURCE) {
521
+ return assertSourceRoot(path.resolve(process.env.VANTALOOM_SOURCE))
522
+ }
523
+ if (existsSync(installedConfigPath)) {
524
+ const config = JSON.parse(readFileSync(installedConfigPath, "utf8"))
525
+ if (config.sourceRoot) {
526
+ return assertSourceRoot(path.resolve(config.sourceRoot))
527
+ }
528
+ }
529
+ return assertSourceRoot(repoCandidate)
530
+ }
531
+
532
+ function assertSourceRoot(sourceRoot) {
533
+ if (!existsSync(path.join(sourceRoot, "apps", "api", "go.mod"))) {
534
+ throw new Error(`not a Vantaloom source root: ${sourceRoot}`)
535
+ }
536
+ return sourceRoot
537
+ }
538
+
539
+ function gitVersion(sourceRoot) {
540
+ const result = spawnSync("git", ["rev-parse", "--short", "HEAD"], {
541
+ cwd: sourceRoot,
542
+ encoding: "utf8",
543
+ })
544
+ if (result.status === 0) {
545
+ return result.stdout.trim() || "dev"
546
+ }
547
+ return "dev"
548
+ }
549
+
550
+ function parseOptions(args) {
551
+ const options = {}
552
+ for (let index = 0; index < args.length; index += 1) {
553
+ const arg = args[index]
554
+ if (!arg.startsWith("--")) {
555
+ throw new Error(`unexpected argument "${arg}"`)
556
+ }
557
+ const [key, inlineValue] = arg.slice(2).split("=", 2)
558
+ switch (key) {
559
+ case "prefix":
560
+ case "source":
561
+ case "component":
562
+ case "package":
563
+ case "output":
564
+ case "repo":
565
+ case "remote":
566
+ case "dist-branch":
567
+ options[toCamel(key)] = inlineValue ?? args[++index]
568
+ if (!options[toCamel(key)]) {
569
+ throw new Error(`missing value for --${key}`)
570
+ }
571
+ break
572
+ case "build-web":
573
+ options.buildWeb = true
574
+ break
575
+ case "no-start":
576
+ options.noStart = true
577
+ break
578
+ case "archive":
579
+ options.archive = true
580
+ break
581
+ case "local":
582
+ options.local = true
583
+ break
584
+ default:
585
+ throw new Error(`unknown option --${key}`)
586
+ }
587
+ }
588
+ return options
589
+ }
590
+
591
+ function safeDirectory(value) {
592
+ const full = path.resolve(value)
593
+ const parsed = path.parse(full)
594
+ if (full === parsed.root) {
595
+ throw new Error(`refusing to operate on unsafe directory: ${value}`)
596
+ }
597
+ return full
598
+ }
599
+
600
+ function removeKnownPath(target, expectedParent) {
601
+ if (!existsSync(target)) {
602
+ return
603
+ }
604
+ const full = path.resolve(target)
605
+ const parent = path.resolve(expectedParent)
606
+ const prefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`
607
+ if (!full.startsWith(prefix)) {
608
+ throw new Error(`refusing to remove path outside expected parent: ${full}`)
609
+ }
610
+ rmSync(full, { recursive: true, force: true })
611
+ }
612
+
613
+ function run(command, args, options = {}) {
614
+ try {
615
+ execFileSync(command, args, { stdio: "inherit", ...options })
616
+ } catch (error) {
617
+ if (error && typeof error.status === "number") {
618
+ throw new Error(`${command} exited with ${error.status}`)
619
+ }
620
+ throw error
621
+ }
622
+ }
623
+
624
+ function binaryName(name) {
625
+ return process.platform === "win32" ? `${name}.exe` : name
626
+ }
627
+
628
+ function platformId() {
629
+ return `${process.platform}-${process.arch}`
630
+ }
631
+
632
+ function defaultPrefix() {
633
+ if (process.env.VANTALOOM_HOME) {
634
+ return process.env.VANTALOOM_HOME
635
+ }
636
+ if (process.platform === "win32") {
637
+ return "D:\\Vantaloom"
638
+ }
639
+ if (process.platform === "darwin") {
640
+ return path.join(os.homedir(), "Applications", "Vantaloom")
641
+ }
642
+ return path.join(os.homedir(), ".local", "vantaloom")
643
+ }
644
+
645
+ function displayCommand(prefix) {
646
+ if (process.platform === "win32") {
647
+ return path.join(prefix, "vantaloom.cmd")
648
+ }
649
+ return path.join(prefix, "vantaloom")
650
+ }
651
+
652
+ function toCamel(value) {
653
+ return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase())
654
+ }
655
+
656
+ async function writeText(filePath, content) {
657
+ mkdirSync(path.dirname(filePath), { recursive: true })
658
+ await writeFile(filePath, content)
659
+ }
660
+
661
+ function printHelp() {
662
+ console.log(`Vantaloom CLI
663
+
664
+ Usage:
665
+ vantaloom install [--prefix <dir>] [--source <repo>] [--build-web] [--package <dir>] [--no-start]
666
+ vantaloom update [--prefix <dir>] [--repo <owner/name>] [--remote <git-url>] [--dist-branch <branch>] [--no-start]
667
+ vantaloom update --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
668
+ vantaloom package [--source <repo>] [--output <dir>] [--build-web] [--archive]
669
+ vantaloom start [--prefix <dir>] [--component all|api|agent|web]
670
+ vantaloom stop [--prefix <dir>] [--component all|api|agent|web]
671
+ vantaloom restart [--prefix <dir>] [--component all|api|agent|web]
672
+ vantaloom status [--prefix <dir>]
673
+ vantaloom ports [--prefix <dir>]
674
+ vantaloom path [--prefix <dir>] [--source <repo>]
675
+ vantaloom platform
676
+ `)
677
+ }