Files
fusion/packages/engine/src/worktree-names.ts

108 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readdirSync } from "node:fs";
import { join } from "node:path";
import { existsSync } from "node:fs";
export const ADJECTIVES = [
"amber", "azure", "bold", "brave", "bright",
"calm", "clear", "cool", "coral", "crisp",
"deft", "dusky", "eager", "early", "faint",
"fast", "fleet", "fresh", "gentle", "gilt",
"glad", "grand", "green", "happy", "hazy",
"ivory", "jade", "keen", "lemon", "light",
"lunar", "maple", "merry", "misty", "noble",
"opal", "pale", "pearl", "plush", "proud",
"quiet", "rapid", "rosy", "rusty", "sandy",
"sharp", "sleek", "solar", "swift", "vivid",
];
export const NOUNS = [
"aspen", "badger", "breeze", "brook", "cedar",
"cliff", "crane", "creek", "daisy", "delta",
"dune", "eagle", "ember", "falcon", "fern",
"finch", "flame", "frost", "grove", "hawk",
"heron", "iris", "lark", "lotus", "marsh",
"mesa", "moss", "oak", "olive", "orbit",
"otter", "panda", "peach", "petal", "pine",
"plume", "quail", "raven", "reef", "ridge",
"robin", "sage", "shore", "spark", "stone",
"thorn", "tiger", "trail", "trout", "wren",
];
/**
* Convert a string to a URL-friendly slug.
*
* - Lowercase
* - Replace spaces, underscores, and special chars with hyphens
* - Collapse multiple hyphens
* - Trim leading/trailing hyphens
*/
export function slugify(str: string): string {
return str
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, "") // Remove special characters except spaces and hyphens
.replace(/\s+/g, "-") // Replace spaces with hyphens
.replace(/-+/g, "-") // Collapse multiple hyphens
.replace(/^-|-$/g, ""); // Trim leading/trailing hyphens
}
/**
* Generate a random, human-friendly worktree directory name.
*
* Names follow an `adjective-noun` pattern (e.g., `swirly-monkey`,
* `quiet-falcon`, `bright-orchid`) drawn from embedded word lists of
* ~50 adjectives × ~50 nouns, producing ~2,500 unique combinations.
*
* **Collision avoidance:** The function checks existing subdirectories
* under `<rootDir>/.worktrees/`. If the randomly chosen name already
* exists, a numeric suffix is appended (e.g., `swift-falcon-2`,
* `swift-falcon-3`) until a unique name is found.
*
* @param rootDir - The project root directory (parent of `.worktrees/`)
* @returns A unique worktree directory name (not a full path)
*/
export function generateWorktreeName(rootDir: string): string {
return generateReservedWorktreeName(rootDir);
}
/**
* Generate a unique worktree directory name while also avoiding names that
* have been reserved in-memory but may not exist on disk yet.
*/
export function generateReservedWorktreeName(
rootDir: string,
reservedNames: Set<string> = new Set(),
): string {
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
const baseName = `${adjective}-${noun}`;
const worktreesDir = join(rootDir, ".worktrees");
const existing = getExistingWorktreeNames(worktreesDir);
for (const reserved of reservedNames) {
existing.add(reserved);
}
if (!existing.has(baseName)) {
return baseName;
}
// Collision — append numeric suffix
let suffix = 2;
while (existing.has(`${baseName}-${suffix}`)) {
suffix++;
}
return `${baseName}-${suffix}`;
}
function getExistingWorktreeNames(worktreesDir: string): Set<string> {
if (!existsSync(worktreesDir)) {
return new Set();
}
try {
const entries = readdirSync(worktreesDir, { withFileTypes: true });
return new Set(entries.filter((e) => e.isDirectory()).map((e) => e.name));
} catch {
return new Set();
}
}