Add reproducible Bun packaging for desktop and server
- Add shared Nix node-modules derivation and Bun normalization scripts - Package the desktop and server apps from the normalized install tree
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
#!/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);
|
||||
}
|
||||
Reference in New Issue
Block a user