@lpm-registry/cli 0.57.0 → 0.59.0

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 +21 -45
  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 +40 -82
  6. package/CHANGELOG.md +0 -197
  7. package/LICENSE +0 -15
  8. package/index.d.ts +0 -131
  9. package/index.js +0 -31
  10. package/lib/api.js +0 -398
  11. package/lib/commands/add.js +0 -1217
  12. package/lib/commands/audit.js +0 -283
  13. package/lib/commands/cache.js +0 -209
  14. package/lib/commands/check-name.js +0 -112
  15. package/lib/commands/config.js +0 -186
  16. package/lib/commands/doctor.js +0 -140
  17. package/lib/commands/info.js +0 -215
  18. package/lib/commands/init.js +0 -146
  19. package/lib/commands/install.js +0 -264
  20. package/lib/commands/login.js +0 -595
  21. package/lib/commands/logout.js +0 -94
  22. package/lib/commands/marketplace-compare.js +0 -164
  23. package/lib/commands/marketplace-earnings.js +0 -89
  24. package/lib/commands/mcp-setup.js +0 -363
  25. package/lib/commands/npmrc.js +0 -178
  26. package/lib/commands/open.js +0 -82
  27. package/lib/commands/outdated.js +0 -291
  28. package/lib/commands/pool-stats.js +0 -100
  29. package/lib/commands/publish.js +0 -949
  30. package/lib/commands/quality.js +0 -211
  31. package/lib/commands/remove.js +0 -82
  32. package/lib/commands/run.js +0 -14
  33. package/lib/commands/search.js +0 -143
  34. package/lib/commands/setup.js +0 -221
  35. package/lib/commands/skills.js +0 -898
  36. package/lib/commands/token-rotate.js +0 -25
  37. package/lib/commands/uninstall.js +0 -107
  38. package/lib/commands/whoami.js +0 -129
  39. package/lib/config.js +0 -328
  40. package/lib/constants.js +0 -190
  41. package/lib/ecosystem.js +0 -501
  42. package/lib/editor-skills.js +0 -321
  43. package/lib/editors.js +0 -215
  44. package/lib/import-rewriter.js +0 -364
  45. package/lib/install-targets/mcp-server.js +0 -245
  46. package/lib/install-targets/vscode-extension.js +0 -178
  47. package/lib/install-targets.js +0 -82
  48. package/lib/integrity.js +0 -179
  49. package/lib/lpm-config-prompts.js +0 -102
  50. package/lib/lpm-config.js +0 -408
  51. package/lib/oidc.js +0 -166
  52. package/lib/project-utils.js +0 -152
  53. package/lib/quality/checks.js +0 -687
  54. package/lib/quality/display.js +0 -155
  55. package/lib/quality/score.js +0 -118
  56. package/lib/quality/swift-checks.js +0 -447
  57. package/lib/safe-path.js +0 -180
  58. package/lib/secure-store.js +0 -321
  59. package/lib/swift-project.js +0 -637
  60. package/lib/ui.js +0 -40
  61. package/scripts/__tests__/install-binary.test.js +0 -346
  62. package/scripts/install-binary.js +0 -571
@@ -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
- })