@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.
@@ -0,0 +1,237 @@
1
+ import {
2
+ existsSync,
3
+ readFileSync,
4
+ readdirSync,
5
+ } from "node:fs"
6
+ import { writeFile } from "node:fs/promises"
7
+ import os from "node:os"
8
+ import path from "node:path"
9
+
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
+ export async function resolveNpmPackageWithFallback({ registries, name, version }) {
47
+ const errors = []
48
+ for (const registry of registries) {
49
+ try {
50
+ const result = await resolveNpmPackage({ registry, name, version })
51
+ return { ...result, registry }
52
+ } catch (error) {
53
+ const causeCode = error?.cause?.code || ""
54
+ const isNetworkError = causeCode === "ECONNREFUSED"
55
+ || causeCode === "ENOTFOUND"
56
+ || causeCode === "ETIMEDOUT"
57
+ || causeCode === "ECONNRESET"
58
+ || causeCode === "UND_ERR_CONNECT_TIMEOUT"
59
+ || (error instanceof TypeError && error.message === "fetch failed")
60
+ const isSslError = causeCode === "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
61
+ || causeCode === "CERT_HAS_EXPIRED"
62
+ || causeCode === "DEPTH_ZERO_SELF_SIGNED_CERT"
63
+ || causeCode === "SELF_SIGNED_CERT_IN_CHAIN"
64
+ || causeCode === "ERR_TLS_CERT_ALTNAME_INVALID"
65
+ const isRetryable = isNetworkError || isSslError
66
+ errors.push({ registry, error, isRetryable, isSslError })
67
+
68
+ if (isRetryable && registries.length > 1) {
69
+ const hint = isSslError ? " (SSL certificate error)" : ""
70
+ console.error(`vantaloom: ${registry} unreachable${hint}, trying next registry...`)
71
+ continue
72
+ }
73
+ if (isSslError) {
74
+ throw new Error(
75
+ `SSL certificate error connecting to ${registry}: ${causeCode}\n` +
76
+ ` Fix: run with --no-strict-ssl, or set npm config: npm config set strict-ssl false`
77
+ )
78
+ }
79
+ // Non-network error (404, parse error, etc.) — don't try fallbacks
80
+ throw error
81
+ }
82
+ }
83
+
84
+ // All registries failed
85
+ const hasSslError = errors.some((e) => e.isSslError)
86
+ const tried = errors.map((e) => e.registry).join(", ")
87
+ const sslHint = hasSslError
88
+ ? `\n SSL fix: run with --no-strict-ssl, or set npm config: npm config set strict-ssl false`
89
+ : ""
90
+ throw new Error(
91
+ `all registries unreachable (tried: ${tried}). ` +
92
+ `Check your network or specify --npm-registry <url>${sslHint}`
93
+ )
94
+ }
95
+
96
+ export async function resolveNpmPackage({ registry, name, version }) {
97
+ const metadataUrl = `${registry}/${encodeURIComponent(name).replace("%2F", "%2f")}`
98
+ let response
99
+ try {
100
+ response = await fetch(metadataUrl, {
101
+ headers: {
102
+ Accept: "application/vnd.npm.install-v1+json",
103
+ "User-Agent": "vantaloom-cli",
104
+ },
105
+ signal: AbortSignal.timeout(15000),
106
+ })
107
+ } catch (error) {
108
+ if (error instanceof TypeError && error.message === "fetch failed") {
109
+ const causeCode = error.cause?.code || ""
110
+ const causeMsg = causeCode || error.cause?.message || String(error.cause || "")
111
+ throw Object.assign(
112
+ new Error(`cannot reach registry ${registry} (${causeMsg})`),
113
+ { cause: error.cause }
114
+ )
115
+ }
116
+ throw error
117
+ }
118
+ if (!response.ok) {
119
+ const detail = await response.text().catch(() => "")
120
+ throw new Error(`failed to inspect npm package ${name}: HTTP ${response.status}${detail ? ` ${detail.slice(0, 200)}` : ""}`)
121
+ }
122
+
123
+ const metadata = await response.json()
124
+ const selectedVersion = metadata["dist-tags"]?.[version] ?? version
125
+ const packageVersion = metadata.versions?.[selectedVersion]
126
+ if (!packageVersion?.dist?.tarball) {
127
+ const available = Object.keys(metadata.versions ?? {}).slice(-8).join(", ") || "none"
128
+ throw new Error(`missing npm package ${name}@${version}; available versions: ${available}`)
129
+ }
130
+ return {
131
+ version: selectedVersion,
132
+ tarball: packageVersion.dist.tarball,
133
+ }
134
+ }
135
+
136
+ export async function downloadNpmTarball({ tarballUrl, target, packageName, version }) {
137
+ console.log(` downloading ${packageName}@${version}...`)
138
+ let response
139
+ try {
140
+ response = await fetch(tarballUrl, {
141
+ headers: {
142
+ "User-Agent": "vantaloom-cli",
143
+ },
144
+ signal: AbortSignal.timeout(120000),
145
+ })
146
+ } catch (error) {
147
+ const causeCode = error?.cause?.code || ""
148
+ const causeMsg = causeCode || error?.cause?.message || error.message || ""
149
+ const isSsl = causeCode.includes("CERT") || causeCode === "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
150
+ const hint = isSsl ? `\n Fix: run with --no-strict-ssl` : ""
151
+ throw new Error(`failed to download ${packageName}@${version} from ${tarballUrl}: ${causeMsg}${hint}`)
152
+ }
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
+ }
222
+
223
+ export function findExtractedNpmPackage(extractRoot) {
224
+ const expected = path.join(extractRoot, "package")
225
+ if (existsSync(expected)) {
226
+ return expected
227
+ }
228
+
229
+ const directories = readdirSync(extractRoot, { withFileTypes: true })
230
+ .filter((entry) => entry.isDirectory())
231
+ .map((entry) => path.join(extractRoot, entry.name))
232
+ if (directories.length === 1) {
233
+ return directories[0]
234
+ }
235
+
236
+ throw new Error(`could not find extracted npm package in ${extractRoot}`)
237
+ }