Files

434 lines
15 KiB
TypeScript
Raw Permalink Normal View History

2026-08-27 21:09:14 +00:00
import { spawn } from "bun";
import { describe, expect, it, test } from "bun:test";
import { bunEnv, bunExe, isASAN, tempDirWithFiles } from "harness";
import { join } from "node:path";
describe.concurrent("bun info", () => {
let i = 0;
function setupTest() {
const testDir = tempDirWithFiles("view-" + i++, {
"package.json": JSON.stringify({
// Since npm reserved the "fs" package name, we know that the "fs" package isn't going to update randomly later on.
name: "fs",
version: "1.0.0",
}),
});
return testDir;
}
async function runCommand(cmd: string[], testDir: string, expectSuccess = true) {
const { stdout, stderr, exited } = spawn({
cmd,
cwd: testDir,
stdout: "pipe",
stdin: "ignore",
stderr: "pipe",
env: bunEnv,
});
const [output, error, exitCode] = await Promise.all([stdout.text(), stderr.text(), exited]);
return { output, error, code: exitCode };
}
describe("bun info (main command)", () => {
it("should display package info for latest version", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "info", "is-number"], testDir);
expect(code).toBe(0);
expect(error).toBe("");
expect(output).toContain("is-number@");
expect(output).toContain("Returns true if a number"); // Part of the package description
expect(output).toContain("maintainers:");
});
it("should display package info for specific version", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "info", "[email protected]"], testDir);
expect(code).toBe(0);
expect(output).toMatchInlineSnapshot(`
"[email protected] | MIT | deps: 0 | versions: 15
Returns true if a number or string value is a finite number. Useful for regex matches, parsing, user input, etc.
https://github.com/jonschlinkert/is-number
keywords: cast, check, coerce, coercion, finite, integer, is, isnan, is-nan, is-num, is-number, isnumber, isfinite, istype, kind, math, nan, num, number, numeric, parseFloat, parseInt, test, type, typeof, value
dist
.tarball: https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz
.shasum: 7535345b896734d5f80c4d06c50955527a14f12b
.integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
.unpackedSize: 9.62 KB
dist-tags:
latest: 7.0.0
maintainers:
- doowb <[email protected]>
- jonschlinkert <[email protected]>
- realityking <[email protected]>
Published: 2018-07-04T15:08:58.238Z
"
`);
});
it("should display specific property", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "info", "@types/bun", "name"], testDir);
expect(error).toBe("");
expect(output.trim().length).toBeGreaterThan(0);
expect(output).toMatchInlineSnapshot(`
"@types/bun
"
`);
expect(code).toBe(0);
});
it("should handle missing arguments", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "info"], testDir, false);
expect(output).toMatchInlineSnapshot(`
"[email protected] | ISC | deps: 0 | versions: 3
This package name is not currently in use, but was formerly occupied by another package. To avoid malicious use, npm is hanging on to the package name, but loosely, and we'll probably give it to you if you want it.
https://github.com/npm/security-holder#readme
dist
.tarball: https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz
.shasum: 8a7bd37186b6dddf3813f23858b57ecaaf5e41d4
.integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==
dist-tags:
latest: 0.0.1-security
maintainers:
- npm <[email protected]>
Published: 2016-08-23T17:56:58.976Z
"
`);
expect(code).toBe(0);
});
});
describe("bun pm view (alias)", () => {
it("should display package info for latest version", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "is-number"], testDir);
expect(code).toBe(0);
expect(error).toBe("");
expect(output).toContain("is-number@");
expect(output).toContain("Returns true if a number"); // Part of the package description
expect(output).toContain("maintainers:");
});
it("should display package info for specific version", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "[email protected]"], testDir);
expect(code).toBe(0);
expect(output).toMatchInlineSnapshot(`
"[email protected] | MIT | deps: 0 | versions: 15
Returns true if a number or string value is a finite number. Useful for regex matches, parsing, user input, etc.
https://github.com/jonschlinkert/is-number
keywords: cast, check, coerce, coercion, finite, integer, is, isnan, is-nan, is-num, is-number, isnumber, isfinite, istype, kind, math, nan, num, number, numeric, parseFloat, parseInt, test, type, typeof, value
dist
.tarball: https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz
.shasum: 7535345b896734d5f80c4d06c50955527a14f12b
.integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
.unpackedSize: 9.62 KB
dist-tags:
latest: 7.0.0
maintainers:
- doowb <[email protected]>
- jonschlinkert <[email protected]>
- realityking <[email protected]>
Published: 2018-07-04T15:08:58.238Z
"
`);
});
it("should display specific property", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "@types/bun", "name"], testDir);
expect(error).toBe("");
expect(output.trim().length).toBeGreaterThan(0);
expect(output).toMatchInlineSnapshot(`
"@types/bun
"
`);
expect(code).toBe(0);
});
it("should display nested property", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand(
[bunExe(), "pm", "view", "is-number", "repository.url"],
testDir,
);
expect(code).toBe(0);
expect(error).toBe("");
expect(output.trim()).toContain("https://");
});
// TODO: JSON output needs to be fixed to show specific version data, not full registry manifest
it("should output JSON format with --json flag", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "[email protected]", "--json"], testDir);
expect(code).toBe(0);
expect(error).toBe("");
// Parse the JSON to verify it's valid
const json = JSON.parse(output);
expect(json).toMatchObject({
name: "is-number",
version: "7.0.0",
description:
"Returns true if a number or string value is a finite number. Useful for regex matches, parsing, user input, etc.",
license: "MIT",
homepage: "https://github.com/jonschlinkert/is-number",
author: {
name: "Jon Schlinkert",
url: "https://github.com/jonschlinkert",
},
repository: {
type: "git",
url: expect.stringContaining("github.com/jonschlinkert/is-number"),
},
main: "index.js",
engines: {
node: ">=0.12.0",
},
});
});
it("should handle non-existent package", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand(
[bunExe(), "pm", "view", "nonexistent-package-12345"],
testDir,
false,
);
expect(code).toBe(1);
expect(error).toContain("Not Found");
expect(output).toBe("");
});
// TODO: Version validation needs to be fixed - currently falls back to first version instead of failing
it("should handle non-existent version", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "[email protected]"], testDir, false);
expect(error).toMatchInlineSnapshot(`
"error: No version of "is-number" satisfying "999.0.0" found
Recent versions:
- 4.0.0
- 5.0.0
- 6.0.0
- 7.0.0
- 7.0.0
... and 11 more
"
`);
expect(code).toBe(1);
});
it("should handle non-existent property", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand(
[bunExe(), "pm", "view", "is-number", "nonexistent"],
testDir,
false,
);
expect(error).toMatchInlineSnapshot(`
"error: Property nonexistent not found
"
`);
expect(code).toBe(1);
});
it("should handle malformed package specifier", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "@"], testDir, false);
expect(code).toBe(1);
expect(error).toContain("Method Not Allowed");
expect(output).toBe("");
});
it("should handle scoped packages", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "@types/semver"], testDir);
expect(code).toBe(0);
expect(error).toBe("");
expect(output).toContain("@types/semver@");
expect(output).toContain("TypeScript definitions");
});
it("should handle .", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "."], testDir, false);
expect(output).toMatchInlineSnapshot(`
"[email protected] | ISC | deps: 0 | versions: 3
This package name is not currently in use, but was formerly occupied by another package. To avoid malicious use, npm is hanging on to the package name, but loosely, and we'll probably give it to you if you want it.
https://github.com/npm/security-holder#readme
dist
.tarball: https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz
.shasum: 8a7bd37186b6dddf3813f23858b57ecaaf5e41d4
.integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==
dist-tags:
latest: 0.0.1-security
maintainers:
- npm <[email protected]>
Published: 2016-08-23T17:56:58.976Z
"
`);
expect(code).toBe(0);
});
it("should handle version ranges", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "is-number@^7.0.0"], testDir);
expect(code).toBe(0);
expect(error).toBe("");
expect(output).toContain("is-number@7.");
expect(output).toContain("Returns true if a number");
});
it("versions should work", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "fs", "versions"], testDir);
expect(error).toBe("");
expect(output).toMatchInlineSnapshot(`
"[
"0.0.1-security",
"0.0.0",
"0.0.2"
]
"
`);
expect(code).toBe(0);
});
it("versions[0] should work", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "fs", "versions[0]"], testDir);
expect(error).toBe("");
expect(output).toMatchInlineSnapshot(`
"0.0.1-security
"
`);
expect(code).toBe(0);
});
it("should handle dist-tags like latest", async () => {
const testDir = await setupTest();
const { output, error, code } = await runCommand([bunExe(), "pm", "view", "fs@latest"], testDir);
expect(code).toBe(0);
expect(error).toBe("");
expect(output).toContain("0.0.1-security"); // latest should resolve to 7.0.0
expect(output).toMatchInlineSnapshot(`
"[email protected] | ISC | deps: 0 | versions: 3
This package name is not currently in use, but was formerly occupied by another package. To avoid malicious use, npm is hanging on to the package name, but loosely, and we'll probably give it to you if you want it.
https://github.com/npm/security-holder#readme
dist
.tarball: https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz
.shasum: 8a7bd37186b6dddf3813f23858b57ecaaf5e41d4
.integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==
dist-tags:
latest: 0.0.1-security
maintainers:
- npm <[email protected]>
Published: 2016-08-23T17:56:58.976Z
"
`);
});
});
});
// LSan's default conservative scan only flags the `send_sync` response-metadata
// leak when no idle thread parks with a stale pointer in a callee-saved
// register; excluding registers as roots makes the check deterministic.
test.skipIf(!isASAN)(
"bun info does not leak the sync response metadata",
async () => {
const manifest = JSON.stringify({
name: "leakpkg",
"dist-tags": { latest: "0.0.1" },
versions: {
"0.0.1": {
name: "leakpkg",
version: "0.0.1",
dist: { tarball: "http://localhost/leakpkg-0.0.1.tgz", shasum: "0".repeat(40) },
},
},
time: { "0.0.1": "2020-01-01T00:00:00.000Z" },
});
await using server = Bun.serve({
port: 0,
fetch() {
return new Response(manifest, { headers: { "content-type": "application/json" } });
},
});
const dir = tempDirWithFiles("bun-info-lsan", {
"package.json": JSON.stringify({ name: "test", version: "1.0.0" }),
"bunfig.toml": Bun.TOML.stringify({
install: {
registry: `http://localhost:${server.port}/`,
},
}),
});
await using proc = spawn({
cmd: [bunExe(), "info", "leakpkg", "name"],
cwd: dir,
env: {
...bunEnv,
http_proxy: "",
https_proxy: "",
HTTP_PROXY: "",
HTTPS_PROXY: "",
ASAN_OPTIONS: [bunEnv.ASAN_OPTIONS, "detect_leaks=1"].filter(Boolean).join(":"),
LSAN_OPTIONS: `use_registers=0:print_suppressions=0:suppressions=${join(import.meta.dirname, "../../leaksan.supp")}`,
},
stdout: "pipe",
stderr: "pipe",
stdin: "ignore",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout: stdout.trim(), stderr, exitCode }).toEqual({
stdout: "leakpkg",
stderr: expect.not.stringContaining("LeakSanitizer: detected memory leaks"),
exitCode: 0,
});
// Timeout: a detected leak makes llvm-symbolizer load the full DWARF of the
// test binary before the assertion above can run; the no-leak path is < 1s.
},
30_000,
);