@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.
Files changed (62) hide show
  1. package/README.md +25 -44
  2. package/bin/lpm.js +2 -354
  3. package/bin/lpx.js +4 -0
  4. package/bin/native.js +154 -0
  5. package/package.json +42 -82
  6. package/scripts/install-binary.js +219 -550
  7. package/CHANGELOG.md +0 -197
  8. package/LICENSE +0 -15
  9. package/index.d.ts +0 -131
  10. package/index.js +0 -31
  11. package/lib/api.js +0 -398
  12. package/lib/commands/add.js +0 -1217
  13. package/lib/commands/audit.js +0 -283
  14. package/lib/commands/cache.js +0 -209
  15. package/lib/commands/check-name.js +0 -112
  16. package/lib/commands/config.js +0 -186
  17. package/lib/commands/doctor.js +0 -140
  18. package/lib/commands/info.js +0 -215
  19. package/lib/commands/init.js +0 -146
  20. package/lib/commands/install.js +0 -264
  21. package/lib/commands/login.js +0 -595
  22. package/lib/commands/logout.js +0 -94
  23. package/lib/commands/marketplace-compare.js +0 -164
  24. package/lib/commands/marketplace-earnings.js +0 -89
  25. package/lib/commands/mcp-setup.js +0 -363
  26. package/lib/commands/npmrc.js +0 -178
  27. package/lib/commands/open.js +0 -82
  28. package/lib/commands/outdated.js +0 -291
  29. package/lib/commands/pool-stats.js +0 -100
  30. package/lib/commands/publish.js +0 -949
  31. package/lib/commands/quality.js +0 -211
  32. package/lib/commands/remove.js +0 -82
  33. package/lib/commands/run.js +0 -14
  34. package/lib/commands/search.js +0 -143
  35. package/lib/commands/setup.js +0 -221
  36. package/lib/commands/skills.js +0 -898
  37. package/lib/commands/token-rotate.js +0 -25
  38. package/lib/commands/uninstall.js +0 -107
  39. package/lib/commands/whoami.js +0 -129
  40. package/lib/config.js +0 -328
  41. package/lib/constants.js +0 -190
  42. package/lib/ecosystem.js +0 -501
  43. package/lib/editor-skills.js +0 -321
  44. package/lib/editors.js +0 -215
  45. package/lib/import-rewriter.js +0 -364
  46. package/lib/install-targets/mcp-server.js +0 -245
  47. package/lib/install-targets/vscode-extension.js +0 -178
  48. package/lib/install-targets.js +0 -82
  49. package/lib/integrity.js +0 -179
  50. package/lib/lpm-config-prompts.js +0 -102
  51. package/lib/lpm-config.js +0 -408
  52. package/lib/oidc.js +0 -166
  53. package/lib/project-utils.js +0 -152
  54. package/lib/quality/checks.js +0 -687
  55. package/lib/quality/display.js +0 -155
  56. package/lib/quality/score.js +0 -118
  57. package/lib/quality/swift-checks.js +0 -447
  58. package/lib/safe-path.js +0 -180
  59. package/lib/secure-store.js +0 -321
  60. package/lib/swift-project.js +0 -637
  61. package/lib/ui.js +0 -40
  62. package/scripts/__tests__/install-binary.test.js +0 -346
@@ -1,346 +0,0 @@
1
- /**
2
- * Tier-2 integrity-gate tests for scripts/install-binary.js.
3
- *
4
- * The suite mocks the network primitives (`download`, `downloadToBuffer`,
5
- * `sha256File`, `tryVerifyManifestSignature`) via the dependency-injection
6
- * `deps` parameter on `tryGitHubDownload`. No real network calls fire.
7
- *
8
- * Coverage:
9
- * - happy path: manifest SHA matches → binary installed.
10
- * - manifest 404 → return null (falls through to JS CLI fallback).
11
- * - SHA mismatch → return null + tmp file cleaned up.
12
- * - manifest missing the platform binary entry → return null.
13
- * - Sigstore verifier rejected → return null.
14
- * - Sigstore package unavailable → SHA-only path runs, WARN logged.
15
- * - Sigstore verified → both gates surface.
16
- * - LPM_INSTALL_INSECURE=1 → all integrity skipped, install proceeds.
17
- *
18
- * Pure-helper coverage:
19
- * - parseManifestEntry: canonical, malformed, missing-platform.
20
- */
21
-
22
- import crypto from "node:crypto"
23
- import fs from "node:fs"
24
- import os from "node:os"
25
- import path from "node:path"
26
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
27
-
28
- import {
29
- download,
30
- downloadToBuffer,
31
- parseManifestEntry,
32
- tryGitHubDownload,
33
- } from "../install-binary.js"
34
-
35
- function sha256(bytes) {
36
- return crypto.createHash("sha256").update(bytes).digest("hex")
37
- }
38
-
39
- describe("parseManifestEntry", () => {
40
- it("returns the SHA for a matching filename", () => {
41
- const manifest =
42
- "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa lpm-darwin-arm64\n" +
43
- "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb lpm-linux-x64\n"
44
- expect(parseManifestEntry(manifest, "lpm-linux-x64")).toBe(
45
- "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
46
- )
47
- })
48
-
49
- it("returns null when the filename is not enumerated", () => {
50
- const manifest =
51
- "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa lpm-darwin-arm64\n"
52
- expect(parseManifestEntry(manifest, "lpm-linux-x64")).toBeNull()
53
- })
54
-
55
- it("returns null on malformed lines (short digest)", () => {
56
- const manifest = "abc lpm-linux-x64\n"
57
- expect(parseManifestEntry(manifest, "lpm-linux-x64")).toBeNull()
58
- })
59
-
60
- it("returns null on malformed lines (non-hex)", () => {
61
- const manifest =
62
- "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz lpm-linux-x64\n"
63
- expect(parseManifestEntry(manifest, "lpm-linux-x64")).toBeNull()
64
- })
65
-
66
- it("returns null on malformed lines (single-space separator)", () => {
67
- const manifest =
68
- "0000000000000000000000000000000000000000000000000000000000000000 lpm-linux-x64\n"
69
- expect(parseManifestEntry(manifest, "lpm-linux-x64")).toBeNull()
70
- })
71
-
72
- it("strips trailing carriage returns for Windows-format manifests", () => {
73
- const manifest =
74
- "1111111111111111111111111111111111111111111111111111111111111111 lpm-darwin-arm64\r\n"
75
- expect(parseManifestEntry(manifest, "lpm-darwin-arm64")).toBe(
76
- "1111111111111111111111111111111111111111111111111111111111111111",
77
- )
78
- })
79
-
80
- it("lowercases hex digests", () => {
81
- const manifest =
82
- "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890 X\n"
83
- expect(parseManifestEntry(manifest, "X")).toBe(
84
- "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
85
- )
86
- })
87
- })
88
-
89
- describe("tryGitHubDownload", () => {
90
- let tmpBinDir
91
- let assetBytes
92
- let assetSha
93
- let manifestText
94
- let priorInsecure
95
-
96
- beforeEach(() => {
97
- tmpBinDir = fs.mkdtempSync(path.join(os.tmpdir(), "lpm-install-binary-"))
98
- assetBytes = Buffer.from("native-binary-fixture-bytes")
99
- assetSha = sha256(assetBytes)
100
- manifestText = `${assetSha} lpm-linux-x64\n`
101
- priorInsecure = process.env.LPM_INSTALL_INSECURE
102
- delete process.env.LPM_INSTALL_INSECURE
103
- // Stub console.log so the test output stays clean. Replace
104
- // per-test so individual cases can re-bind to capture lines.
105
- vi.spyOn(console, "log").mockImplementation(() => {})
106
- })
107
-
108
- afterEach(() => {
109
- fs.rmSync(tmpBinDir, { recursive: true, force: true })
110
- if (priorInsecure === undefined) {
111
- delete process.env.LPM_INSTALL_INSECURE
112
- } else {
113
- process.env.LPM_INSTALL_INSECURE = priorInsecure
114
- }
115
- vi.restoreAllMocks()
116
- })
117
-
118
- function mkDeps(overrides = {}) {
119
- return {
120
- destDir: tmpBinDir,
121
- download: vi.fn(async (_url, dest) => {
122
- fs.writeFileSync(dest, assetBytes)
123
- }),
124
- downloadToBuffer: vi.fn(async url => {
125
- if (url.endsWith("SHA256SUMS.txt")) {
126
- return Buffer.from(manifestText, "utf-8")
127
- }
128
- if (url.endsWith("SHA256SUMS.txt.sigstore")) {
129
- return Buffer.from("dummy-bundle-bytes")
130
- }
131
- throw Object.assign(new Error("not mocked"), { statusCode: 404 })
132
- }),
133
- sha256File: vi.fn(async filePath => sha256(fs.readFileSync(filePath))),
134
- tryVerifyManifestSignature: vi.fn(async () => "unavailable"),
135
- ...overrides,
136
- }
137
- }
138
-
139
- it("installs the verified binary when SHA matches and Sigstore is unavailable", async () => {
140
- const deps = mkDeps()
141
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
142
- expect(result).toBeTruthy()
143
- expect(fs.existsSync(result)).toBe(true)
144
- // The installed bytes must match the verified bytes — guards
145
- // against a regression that "verifies" but installs garbage.
146
- expect(fs.readFileSync(result)).toEqual(assetBytes)
147
- expect(deps.downloadToBuffer).toHaveBeenCalledTimes(2)
148
- expect(deps.sha256File).toHaveBeenCalledTimes(1)
149
- })
150
-
151
- it("returns null when the manifest is 404 (predates signed-install gate)", async () => {
152
- const deps = mkDeps({
153
- downloadToBuffer: vi.fn(async url => {
154
- if (url.endsWith("SHA256SUMS.txt")) {
155
- throw Object.assign(new Error("HTTP 404"), { statusCode: 404 })
156
- }
157
- return Buffer.from("never reached")
158
- }),
159
- })
160
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
161
- expect(result).toBeNull()
162
- // Asset must NOT have been fetched once we know the manifest
163
- // is missing — releases that predate the gate are install-via-
164
- // manual-link, not silently downgraded to unsigned.
165
- expect(deps.download).not.toHaveBeenCalled()
166
- })
167
-
168
- it("returns null and cleans up the tmp file when SHA mismatches", async () => {
169
- const wrongSha =
170
- "0000000000000000000000000000000000000000000000000000000000000000"
171
- const deps = mkDeps({
172
- downloadToBuffer: vi.fn(async url => {
173
- if (url.endsWith("SHA256SUMS.txt")) {
174
- return Buffer.from(`${wrongSha} lpm-linux-x64\n`, "utf-8")
175
- }
176
- return Buffer.from("dummy-bundle-bytes")
177
- }),
178
- })
179
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
180
- expect(result).toBeNull()
181
- expect(deps.download).toHaveBeenCalledTimes(1)
182
- // destDir must be empty — the rejected binary's tmp file must be
183
- // cleaned up so no partially-downloaded asset survives the gate.
184
- expect(fs.readdirSync(tmpBinDir)).toEqual([])
185
- })
186
-
187
- it("returns null when the manifest does not enumerate the platform binary", async () => {
188
- const deps = mkDeps({
189
- downloadToBuffer: vi.fn(async url => {
190
- if (url.endsWith("SHA256SUMS.txt")) {
191
- return Buffer.from(`${assetSha} lpm-some-other-platform\n`, "utf-8")
192
- }
193
- return Buffer.from("dummy-bundle-bytes")
194
- }),
195
- })
196
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
197
- expect(result).toBeNull()
198
- })
199
-
200
- it("returns null when Sigstore verifier explicitly rejects the bundle", async () => {
201
- const deps = mkDeps({
202
- tryVerifyManifestSignature: vi.fn(async () => "rejected"),
203
- })
204
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
205
- expect(result).toBeNull()
206
- expect(deps.download).not.toHaveBeenCalled()
207
- })
208
-
209
- it("degrades to SHA-only with WARN when sigstore package is unavailable", async () => {
210
- const logSpy = vi.spyOn(console, "log")
211
- const deps = mkDeps({
212
- tryVerifyManifestSignature: vi.fn(async () => "unavailable"),
213
- })
214
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
215
- expect(result).toBeTruthy()
216
- const warned = logSpy.mock.calls.some(args =>
217
- String(args[0] ?? "").includes(
218
- "optional 'sigstore' package not available",
219
- ),
220
- )
221
- expect(warned).toBe(true)
222
- })
223
-
224
- it("emits Sigstore verified message when verifier succeeds", async () => {
225
- const logSpy = vi.spyOn(console, "log")
226
- const deps = mkDeps({
227
- tryVerifyManifestSignature: vi.fn(async () => "verified"),
228
- })
229
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
230
- expect(result).toBeTruthy()
231
- const verifiedLine = logSpy.mock.calls.some(args =>
232
- String(args[0] ?? "").includes("Verified Sigstore signature on manifest"),
233
- )
234
- expect(verifiedLine).toBe(true)
235
- })
236
-
237
- it("skips ALL integrity checks under LPM_INSTALL_INSECURE=1 with a loud WARN", async () => {
238
- process.env.LPM_INSTALL_INSECURE = "1"
239
- const logSpy = vi.spyOn(console, "log")
240
- // downloadToBuffer must NOT be called when insecure mode is on —
241
- // the manifest fetch is part of the integrity flow that gets
242
- // skipped wholesale.
243
- const deps = mkDeps({
244
- downloadToBuffer: vi.fn(async () => {
245
- throw new Error(
246
- "manifest fetch must not run under LPM_INSTALL_INSECURE=1",
247
- )
248
- }),
249
- })
250
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
251
- expect(result).toBeTruthy()
252
- expect(fs.readFileSync(result)).toEqual(assetBytes)
253
- const warned = logSpy.mock.calls.some(args =>
254
- String(args[0] ?? "").includes("LPM_INSTALL_INSECURE=1"),
255
- )
256
- expect(warned).toBe(true)
257
- expect(deps.downloadToBuffer).not.toHaveBeenCalled()
258
- })
259
-
260
- it("returns null and cleans up if the asset download fails", async () => {
261
- const deps = mkDeps({
262
- download: vi.fn(async () => {
263
- throw new Error("network down")
264
- }),
265
- })
266
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
267
- expect(result).toBeNull()
268
- })
269
-
270
- it("treats a missing Sigstore bundle as opportunistic skip (SHA gate still runs)", async () => {
271
- const logSpy = vi.spyOn(console, "log")
272
- // Bundle fetch fails; manifest succeeds. SHA gate still runs.
273
- const deps = mkDeps({
274
- downloadToBuffer: vi.fn(async url => {
275
- if (url.endsWith("SHA256SUMS.txt")) {
276
- return Buffer.from(manifestText, "utf-8")
277
- }
278
- throw Object.assign(new Error("HTTP 404"), { statusCode: 404 })
279
- }),
280
- tryVerifyManifestSignature: vi.fn(async () => {
281
- throw new Error("must not be called when bundle is absent")
282
- }),
283
- })
284
- const result = await tryGitHubDownload("0.42.0", "lpm-linux-x64", deps)
285
- expect(result).toBeTruthy()
286
- expect(deps.tryVerifyManifestSignature).not.toHaveBeenCalled()
287
- // SHA-only path was the floor — no Sigstore line printed.
288
- const sigstoreLine = logSpy.mock.calls.some(args =>
289
- String(args[0] ?? "").includes("Verified Sigstore signature"),
290
- )
291
- expect(sigstoreLine).toBe(false)
292
- })
293
- })
294
-
295
- /**
296
- * HTTPS-only transport hardening. The integrity gate's SHA / Sigstore
297
- * verification only protects what's on the wire — a redirect that
298
- * downgrades transport to plaintext would let an on-path attacker
299
- * intercept the bytes that later get hashed and force the unsigned-
300
- * fallback warning path.
301
- *
302
- * Mitigation has two layers:
303
- *
304
- * 1. Entry check on both helpers refuses any non-`https://` URL.
305
- * 2. The redirect handler re-enters the same function with the
306
- * resolved Location, so a `Location: http://…` triggers the
307
- * same entry check on the recursive call. No real HTTPS server
308
- * is needed to prove this — the property is structural.
309
- *
310
- * A regression guard asserts that `node:http` is no longer imported,
311
- * so the dual-client selector that triggered the audit finding can't
312
- * silently come back via a future refactor.
313
- */
314
- describe("Tier-2 transport hardening (HTTPS-only)", () => {
315
- it("download() rejects non-HTTPS initial URLs", async () => {
316
- await expect(download("http://example.com/x", "/tmp/x")).rejects.toThrow(
317
- /refusing non-HTTPS transport/,
318
- )
319
- })
320
-
321
- it("downloadToBuffer() rejects non-HTTPS initial URLs", async () => {
322
- await expect(
323
- downloadToBuffer("http://example.com/x", 1024),
324
- ).rejects.toThrow(/refusing non-HTTPS transport/)
325
- })
326
-
327
- it("rejects ftp://, file://, javascript:, and other non-https schemes", async () => {
328
- for (const scheme of ["ftp", "file", "javascript", "data"]) {
329
- await expect(
330
- downloadToBuffer(`${scheme}://payload`, 1024),
331
- ).rejects.toThrow(/refusing non-HTTPS transport/)
332
- }
333
- })
334
-
335
- it("does not import node:http (regression guard for the dual-client selector)", () => {
336
- const src = fs.readFileSync(
337
- path.join(
338
- path.dirname(new URL(import.meta.url).pathname),
339
- "..",
340
- "install-binary.js",
341
- ),
342
- "utf-8",
343
- )
344
- expect(src).not.toMatch(/from\s+["']node:http["']/)
345
- })
346
- })