@vantaloom/cli 0.6.0 → 0.13.7
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,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
|
+
}
|