@hyrious/bun.lockb
Version:
Parse and print bun.lockb
331 lines (330 loc) • 12.1 kB
JavaScript
// src/lockb.ts
function parse(buf) {
let pos = 0;
let view = buf instanceof ArrayBuffer ? new DataView(buf) : new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
const header_bytes = new TextEncoder().encode("#!/usr/bin/env bun\nbun-lockfile-format-v0\n");
const u32 = () => {
if (pos + 4 > view.byteLength) throw new TypeError("too short");
return view.getUint32((pos += 4) - 4, true);
};
const u64 = () => {
if (pos + 8 > view.byteLength) throw new TypeError("too short");
const a = view.getUint32((pos += 4) - 4, true);
const b = view.getUint32((pos += 4) - 4, true);
return a + b * 2 ** 32;
};
const to_u32 = (a) => {
if (a.byteOffset % 4 === 0) {
return new Uint32Array(a.buffer, a.byteOffset, a.byteLength / 4);
} else {
const view2 = new DataView(a.buffer, a.byteOffset, a.byteLength);
return Uint32Array.from({ length: a.byteLength / 4 }, (_, i) => view2.getUint32(i * 4, true));
}
};
const read = (n) => {
if (pos + n > view.byteLength) throw new TypeError("too short");
return new Uint8Array(view.buffer, view.byteOffset + (pos += n) - n, n);
};
const eq = (a, b) => {
if (a.byteLength !== b.byteLength) return false;
for (let i = a.byteLength - 1; i >= 0; i--) {
if (a[i] !== b[i]) return false;
}
return true;
};
const assert = (truthy, message = "assert failed") => {
if (truthy) return;
throw new TypeError(message);
};
const header_buf = read(header_bytes.byteLength);
assert(eq(header_buf, header_bytes), "invalid lockfile");
const format = u32();
assert(format === 2, "outdated lockfile version");
const meta_hash = read(32);
const end = u64();
assert(end <= view.byteLength, "lockfile is missing data");
const list_len = u64();
assert(list_len < 2 ** 32, "lockfile validation failed: list is impossibly long");
const input_alignment = u64();
assert(input_alignment === 8);
const field_count = u64();
assert(field_count === 8);
const begin_at = u64();
const end_at = u64();
assert(begin_at <= end && end_at <= end && begin_at <= end_at, "lockfile validation failed: invalid package list range");
pos = begin_at;
const packages = Object.entries({
name: 8,
name_hash: 8,
resolution: 64,
dependencies: 8,
resolutions: 8,
meta: 88,
bin: 20,
scripts: 48
}).reduce((list, [field, len]) => {
const data = read(len * list_len);
list.forEach((a, i) => {
a[field] = data.subarray(i * len, i * len + len);
});
return list;
}, Array.from({ length: list_len }, () => ({})));
pos = end_at;
const buffers = [
"trees",
"hoisted_dependencies",
"resolutions",
// u32[]
"dependencies",
// name(8) + name_hash(8) + behavior(1) + tag(1) + literal(8) = 26[]
"extern_strings",
"string_bytes"
].reduce((a, key) => {
const start = u64();
const end2 = u64();
pos = start;
a[key] = read(end2 - start);
pos = end2;
return a;
}, {});
const decoder = new TextDecoder();
const str = (a) => {
if ((a[7] & 128) === 0) {
let i = a.indexOf(0);
if (i >= 0) a = a.subarray(0, i);
return decoder.decode(a);
} else {
let [off, len] = to_u32(a);
len &= ~2147483648;
return decoder.decode(buffers.string_bytes.subarray(off, off + len));
}
};
const requested_versions = new Array(list_len);
requested_versions[0] = [];
for (let i = 1; i < list_len; i++) {
let resolutions = to_u32(buffers.resolutions.subarray());
let dependencies = buffers.dependencies.subarray();
let k = -1;
let all_requested_versions = [];
while ((k = resolutions.indexOf(i)) >= 0) {
all_requested_versions.push(dependencies.subarray(k * 26, k * 26 + 26));
dependencies = dependencies.subarray(k * 26 + 26);
resolutions = resolutions.subarray(k + 1);
}
requested_versions[i] = all_requested_versions;
}
const hex = (a) => (256 + a).toString(16).slice(1);
const fmt_hash = (a) => {
if (a.byteLength < 32) throw new TypeError("meta_hash too short");
let hash = "";
for (let i = 0; i < 32; i++) {
let c = hex(a[i]);
if (i < 8 || 16 <= i && i < 24) c = c.toUpperCase();
hash += c;
if (i < 31 && (i + 1) % 8 === 0) hash += "-";
}
return hash;
};
let ResolutionTag;
((ResolutionTag2) => {
ResolutionTag2[ResolutionTag2["uninitialized"] = 0] = "uninitialized";
ResolutionTag2[ResolutionTag2["root"] = 1] = "root";
ResolutionTag2[ResolutionTag2["npm"] = 2] = "npm";
ResolutionTag2[ResolutionTag2["folder"] = 4] = "folder";
ResolutionTag2[ResolutionTag2["local_tarball"] = 8] = "local_tarball";
ResolutionTag2[ResolutionTag2["github"] = 16] = "github";
ResolutionTag2[ResolutionTag2["gitlab"] = 24] = "gitlab";
ResolutionTag2[ResolutionTag2["git"] = 32] = "git";
ResolutionTag2[ResolutionTag2["symlink"] = 64] = "symlink";
ResolutionTag2[ResolutionTag2["workspace"] = 72] = "workspace";
ResolutionTag2[ResolutionTag2["remote_tarball"] = 80] = "remote_tarball";
ResolutionTag2[ResolutionTag2["single_file_module"] = 100] = "single_file_module";
})(ResolutionTag || (ResolutionTag = {}));
const is_scp = (s) => {
if (s.length < 3) return false;
let at = -1;
for (let i = 0; i < s.length; i++) {
if (s[i] === "@") {
if (at < 0) at = i;
} else if (s[i] === ":") {
if (s.slice(i).startsWith("://")) return false;
return at >= 0 ? i > at + 1 : i > 0;
} else if (s[i] === "/") {
return at >= 0 && i > at + 1;
}
}
return false;
};
const fmt_resolution = (a) => {
if (a.byteLength < 64) throw new TypeError("resolution too short");
const tag = a[0];
const view2 = new DataView(a.buffer, a.byteOffset, a.byteLength);
let pos2 = 8;
if (tag === 2 /* npm */) {
pos2 += 8;
const major = view2.getUint32((pos2 += 4) - 4, true);
const minor = view2.getUint32((pos2 += 4) - 4, true);
const patch = view2.getUint32((pos2 += 4) - 4, true);
pos2 += 4;
const version_tag = new Uint8Array(view2.buffer, view2.byteOffset + pos2, 32);
const pre = str(version_tag.subarray(0, 8));
const build = str(version_tag.subarray(16, 24));
let v = `${major}.${minor}.${patch}`;
if (pre) v += "-" + pre;
if (build) v += "+" + build;
return v;
}
if (tag === 4 /* folder */ || tag === 8 /* local_tarball */ || tag === 80 /* remote_tarball */ || tag === 72 /* workspace */ || tag === 64 /* symlink */ || tag === 100 /* single_file_module */) {
let v = str(new Uint8Array(view2.buffer, view2.byteOffset + pos2, 8));
if (tag === 72 /* workspace */) v = `workspace:${v}`;
if (tag === 64 /* symlink */) v = `link:${v}`;
if (tag === 100 /* single_file_module */) v = `module:${v}`;
return v;
}
if (tag === 32 /* git */ || tag === 16 /* github */ || tag === 24 /* gitlab */) {
let out2 = tag === 32 /* git */ ? "git+" : tag === 16 /* github */ ? "github:" : "gitlab:";
let owner = str(new Uint8Array(view2.buffer, view2.byteOffset + pos2, 8));
let repo = str(new Uint8Array(view2.buffer, view2.byteOffset + pos2 + 8, 8));
if (owner) out2 += owner + "/";
else if (is_scp(repo)) out2 += "ssh://";
out2 += repo;
pos2 += 16;
let commitish = str(new Uint8Array(view2.buffer, view2.byteOffset + pos2, 8));
let resolved = str(new Uint8Array(view2.buffer, view2.byteOffset + pos2 + 8, 8));
if (resolved) {
out2 += "#";
let i = -1;
if ((i = resolved.lastIndexOf("-")) >= 0) {
resolved = resolved.slice(i + 1);
}
out2 += resolved;
} else if (commitish) {
out2 += "#" + commitish;
}
return out2;
}
return "";
};
const fmt_url = (a) => {
if (a.byteLength < 64) throw new TypeError("resolution too short");
if (a[0] === 2 /* npm */) {
return str(new Uint8Array(a.buffer, a.byteOffset + 8, 8));
} else {
return fmt_resolution(a);
}
};
const slice = (data, a, item) => {
const [off, length] = to_u32(a);
return Array.from({ length }, (_, i) => data.subarray(
item * off + item * i,
item * off + item * i + item
));
};
const base64 = (a) => {
let ret;
if (a.length < 65535) {
ret = globalThis.btoa(String.fromCodePoint.apply(String, a));
} else {
ret = "";
for (let value of a) {
ret += String.fromCodePoint(value);
}
ret = globalThis.btoa(ret);
}
return ret;
};
const fmt_integrity = (a) => {
if (a.byteLength < 65) throw new TypeError("integrity too short");
const tag = a[0];
a = a.subarray(1);
let out2;
if (tag === 1) out2 = "sha1-";
else if (tag === 2) out2 = "sha256-";
else if (tag === 3) out2 = "sha384-";
else if (tag === 4) out2 = "sha512-";
else return "";
out2 += base64(a);
return out2;
};
const quote = (s) => {
if (s.startsWith("true") || s.startsWith("false") || /[:\s\n\\",\[\]|\t!]/g.test(s) || /^[0-9]/g.test(s) || !/^[a-zA-Z]/g.test(s))
return JSON.stringify(s);
else
return s;
};
const fmt_specs = (name, specs, version) => {
specs = Array.from(new Set(specs.map((e) => e || `^${version}`)));
specs.sort((a, b) => a.localeCompare(b));
let out2 = "", comma = false;
for (const spec of specs) {
const item = name + "@" + spec;
if (comma) out2 += ", ";
out2 += quote(item);
comma = true;
}
return out2 + ":";
};
let out = [
"# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"# yarn lockfile v1",
"# bun ./bun.lockb --hash: " + fmt_hash(meta_hash),
""
];
const order = Array.from({ length: list_len }, (_, i) => i).slice(1).sort((a, b) => {
const pa = packages[a];
const pb = packages[b];
return str(pa.name).localeCompare(str(pb.name)) || fmt_resolution(pa.resolution).localeCompare(fmt_resolution(pb.resolution));
});
for (const i of order) {
const a = packages[i];
const name = str(a.name);
const resolution = a.resolution;
const meta = a.meta;
const dependencies = slice(buffers.dependencies, a.dependencies, 26);
const dependency_versions = requested_versions[i];
const version = fmt_resolution(resolution);
const versions = dependency_versions.map((b) => str(b.subarray(18, 18 + 8)));
const url = fmt_url(resolution);
const integrity = fmt_integrity(meta.subarray(20, 85));
out.push("");
out.push(fmt_specs(name, versions, version));
out.push(` version ${JSON.stringify(version)}`);
out.push(` resolved ${JSON.stringify(url)}`);
if (integrity) {
out.push(` integrity ${integrity}`);
}
if (dependencies.length > 0) {
let Behavior;
((Behavior2) => {
Behavior2[Behavior2["_"] = 0] = "_";
Behavior2[Behavior2["normal"] = 2] = "normal";
Behavior2[Behavior2["optional"] = 4] = "optional";
Behavior2[Behavior2["dev"] = 8] = "dev";
Behavior2[Behavior2["peer"] = 16] = "peer";
Behavior2[Behavior2["workspace"] = 32] = "workspace";
})(Behavior || (Behavior = {}));
let behavior = 0 /* _ */;
for (let dependency of dependencies) {
let dep_behavior = dependency[16];
if (behavior !== dep_behavior) {
if ((dep_behavior & 4 /* optional */) > 0) {
out.push(" optionalDependencies:");
} else if ((dep_behavior & 2 /* normal */) > 0) {
out.push(" dependencies:");
} else if ((dep_behavior & 8 /* dev */) > 0) {
out.push(" devDependencies:");
} else continue;
behavior = dep_behavior;
}
let dep_name = str(dependency.subarray(0, 8));
let literal = str(dependency.subarray(18, 18 + 8));
out.push(` ${quote(dep_name)} "${literal}"`);
}
}
}
out.push("");
return out.join("\n");
}
export {
parse
};