Files
t3code-flake/packages/scripts/canonicalize-node-modules.ts
T
unexplrd 0cf92db43b Move package scripts into packages tree
- relocate Bun normalization helpers under `packages/scripts`
- update shared package build logic to use the new paths
- remove obsolete top-level package wrappers
2026-04-25 12:20:35 +03:00

105 lines
2.9 KiB
TypeScript

#!/usr/bin/env bun
// Canonicalize Bun-managed install indirection without modifying package contents.
import {
lstat,
readdir,
readlink,
symlink,
unlink,
} from "node:fs/promises";
import { dirname, join, relative, resolve } from "node:path";
const nodeModulesRoot = resolve(process.cwd(), process.argv[2] ?? "node_modules");
async function pathExists(path: string): Promise<boolean> {
try {
await lstat(path);
return true;
} catch {
return false;
}
}
function lexicalSort(values: Iterable<string>): string[] {
return [...values].sort((left, right) => left.localeCompare(right, "en"));
}
function normalizeRelativeTarget(fromDir: string, targetPath: string): string {
const target = relative(fromDir, targetPath) || ".";
return target.replaceAll(/\/+/g, "/");
}
async function rewriteSymlink(linkPath: string, targetPath: string): Promise<void> {
const nextTarget = normalizeRelativeTarget(dirname(linkPath), targetPath);
const currentTarget = await readlink(linkPath).catch(() => null);
if (currentTarget === nextTarget) {
return;
}
await unlink(linkPath);
await symlink(nextTarget, linkPath);
}
async function canonicalizeManagedSymlink(linkPath: string): Promise<void> {
const linkDir = dirname(linkPath);
const currentTarget = await readlink(linkPath).catch(() => null);
if (currentTarget === null || currentTarget.length === 0) {
return;
}
const resolvedTarget = resolve(linkDir, currentTarget);
if (await pathExists(resolvedTarget)) {
await rewriteSymlink(linkPath, resolvedTarget);
return;
}
const normalizedBrokenTarget = normalizeRelativeTarget(linkDir, resolvedTarget);
if (currentTarget !== normalizedBrokenTarget) {
await unlink(linkPath);
await symlink(normalizedBrokenTarget, linkPath);
}
}
async function readSortedDir(path: string): Promise<string[]> {
return lexicalSort(await readdir(path));
}
function isNodeModulesPackageEntry(path: string): boolean {
const parent = dirname(path);
const grandparent = dirname(parent);
return parent.endsWith("/node_modules") || grandparent.endsWith("/node_modules");
}
async function walkNodeModules(path: string): Promise<void> {
const entries = await readSortedDir(path);
for (const entry of entries) {
const entryPath = join(path, entry);
const stats = await lstat(entryPath);
if (stats.isSymbolicLink()) {
const relativePath = relative(nodeModulesRoot, entryPath);
const inBunStore = relativePath === ".bun" || relativePath.startsWith(".bun/");
const isWorkspaceLink = isNodeModulesPackageEntry(entryPath);
if (inBunStore || isWorkspaceLink) {
await canonicalizeManagedSymlink(entryPath);
}
continue;
}
if (!stats.isDirectory()) {
continue;
}
if (entry === ".bin") {
continue;
}
await walkNodeModules(entryPath);
}
}
if (await pathExists(nodeModulesRoot)) {
await walkNodeModules(nodeModulesRoot);
}