@lpm-registry/cli 0.58.0 → 0.59.1
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 +25 -44
- package/bin/lpm.js +2 -354
- package/bin/lpx.js +4 -0
- package/bin/native.js +154 -0
- package/package.json +42 -82
- package/scripts/install-binary.js +219 -550
- package/CHANGELOG.md +0 -197
- package/LICENSE +0 -15
- package/index.d.ts +0 -131
- package/index.js +0 -31
- package/lib/api.js +0 -398
- package/lib/commands/add.js +0 -1217
- package/lib/commands/audit.js +0 -283
- package/lib/commands/cache.js +0 -209
- package/lib/commands/check-name.js +0 -112
- package/lib/commands/config.js +0 -186
- package/lib/commands/doctor.js +0 -140
- package/lib/commands/info.js +0 -215
- package/lib/commands/init.js +0 -146
- package/lib/commands/install.js +0 -264
- package/lib/commands/login.js +0 -595
- package/lib/commands/logout.js +0 -94
- package/lib/commands/marketplace-compare.js +0 -164
- package/lib/commands/marketplace-earnings.js +0 -89
- package/lib/commands/mcp-setup.js +0 -363
- package/lib/commands/npmrc.js +0 -178
- package/lib/commands/open.js +0 -82
- package/lib/commands/outdated.js +0 -291
- package/lib/commands/pool-stats.js +0 -100
- package/lib/commands/publish.js +0 -949
- package/lib/commands/quality.js +0 -211
- package/lib/commands/remove.js +0 -82
- package/lib/commands/run.js +0 -14
- package/lib/commands/search.js +0 -143
- package/lib/commands/setup.js +0 -221
- package/lib/commands/skills.js +0 -898
- package/lib/commands/token-rotate.js +0 -25
- package/lib/commands/uninstall.js +0 -107
- package/lib/commands/whoami.js +0 -129
- package/lib/config.js +0 -328
- package/lib/constants.js +0 -190
- package/lib/ecosystem.js +0 -501
- package/lib/editor-skills.js +0 -321
- package/lib/editors.js +0 -215
- package/lib/import-rewriter.js +0 -364
- package/lib/install-targets/mcp-server.js +0 -245
- package/lib/install-targets/vscode-extension.js +0 -178
- package/lib/install-targets.js +0 -82
- package/lib/integrity.js +0 -179
- package/lib/lpm-config-prompts.js +0 -102
- package/lib/lpm-config.js +0 -408
- package/lib/oidc.js +0 -166
- package/lib/project-utils.js +0 -152
- package/lib/quality/checks.js +0 -687
- package/lib/quality/display.js +0 -155
- package/lib/quality/score.js +0 -118
- package/lib/quality/swift-checks.js +0 -447
- package/lib/safe-path.js +0 -180
- package/lib/secure-store.js +0 -321
- package/lib/swift-project.js +0 -637
- package/lib/ui.js +0 -40
- package/scripts/__tests__/install-binary.test.js +0 -346
|
@@ -1,571 +1,240 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
let binaryPath = tryOptionalDependency(platformInfo)
|
|
88
|
-
|
|
89
|
-
if (!binaryPath) {
|
|
90
|
-
const githubBinaryName = GITHUB_BINARY_NAMES[platform]
|
|
91
|
-
if (githubBinaryName) {
|
|
92
|
-
binaryPath = await tryGitHubDownload(pkg.version, githubBinaryName)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!binaryPath) {
|
|
97
|
-
console.log("[lpm] Binary not available. Using JS CLI fallback.")
|
|
98
|
-
return
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (process.platform !== "win32") {
|
|
102
|
-
tryHardLink(binaryPath)
|
|
103
|
-
} else {
|
|
104
|
-
tryCopyBinary(binaryPath)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function tryOptionalDependency(platformInfo) {
|
|
109
|
-
try {
|
|
110
|
-
const pkgJsonPath = require.resolve(`${platformInfo.pkg}/package.json`)
|
|
111
|
-
const platformPkgDir = path.dirname(pkgJsonPath)
|
|
112
|
-
const binaryPath = path.join(platformPkgDir, platformInfo.binary)
|
|
113
|
-
|
|
114
|
-
if (fs.existsSync(binaryPath)) {
|
|
115
|
-
try {
|
|
116
|
-
execSync(`"${binaryPath}" --version`, {
|
|
117
|
-
stdio: "ignore",
|
|
118
|
-
timeout: 5000,
|
|
119
|
-
})
|
|
120
|
-
console.log(`[lpm] Using native binary from ${platformInfo.pkg}`)
|
|
121
|
-
return binaryPath
|
|
122
|
-
} catch {
|
|
123
|
-
console.log(
|
|
124
|
-
`[lpm] Binary from ${platformInfo.pkg} exists but not executable.`,
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
// Package not installed (--no-optional, or resolution failed).
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return null
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import {
|
|
8
|
+
LpmWrapperError,
|
|
9
|
+
PLATFORMS,
|
|
10
|
+
platformKey,
|
|
11
|
+
resolveNativeBinary,
|
|
12
|
+
} from "../bin/native.js";
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const defaultWrapperRoot = path.resolve(scriptDir, "..");
|
|
17
|
+
const defaultValidateTimeoutMs = 30_000;
|
|
18
|
+
const commands = ["lpm", "lpx"];
|
|
19
|
+
|
|
20
|
+
export function installNativeBinaries(options = {}) {
|
|
21
|
+
const wrapperRoot = options.wrapperRoot ?? defaultWrapperRoot;
|
|
22
|
+
const platform = options.platform ?? process.platform;
|
|
23
|
+
const arch = options.arch ?? os.arch();
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
const requireFn = options.requireFn ?? require;
|
|
26
|
+
const fsModule = options.fsModule ?? fs;
|
|
27
|
+
const spawnSyncFn = options.spawnSyncFn ?? spawnSync;
|
|
28
|
+
const logFn = options.logFn ?? console.log;
|
|
29
|
+
const validateTimeoutMs =
|
|
30
|
+
options.validateTimeoutMs ?? defaultValidateTimeoutMs;
|
|
31
|
+
|
|
32
|
+
const key = platformKey(platform, arch);
|
|
33
|
+
const spec = PLATFORMS[key];
|
|
34
|
+
if (!spec) {
|
|
35
|
+
logFn(
|
|
36
|
+
`[lpm] No pre-built native npm package for ${key}; the JS wrapper will report this at runtime.`,
|
|
37
|
+
);
|
|
38
|
+
return { status: "unsupported", platform: key, optimized: [] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const expectedVersion = readWrapperVersion(wrapperRoot, fsModule);
|
|
42
|
+
const lpmBinary = resolveNativeBinary({
|
|
43
|
+
command: "lpm",
|
|
44
|
+
platform,
|
|
45
|
+
arch,
|
|
46
|
+
env,
|
|
47
|
+
requireFn,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
validateNativeBinary(lpmBinary.path, {
|
|
51
|
+
expectedVersion,
|
|
52
|
+
env,
|
|
53
|
+
spawnSyncFn,
|
|
54
|
+
validateTimeoutMs,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const optimized = [];
|
|
58
|
+
for (const command of commands) {
|
|
59
|
+
const native =
|
|
60
|
+
command === "lpm"
|
|
61
|
+
? lpmBinary
|
|
62
|
+
: resolveNativeBinary({
|
|
63
|
+
command,
|
|
64
|
+
platform,
|
|
65
|
+
arch,
|
|
66
|
+
env,
|
|
67
|
+
requireFn,
|
|
68
|
+
});
|
|
69
|
+
const target = optimizedTargetPath(wrapperRoot, platform, command);
|
|
70
|
+
const action = stageOptimizedBinary({
|
|
71
|
+
source: native.path,
|
|
72
|
+
target,
|
|
73
|
+
platform,
|
|
74
|
+
fsModule,
|
|
75
|
+
});
|
|
76
|
+
optimized.push({
|
|
77
|
+
command,
|
|
78
|
+
source: native.path,
|
|
79
|
+
target,
|
|
80
|
+
action,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
logFn(`[lpm] Installed native binary from ${spec.pkg}`);
|
|
85
|
+
return { status: "installed", platform: key, package: spec.pkg, optimized };
|
|
133
86
|
}
|
|
134
87
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// sigstore verification is opportunistic on this channel.
|
|
192
|
-
bundleBody = null
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (bundleBody) {
|
|
196
|
-
const verdict = await verifyFn(manifestBody, bundleBody)
|
|
197
|
-
if (verdict === "verified") {
|
|
198
|
-
console.log(
|
|
199
|
-
"[lpm] Verified Sigstore signature on manifest (identity-pinned to release.yml)",
|
|
200
|
-
)
|
|
201
|
-
} else if (verdict === "rejected") {
|
|
202
|
-
// Verifier ran and refused — strong attack signal.
|
|
203
|
-
console.log(
|
|
204
|
-
"[lpm] ERROR: Sigstore verifier rejected the manifest signature.",
|
|
205
|
-
)
|
|
206
|
-
console.log(
|
|
207
|
-
"[lpm] Refusing to install. See https://github.com/lpm-dev/rust-client/issues",
|
|
208
|
-
)
|
|
209
|
-
return null
|
|
210
|
-
} else {
|
|
211
|
-
// Optional `sigstore` package not resolvable — degrade to
|
|
212
|
-
// SHA-only with a loud WARN so the user knows what to
|
|
213
|
-
// install for the stronger posture.
|
|
214
|
-
console.log(
|
|
215
|
-
"[lpm] WARN: optional 'sigstore' package not available; falling back to SHA-256 against the signed manifest only.",
|
|
216
|
-
)
|
|
217
|
-
console.log(
|
|
218
|
-
"[lpm] For Sigstore verification, run `npm install --include=optional` or unblock the sigstore package.",
|
|
219
|
-
)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const destName = process.platform === "win32" ? "lpm-bin.exe" : "lpm-bin"
|
|
225
|
-
const destPath = path.join(destDir, destName)
|
|
226
|
-
const tmpPath = `${destPath}.tmp.${process.pid}`
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
console.log("[lpm] Downloading native binary from GitHub Releases...")
|
|
230
|
-
await downloadFn(assetUrl, tmpPath, 0, ASSET_MAX_BYTES)
|
|
231
|
-
} catch (err) {
|
|
232
|
-
console.log(`[lpm] Download failed: ${err.message}. Using JS CLI fallback.`)
|
|
233
|
-
safeUnlink(tmpPath)
|
|
234
|
-
return null
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (manifestBody) {
|
|
238
|
-
const actualSha = await sha256FileFn(tmpPath)
|
|
239
|
-
const expectedSha = parseManifestEntry(
|
|
240
|
-
manifestBody.toString("utf-8"),
|
|
241
|
-
githubBinaryName,
|
|
242
|
-
)
|
|
243
|
-
if (!expectedSha) {
|
|
244
|
-
console.log(
|
|
245
|
-
`[lpm] ERROR: manifest does not enumerate ${githubBinaryName}; release-pipeline bug.`,
|
|
246
|
-
)
|
|
247
|
-
safeUnlink(tmpPath)
|
|
248
|
-
return null
|
|
249
|
-
}
|
|
250
|
-
if (actualSha !== expectedSha) {
|
|
251
|
-
console.log(`[lpm] ERROR: SHA-256 mismatch for ${githubBinaryName}`)
|
|
252
|
-
console.log(
|
|
253
|
-
`[lpm] expected: ${expectedSha} (from signed manifest)`,
|
|
254
|
-
)
|
|
255
|
-
console.log(`[lpm] actual: ${actualSha}`)
|
|
256
|
-
console.log("[lpm] Refusing to install — strong tampering signal.")
|
|
257
|
-
safeUnlink(tmpPath)
|
|
258
|
-
return null
|
|
259
|
-
}
|
|
260
|
-
console.log(`[lpm] Verified SHA-256: ${actualSha}`)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
fs.renameSync(tmpPath, destPath)
|
|
265
|
-
fs.chmodSync(destPath, 0o755)
|
|
266
|
-
} catch (err) {
|
|
267
|
-
console.log(`[lpm] Could not install verified binary: ${err.message}`)
|
|
268
|
-
safeUnlink(tmpPath)
|
|
269
|
-
return null
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
console.log("[lpm] Native binary installed from GitHub Releases.")
|
|
273
|
-
return destPath
|
|
88
|
+
export function validateNativeBinary(
|
|
89
|
+
binaryPath,
|
|
90
|
+
{
|
|
91
|
+
expectedVersion = "",
|
|
92
|
+
env = process.env,
|
|
93
|
+
spawnSyncFn = spawnSync,
|
|
94
|
+
validateTimeoutMs = defaultValidateTimeoutMs,
|
|
95
|
+
} = {},
|
|
96
|
+
) {
|
|
97
|
+
let result;
|
|
98
|
+
try {
|
|
99
|
+
result = spawnSyncFn(binaryPath, ["--version"], {
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
101
|
+
encoding: "utf8",
|
|
102
|
+
env,
|
|
103
|
+
timeout: validateTimeoutMs,
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Native binary at ${binaryPath} could not be started: ${error.message}`,
|
|
108
|
+
{ cause: error },
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (result.error) {
|
|
113
|
+
const timeout =
|
|
114
|
+
result.error.code === "ETIMEDOUT"
|
|
115
|
+
? ` within ${Math.ceil(validateTimeoutMs / 1000)}s`
|
|
116
|
+
: "";
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Native binary at ${binaryPath} did not report its version${timeout}: ${result.error.message}`,
|
|
119
|
+
{ cause: result.error },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (result.signal) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Native binary at ${binaryPath} was interrupted by ${result.signal} while reporting its version.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result.status !== 0) {
|
|
130
|
+
const stderr = String(result.stderr ?? "").trim();
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Native binary at ${binaryPath} exited with status ${result.status} while reporting its version.${stderr ? ` ${stderr}` : ""}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
137
|
+
if (expectedVersion && !output.includes(expectedVersion)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Native binary at ${binaryPath} reported version ${JSON.stringify(output || "<empty>")} instead of ${expectedVersion}.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return output;
|
|
274
144
|
}
|
|
275
145
|
|
|
276
|
-
function
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
console.log("[lpm] Hard-linked native binary (zero Node.js overhead)")
|
|
291
|
-
} catch (err) {
|
|
292
|
-
if (fs.existsSync(backupPath) && !fs.existsSync(shimPath)) {
|
|
293
|
-
fs.copyFileSync(backupPath, shimPath)
|
|
294
|
-
}
|
|
295
|
-
console.log(`[lpm] Hard-link failed (${err.code}). Using JS wrapper.`)
|
|
296
|
-
}
|
|
146
|
+
export function stageOptimizedBinary({ source, target, platform, fsModule = fs }) {
|
|
147
|
+
fsModule.mkdirSync(path.dirname(target), { recursive: true });
|
|
148
|
+
if (platform === "win32") {
|
|
149
|
+
atomicCopy(source, target, fsModule);
|
|
150
|
+
return "copy";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
atomicLink(source, target, fsModule);
|
|
155
|
+
return "link";
|
|
156
|
+
} catch {
|
|
157
|
+
atomicCopy(source, target, fsModule);
|
|
158
|
+
return "copy";
|
|
159
|
+
}
|
|
297
160
|
}
|
|
298
161
|
|
|
299
|
-
function
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
fs.copyFileSync(binaryPath, destPath)
|
|
303
|
-
console.log("[lpm] Copied native binary to bin/")
|
|
304
|
-
} catch (err) {
|
|
305
|
-
console.log(`[lpm] Copy failed: ${err.message}. Using JS wrapper.`)
|
|
306
|
-
}
|
|
162
|
+
function optimizedTargetPath(wrapperRoot, platform, command) {
|
|
163
|
+
const filename = platform === "win32" ? `${command}.exe` : `${command}.js`;
|
|
164
|
+
return path.join(wrapperRoot, "bin", filename);
|
|
307
165
|
}
|
|
308
166
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
* cannot exhaust disk by serving an unbounded body. The 5-redirect cap
|
|
319
|
-
* is preserved from the pre-integrity-gate shape.
|
|
320
|
-
*
|
|
321
|
-
* Exported for testability.
|
|
322
|
-
*/
|
|
323
|
-
export function download(url, dest, redirects = 0, maxBytes = ASSET_MAX_BYTES) {
|
|
324
|
-
if (redirects > 5) return Promise.reject(new Error("Too many redirects"))
|
|
325
|
-
if (!url.startsWith("https://")) {
|
|
326
|
-
return Promise.reject(
|
|
327
|
-
new Error(
|
|
328
|
-
`refusing non-HTTPS transport: ${url} (Tier-2 download requires HTTPS end-to-end)`,
|
|
329
|
-
),
|
|
330
|
-
)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return new Promise((resolve, reject) => {
|
|
334
|
-
const req = https.get(url, res => {
|
|
335
|
-
if (
|
|
336
|
-
res.statusCode >= 300 &&
|
|
337
|
-
res.statusCode < 400 &&
|
|
338
|
-
res.headers.location
|
|
339
|
-
) {
|
|
340
|
-
res.resume()
|
|
341
|
-
let nextUrl
|
|
342
|
-
try {
|
|
343
|
-
nextUrl = new URL(res.headers.location, url).href
|
|
344
|
-
} catch {
|
|
345
|
-
reject(
|
|
346
|
-
new Error(`invalid redirect Location: ${res.headers.location}`),
|
|
347
|
-
)
|
|
348
|
-
return
|
|
349
|
-
}
|
|
350
|
-
resolve(download(nextUrl, dest, redirects + 1, maxBytes))
|
|
351
|
-
return
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (res.statusCode !== 200) {
|
|
355
|
-
const err = new Error(`HTTP ${res.statusCode}`)
|
|
356
|
-
err.statusCode = res.statusCode
|
|
357
|
-
res.resume()
|
|
358
|
-
reject(err)
|
|
359
|
-
return
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const advertised = Number(res.headers["content-length"])
|
|
363
|
-
if (Number.isFinite(advertised) && advertised > maxBytes) {
|
|
364
|
-
res.resume()
|
|
365
|
-
reject(
|
|
366
|
-
new Error(
|
|
367
|
-
`response advertises ${advertised} bytes; cap of ${maxBytes} would be exceeded`,
|
|
368
|
-
),
|
|
369
|
-
)
|
|
370
|
-
return
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
let received = 0
|
|
374
|
-
const out = fs.createWriteStream(dest)
|
|
375
|
-
res.on("data", chunk => {
|
|
376
|
-
received += chunk.length
|
|
377
|
-
if (received > maxBytes) {
|
|
378
|
-
out.destroy()
|
|
379
|
-
res.destroy()
|
|
380
|
-
safeUnlink(dest)
|
|
381
|
-
reject(
|
|
382
|
-
new Error(
|
|
383
|
-
`response body exceeded cap of ${maxBytes} bytes mid-stream`,
|
|
384
|
-
),
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
})
|
|
388
|
-
res.pipe(out)
|
|
389
|
-
out.on("finish", () => {
|
|
390
|
-
out.close()
|
|
391
|
-
resolve()
|
|
392
|
-
})
|
|
393
|
-
out.on("error", reject)
|
|
394
|
-
})
|
|
395
|
-
req.on("error", reject)
|
|
396
|
-
})
|
|
167
|
+
function readWrapperVersion(wrapperRoot, fsModule) {
|
|
168
|
+
try {
|
|
169
|
+
const manifest = JSON.parse(
|
|
170
|
+
fsModule.readFileSync(path.join(wrapperRoot, "package.json"), "utf8"),
|
|
171
|
+
);
|
|
172
|
+
return typeof manifest.version === "string" ? manifest.version : "";
|
|
173
|
+
} catch {
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
397
176
|
}
|
|
398
177
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
*
|
|
405
|
-
* HTTPS-only: identical transport-hardening posture as [`download`].
|
|
406
|
-
*
|
|
407
|
-
* Exported for testability.
|
|
408
|
-
*/
|
|
409
|
-
export function downloadToBuffer(url, maxBytes, redirects = 0) {
|
|
410
|
-
if (redirects > 5) return Promise.reject(new Error("Too many redirects"))
|
|
411
|
-
if (!url.startsWith("https://")) {
|
|
412
|
-
return Promise.reject(
|
|
413
|
-
new Error(
|
|
414
|
-
`refusing non-HTTPS transport: ${url} (Tier-2 download requires HTTPS end-to-end)`,
|
|
415
|
-
),
|
|
416
|
-
)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return new Promise((resolve, reject) => {
|
|
420
|
-
const req = https.get(url, res => {
|
|
421
|
-
if (
|
|
422
|
-
res.statusCode >= 300 &&
|
|
423
|
-
res.statusCode < 400 &&
|
|
424
|
-
res.headers.location
|
|
425
|
-
) {
|
|
426
|
-
res.resume()
|
|
427
|
-
let nextUrl
|
|
428
|
-
try {
|
|
429
|
-
nextUrl = new URL(res.headers.location, url).href
|
|
430
|
-
} catch {
|
|
431
|
-
reject(
|
|
432
|
-
new Error(`invalid redirect Location: ${res.headers.location}`),
|
|
433
|
-
)
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
resolve(downloadToBuffer(nextUrl, maxBytes, redirects + 1))
|
|
437
|
-
return
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (res.statusCode !== 200) {
|
|
441
|
-
const err = new Error(`HTTP ${res.statusCode}`)
|
|
442
|
-
err.statusCode = res.statusCode
|
|
443
|
-
res.resume()
|
|
444
|
-
reject(err)
|
|
445
|
-
return
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const advertised = Number(res.headers["content-length"])
|
|
449
|
-
if (Number.isFinite(advertised) && advertised > maxBytes) {
|
|
450
|
-
res.resume()
|
|
451
|
-
reject(
|
|
452
|
-
new Error(
|
|
453
|
-
`response advertises ${advertised} bytes; cap of ${maxBytes} would be exceeded`,
|
|
454
|
-
),
|
|
455
|
-
)
|
|
456
|
-
return
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const chunks = []
|
|
460
|
-
let received = 0
|
|
461
|
-
res.on("data", chunk => {
|
|
462
|
-
received += chunk.length
|
|
463
|
-
if (received > maxBytes) {
|
|
464
|
-
res.destroy()
|
|
465
|
-
reject(
|
|
466
|
-
new Error(
|
|
467
|
-
`response body exceeded cap of ${maxBytes} bytes mid-stream`,
|
|
468
|
-
),
|
|
469
|
-
)
|
|
470
|
-
return
|
|
471
|
-
}
|
|
472
|
-
chunks.push(chunk)
|
|
473
|
-
})
|
|
474
|
-
res.on("end", () => resolve(Buffer.concat(chunks)))
|
|
475
|
-
res.on("error", reject)
|
|
476
|
-
})
|
|
477
|
-
req.on("error", reject)
|
|
478
|
-
})
|
|
178
|
+
function atomicLink(source, target, fsModule) {
|
|
179
|
+
replaceWithTemp(target, fsModule, temp => {
|
|
180
|
+
fsModule.linkSync(source, temp);
|
|
181
|
+
fsModule.chmodSync(temp, 0o755);
|
|
182
|
+
});
|
|
479
183
|
}
|
|
480
184
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
*/
|
|
487
|
-
export function sha256File(filePath) {
|
|
488
|
-
return new Promise((resolve, reject) => {
|
|
489
|
-
const hash = crypto.createHash("sha256")
|
|
490
|
-
const stream = fs.createReadStream(filePath)
|
|
491
|
-
stream.on("data", chunk => hash.update(chunk))
|
|
492
|
-
stream.on("end", () => resolve(hash.digest("hex")))
|
|
493
|
-
stream.on("error", reject)
|
|
494
|
-
})
|
|
185
|
+
function atomicCopy(source, target, fsModule) {
|
|
186
|
+
replaceWithTemp(target, fsModule, temp => {
|
|
187
|
+
fsModule.copyFileSync(source, temp);
|
|
188
|
+
fsModule.chmodSync(temp, 0o755);
|
|
189
|
+
});
|
|
495
190
|
}
|
|
496
191
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (line.length === 0) continue
|
|
512
|
-
const idx = line.indexOf(" ")
|
|
513
|
-
if (idx !== 64) return null
|
|
514
|
-
const digest = line.slice(0, 64).toLowerCase()
|
|
515
|
-
if (!/^[0-9a-f]{64}$/.test(digest)) return null
|
|
516
|
-
const name = line.slice(idx + 2).trim()
|
|
517
|
-
if (name.length === 0) return null
|
|
518
|
-
if (name === filename) return digest
|
|
519
|
-
}
|
|
520
|
-
return null
|
|
192
|
+
function replaceWithTemp(target, fsModule, writeTemp) {
|
|
193
|
+
const temp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
194
|
+
let replaced = false;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
unlinkIfExists(temp, fsModule);
|
|
198
|
+
writeTemp(temp);
|
|
199
|
+
fsModule.renameSync(temp, target);
|
|
200
|
+
replaced = true;
|
|
201
|
+
} finally {
|
|
202
|
+
if (!replaced) {
|
|
203
|
+
unlinkIfExists(temp, fsModule);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
521
206
|
}
|
|
522
207
|
|
|
523
|
-
function
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
208
|
+
function unlinkIfExists(filePath, fsModule) {
|
|
209
|
+
try {
|
|
210
|
+
fsModule.unlinkSync(filePath);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error?.code !== "ENOENT") {
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
529
216
|
}
|
|
530
217
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
export async function tryVerifyManifestSignature(manifestBody, bundleBody) {
|
|
545
|
-
let sigstore
|
|
546
|
-
try {
|
|
547
|
-
sigstore = await import("sigstore")
|
|
548
|
-
} catch {
|
|
549
|
-
return "unavailable"
|
|
550
|
-
}
|
|
551
|
-
try {
|
|
552
|
-
const bundle = JSON.parse(bundleBody.toString("utf-8"))
|
|
553
|
-
await sigstore.verify(bundle, manifestBody, SIGSTORE_IDENTITY)
|
|
554
|
-
return "verified"
|
|
555
|
-
} catch {
|
|
556
|
-
return "rejected"
|
|
557
|
-
}
|
|
218
|
+
function printInstallError(error) {
|
|
219
|
+
if (error instanceof LpmWrapperError) {
|
|
220
|
+
console.error(`[lpm] ${error.message}`);
|
|
221
|
+
for (const hint of error.hints) {
|
|
222
|
+
console.error(`[lpm] ${hint}`);
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
console.error(`[lpm] Failed to install native binary: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.error(
|
|
229
|
+
"[lpm] Reinstall with optional dependencies and install scripts enabled: npm install -g @lpm-registry/cli",
|
|
230
|
+
);
|
|
558
231
|
}
|
|
559
232
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (invokedAsScript) {
|
|
568
|
-
main().catch(() => {
|
|
569
|
-
// Silently fail — JS CLI fallback will handle all commands.
|
|
570
|
-
})
|
|
233
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
234
|
+
try {
|
|
235
|
+
installNativeBinaries();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
printInstallError(error);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
571
240
|
}
|