@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,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
|
-
})
|