@vantaloom/cli 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.mjs +140 -54
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vantaloom/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Vantaloom local runtime manager.",
package/src/cli.mjs CHANGED
@@ -1,5 +1,16 @@
1
1
  import { execFileSync, spawnSync } from "node:child_process"
2
- import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
2
+ import {
3
+ chmodSync,
4
+ existsSync,
5
+ lstatSync,
6
+ mkdirSync,
7
+ mkdtempSync,
8
+ readdirSync,
9
+ readFileSync,
10
+ readlinkSync,
11
+ rmSync,
12
+ writeFileSync,
13
+ } from "node:fs"
3
14
  import { cp, writeFile } from "node:fs/promises"
4
15
  import os from "node:os"
5
16
  import path from "node:path"
@@ -8,7 +19,7 @@ import { fileURLToPath } from "node:url"
8
19
  const cliRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..")
9
20
  const repoCandidate = path.resolve(cliRoot, "..", "..")
10
21
  const installedConfigPath = path.join(cliRoot, "config.json")
11
- const defaultDistBranch = "vantaloom-dist"
22
+ const defaultReleaseTag = "runtime-latest"
12
23
  const defaultRepo = "Timefiles404/Vantaloom-next"
13
24
 
14
25
  const runtimePackages = [
@@ -32,6 +43,8 @@ export async function main(argv) {
32
43
  case "install":
33
44
  if (options.package) {
34
45
  await installFromPackage({ ...options, update: false })
46
+ } else if (shouldUseDistSync(options)) {
47
+ await syncFromRelease(options, "install")
35
48
  } else {
36
49
  await installFromSource(options)
37
50
  }
@@ -39,8 +52,8 @@ export async function main(argv) {
39
52
  case "update":
40
53
  if (options.package) {
41
54
  await installFromPackage({ ...options, update: true })
42
- } else if (shouldUseDistUpdate(options)) {
43
- await updateFromDist(options)
55
+ } else if (shouldUseDistSync(options)) {
56
+ await syncFromRelease(options, "update")
44
57
  } else {
45
58
  await installFromSource({ ...options, update: true })
46
59
  }
@@ -145,7 +158,7 @@ async function buildRuntimePackage(sourceRoot, packageRoot, options) {
145
158
  buildGo(sourceRoot, buildBin, "vantaloomctl")
146
159
 
147
160
  if (options.buildWeb) {
148
- run("pnpm", ["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
161
+ runPnpm(["--filter", "vantaloom-app", "build"], { cwd: sourceRoot })
149
162
  }
150
163
 
151
164
  await copyStandaloneWeb(sourceRoot, buildWeb)
@@ -176,7 +189,7 @@ async function applyPackage(packageRoot, prefix, options) {
176
189
  await cp(path.join(packageRoot, name), path.join(prefix, name), {
177
190
  recursive: true,
178
191
  force: true,
179
- dereference: true,
192
+ dereference: false,
180
193
  })
181
194
  }
182
195
 
@@ -206,54 +219,71 @@ async function applyPackage(packageRoot, prefix, options) {
206
219
  return version
207
220
  }
208
221
 
209
- async function updateFromDist(options) {
222
+ async function syncFromRelease(options, action) {
210
223
  const prefix = safeDirectory(options.prefix ?? defaultPrefix())
211
224
  const installedConfig = readInstalledConfig(prefix)
212
225
  const sourceRoot = installedConfig.sourceRoot
213
226
  ? tryAssertSourceRoot(installedConfig.sourceRoot)
214
227
  : tryFindSourceRoot()
215
228
  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)
229
+ const repo = options.repo || installedConfig.repo || inferGitHubRepo(options.remote ?? installedConfig.remote ?? sourceRemote) || defaultRepo
230
+ const releaseTag = options.releaseTag || installedConfig.releaseTag || defaultReleaseTag
220
231
  const tempRoot = mkdtempSync(path.join(os.tmpdir(), "vantaloom-update-"))
221
232
 
222
233
  try {
223
- const distRoot = path.join(tempRoot, "dist")
224
234
  const extractRoot = path.join(tempRoot, "extract")
235
+ const archive = path.join(tempRoot, `vantaloom-${platformId()}.tar.gz`)
225
236
  mkdirSync(extractRoot, { recursive: true })
226
237
 
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
+ await downloadReleaseAsset({
239
+ repo,
240
+ releaseTag,
241
+ assetName: `vantaloom-${platformId()}.tar.gz`,
242
+ target: archive,
243
+ token: options.githubToken,
244
+ })
238
245
  run("tar", ["-xzf", archive, "-C", extractRoot])
239
246
 
240
247
  const packageRoot = findExtractedPackage(extractRoot, platformId())
241
248
  const version = await applyPackage(packageRoot, prefix, {
242
249
  noStart: options.noStart,
243
250
  sourceRoot: installedConfig.sourceRoot,
244
- update: true,
251
+ update: action === "update",
245
252
  })
246
253
 
247
- console.log(`updated Vantaloom: ${prefix}`)
254
+ console.log(`${action === "update" ? "updated" : "installed"} Vantaloom: ${prefix}`)
248
255
  console.log(`version: ${version}`)
249
- console.log(`source: ${repo}#${distBranch}`)
256
+ console.log(`source: ${repo}@${releaseTag}`)
250
257
  console.log(`run: ${displayCommand(prefix)} status`)
251
258
  } finally {
252
259
  removeKnownPath(tempRoot, os.tmpdir())
253
260
  }
254
261
  }
255
262
 
256
- function shouldUseDistUpdate(options) {
263
+ async function downloadReleaseAsset({ repo, releaseTag, assetName, target, token }) {
264
+ const url = `https://github.com/${repo}/releases/download/${releaseTag}/${assetName}`
265
+ const headers = {
266
+ "User-Agent": "vantaloom-cli",
267
+ }
268
+ const githubToken = token || process.env.VANTALOOM_GITHUB_TOKEN || process.env.GITHUB_TOKEN
269
+ if (githubToken) {
270
+ headers.Authorization = `Bearer ${githubToken}`
271
+ }
272
+
273
+ const response = await fetch(url, { headers })
274
+ if (!response.ok) {
275
+ const detail = await response.text().catch(() => "")
276
+ const authHint = response.status === 404 || response.status === 403
277
+ ? " If the repository is private, set VANTALOOM_GITHUB_TOKEN to a GitHub token that can read releases."
278
+ : ""
279
+ throw new Error(`failed to download ${assetName} from ${repo}@${releaseTag}: HTTP ${response.status}.${authHint}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
280
+ }
281
+
282
+ const buffer = Buffer.from(await response.arrayBuffer())
283
+ await writeFile(target, buffer)
284
+ }
285
+
286
+ function shouldUseDistSync(options) {
257
287
  return !options.local && !options.source && !options.buildWeb
258
288
  }
259
289
 
@@ -287,7 +317,7 @@ function readJSONIfExists(filePath) {
287
317
 
288
318
  function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
289
319
  const merged = { ...packageConfig }
290
- for (const key of ["sourceRoot", "remote", "repo", "distBranch"]) {
320
+ for (const key of ["sourceRoot", "remote", "repo", "releaseTag"]) {
291
321
  if (!merged[key] && existingConfig[key]) {
292
322
  merged[key] = existingConfig[key]
293
323
  }
@@ -298,8 +328,8 @@ function mergeRuntimeConfig(packageConfig, existingConfig, overrides) {
298
328
  if (!merged.repo) {
299
329
  merged.repo = defaultRepo
300
330
  }
301
- if (!merged.distBranch) {
302
- merged.distBranch = defaultDistBranch
331
+ if (!merged.releaseTag) {
332
+ merged.releaseTag = defaultReleaseTag
303
333
  }
304
334
  return merged
305
335
  }
@@ -311,23 +341,10 @@ function runtimeConfigFromSource(sourceRoot) {
311
341
  ...(process.env.GITHUB_ACTIONS ? {} : { sourceRoot }),
312
342
  ...(remote ? { remote } : {}),
313
343
  repo,
314
- distBranch: defaultDistBranch,
344
+ releaseTag: defaultReleaseTag,
315
345
  }
316
346
  }
317
347
 
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
348
  function findExtractedPackage(extractRoot, platform) {
332
349
  const expected = path.join(extractRoot, `vantaloom-${platform}`)
333
350
  if (existsSync(expected)) {
@@ -384,10 +401,6 @@ function inferGitHubRepo(remote) {
384
401
  return sshMatch?.[1] ?? ""
385
402
  }
386
403
 
387
- function sshRemoteFromRepo(repo) {
388
- return `git@github.com:${repo}.git`
389
- }
390
-
391
404
  async function copyStandaloneWeb(sourceRoot, buildWeb) {
392
405
  const standalone = path.join(sourceRoot, "apps", "vantaloom", ".next", "standalone")
393
406
  const staticSource = path.join(sourceRoot, "apps", "vantaloom", ".next", "static")
@@ -399,7 +412,11 @@ async function copyStandaloneWeb(sourceRoot, buildWeb) {
399
412
  }
400
413
 
401
414
  removeKnownPath(buildWeb, path.dirname(buildWeb))
402
- await copyDir(standalone, buildWeb)
415
+ const preserveStandaloneLinks = process.platform !== "win32"
416
+ await copyDir(standalone, buildWeb, { dereference: !preserveStandaloneLinks })
417
+ if (preserveStandaloneLinks) {
418
+ await copyStandaloneHoistedTargets(sourceRoot, buildWeb)
419
+ }
403
420
  await copyDir(staticSource, path.join(buildWeb, "apps", "vantaloom", ".next", "static"))
404
421
  await copyDir(publicSource, path.join(buildWeb, "apps", "vantaloom", "public"))
405
422
 
@@ -486,13 +503,72 @@ async function copyPnpmRuntimePackage(sourceRoot, packageName, destinationNodeMo
486
503
  await copyDir(source, path.join(destinationNodeModules, ...packageName.split("/")))
487
504
  }
488
505
 
489
- async function copyDir(source, destination) {
506
+ async function copyStandaloneHoistedTargets(sourceRoot, buildWeb) {
507
+ const sourceHoisted = path.join(sourceRoot, "node_modules", ".pnpm", "node_modules")
508
+ const buildHoisted = path.join(buildWeb, "node_modules", ".pnpm", "node_modules")
509
+ const packageNames = findHoistedSymlinkPackages(path.join(buildWeb, "node_modules"))
510
+
511
+ for (const packageName of packageNames) {
512
+ const source = path.join(sourceHoisted, ...packageName.split("/"))
513
+ if (existsSync(source)) {
514
+ await copyDir(source, path.join(buildHoisted, ...packageName.split("/")))
515
+ }
516
+ }
517
+ }
518
+
519
+ function findHoistedSymlinkPackages(nodeModules) {
520
+ const packages = new Set()
521
+ if (!existsSync(nodeModules)) {
522
+ return packages
523
+ }
524
+
525
+ for (const entry of readdirSync(nodeModules, { withFileTypes: true })) {
526
+ if (entry.name === ".pnpm" || entry.name.startsWith(".")) {
527
+ continue
528
+ }
529
+
530
+ const entryPath = path.join(nodeModules, entry.name)
531
+ if (entry.name.startsWith("@") && entry.isDirectory()) {
532
+ for (const scopedEntry of readdirSync(entryPath, { withFileTypes: true })) {
533
+ addHoistedSymlinkPackage(packages, path.join(entryPath, scopedEntry.name), `${entry.name}/${scopedEntry.name}`)
534
+ }
535
+ continue
536
+ }
537
+
538
+ addHoistedSymlinkPackage(packages, entryPath, entry.name)
539
+ }
540
+
541
+ return packages
542
+ }
543
+
544
+ function addHoistedSymlinkPackage(packages, packagePath, packageName) {
545
+ let stat
546
+ try {
547
+ stat = lstatSync(packagePath)
548
+ } catch {
549
+ return
550
+ }
551
+ if (!stat.isSymbolicLink()) {
552
+ return
553
+ }
554
+
555
+ const target = readlinkSync(packagePath).replaceAll("\\", "/")
556
+ if (target.includes(".pnpm/node_modules/")) {
557
+ packages.add(packageName)
558
+ }
559
+ }
560
+
561
+ async function copyDir(source, destination, options = {}) {
490
562
  if (!existsSync(source)) {
491
563
  throw new Error(`missing source directory: ${source}`)
492
564
  }
493
565
  removeKnownPath(destination, path.dirname(destination))
494
566
  mkdirSync(destination, { recursive: true })
495
- await cp(source, destination, { recursive: true, force: true, dereference: true })
567
+ await cp(source, destination, {
568
+ recursive: true,
569
+ force: true,
570
+ dereference: options.dereference ?? true,
571
+ })
496
572
  }
497
573
 
498
574
  function writeBuildManifest(buildRoot, version, platform) {
@@ -563,7 +639,8 @@ function parseOptions(args) {
563
639
  case "output":
564
640
  case "repo":
565
641
  case "remote":
566
- case "dist-branch":
642
+ case "release-tag":
643
+ case "github-token":
567
644
  options[toCamel(key)] = inlineValue ?? args[++index]
568
645
  if (!options[toCamel(key)]) {
569
646
  throw new Error(`missing value for --${key}`)
@@ -621,6 +698,14 @@ function run(command, args, options = {}) {
621
698
  }
622
699
  }
623
700
 
701
+ function runPnpm(args, options = {}) {
702
+ if (process.platform === "win32") {
703
+ run("cmd.exe", ["/d", "/s", "/c", "pnpm", ...args], options)
704
+ return
705
+ }
706
+ run("pnpm", args, options)
707
+ }
708
+
624
709
  function binaryName(name) {
625
710
  return process.platform === "win32" ? `${name}.exe` : name
626
711
  }
@@ -662,8 +747,9 @@ function printHelp() {
662
747
  console.log(`Vantaloom CLI
663
748
 
664
749
  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]
750
+ vantaloom install [--prefix <dir>] [--repo <owner/name>] [--release-tag <tag>] [--github-token <token>] [--package <dir>] [--no-start]
751
+ vantaloom install --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
752
+ vantaloom update [--prefix <dir>] [--repo <owner/name>] [--release-tag <tag>] [--github-token <token>] [--no-start]
667
753
  vantaloom update --local [--prefix <dir>] [--source <repo>] [--build-web] [--no-start]
668
754
  vantaloom package [--source <repo>] [--output <dir>] [--build-web] [--archive]
669
755
  vantaloom start [--prefix <dir>] [--component all|api|agent|web]