106 lines
2.5 KiB
TypeScript
106 lines
2.5 KiB
TypeScript
import { lstat, mkdir, readdir, rm, symlink } from "fs/promises";
|
|
import { join, relative } from "path";
|
|
|
|
type Entry = {
|
|
dir: string;
|
|
version: string;
|
|
};
|
|
|
|
async function isDirectory(path: string) {
|
|
try {
|
|
const info = await lstat(path);
|
|
return info.isDirectory();
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const isValidSemver = (version: string) => Bun.semver.satisfies(version, "x.x.x");
|
|
|
|
const bunRoot = join(process.cwd(), "node_modules/.bun");
|
|
const linkRoot = join(bunRoot, "node_modules");
|
|
const directories = (await readdir(bunRoot)).sort();
|
|
|
|
const versions = new Map<string, Entry[]>();
|
|
|
|
for (const entry of directories) {
|
|
const full = join(bunRoot, entry);
|
|
if (!(await isDirectory(full))) {
|
|
continue;
|
|
}
|
|
|
|
const parsed = parseEntry(entry);
|
|
if (!parsed) {
|
|
continue;
|
|
}
|
|
|
|
const list = versions.get(parsed.name) ?? [];
|
|
list.push({ dir: full, version: parsed.version });
|
|
versions.set(parsed.name, list);
|
|
}
|
|
|
|
const selections = new Map<string, Entry>();
|
|
|
|
for (const [slug, list] of versions) {
|
|
list.sort((a, b) => {
|
|
const aValid = isValidSemver(a.version);
|
|
const bValid = isValidSemver(b.version);
|
|
|
|
if (aValid && bValid) {
|
|
return -Bun.semver.order(a.version, b.version);
|
|
}
|
|
if (aValid) {
|
|
return -1;
|
|
}
|
|
if (bValid) {
|
|
return 1;
|
|
}
|
|
return b.version.localeCompare(a.version);
|
|
});
|
|
|
|
const first = list[0];
|
|
if (first) {
|
|
selections.set(slug, first);
|
|
}
|
|
}
|
|
|
|
await rm(linkRoot, { recursive: true, force: true });
|
|
await mkdir(linkRoot, { recursive: true });
|
|
|
|
for (const [slug, entry] of Array.from(selections.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
const parts = slug.split("/");
|
|
const leaf = parts.pop();
|
|
if (!leaf) {
|
|
continue;
|
|
}
|
|
|
|
const parent = join(linkRoot, ...parts);
|
|
await mkdir(parent, { recursive: true });
|
|
|
|
const linkPath = join(parent, leaf);
|
|
const desired = join(entry.dir, "node_modules", slug);
|
|
if (!(await isDirectory(desired))) {
|
|
continue;
|
|
}
|
|
|
|
const relativeTarget = relative(parent, desired);
|
|
await rm(linkPath, { recursive: true, force: true });
|
|
await symlink(relativeTarget.length == 0 ? "." : relativeTarget, linkPath);
|
|
}
|
|
|
|
function parseEntry(label: string) {
|
|
const marker = label.startsWith("@") ? label.indexOf("@", 1) : label.indexOf("@");
|
|
if (marker <= 0) {
|
|
return null;
|
|
}
|
|
|
|
const name = label.slice(0, marker).replace(/\+/g, "/");
|
|
const version = label.slice(marker + 1);
|
|
|
|
if (!name || !version) {
|
|
return null;
|
|
}
|
|
|
|
return { name, version };
|
|
}
|