worktree-tool
Version:
A command-line tool for managing Git worktrees with integrated tmux/shell session management
223 lines • 7.16 kB
JavaScript
const PRESETS = {
TMUX_SESSION: {
allowSpaces: false,
allowUppercase: false,
allowDots: false,
allowSpecialChars: "-_",
removeLeadingNumbers: true,
maxLength: 30,
},
TMUX_WINDOW: {
allowSpaces: true,
allowUppercase: true,
allowDots: true,
allowSpecialChars: "-_:",
removeLeadingNumbers: false,
},
GIT_BRANCH: {
allowSpaces: false,
allowUppercase: true,
allowDots: true,
allowSpecialChars: "-_/",
maxLength: 100,
removeLeadingNumbers: false,
},
WORKTREE_NAME: {
allowSpaces: false,
allowUppercase: false,
allowDots: false,
allowSpecialChars: "-_",
maxLength: 100,
removeLeadingNumbers: false,
},
PROJECT_NAME: {
allowSpaces: false,
allowUppercase: true, // Preserve case
allowDots: false,
allowSpecialChars: "-_",
maxLength: 50,
removeLeadingNumbers: false,
},
};
/**
* Sanitizes a string according to specified rules and presets.
*
* This function provides a flexible way to clean and normalize strings for different
* contexts like project names, git branches, tmux sessions, etc.
*
* @param input - The string to sanitize
* @param preset - Predefined sanitization preset (PROJECT_NAME, GIT_BRANCH, TMUX_SESSION, etc.)
* @param customOptions - Additional options to override preset defaults
* @returns The sanitized string
*
* @example
* ```typescript
* // Sanitize for project names
* sanitize("My Project!", "PROJECT_NAME"); // "My-Project"
*
* // Sanitize for git branches
* sanitize("feature/test branch", "GIT_BRANCH"); // "feature/test-branch"
*
* // Custom sanitization
* sanitize("Test String", undefined, {
* allowSpaces: false,
* allowUppercase: false,
* maxLength: 10
* }); // "test-strin"
* ```
*/
export function sanitize(input, preset, customOptions) {
const options = {
...(preset ? PRESETS[preset] : {}),
...customOptions,
};
let result = input.trim();
// Handle special cases for PROJECT_NAME preset
if (preset === "PROJECT_NAME") {
// Remove npm scope if present (e.g., @scope/package -> package)
if (result.startsWith("@") && result.includes("/")) {
result = result.split("/")[1] ?? result;
}
else if (result.startsWith("@")) {
result = result.substring(1);
}
}
// Special handling for GIT_BRANCH preset
if (preset === "GIT_BRANCH") {
// Replace spaces with hyphens first
result = result.replace(/\s+/g, "-");
// Remove specific invalid characters for git branches
// eslint-disable-next-line no-control-regex, no-useless-escape
result = result.replace(/[\x00-\x1F\x7F~^:?*\[\]\\!]/g, "");
// Remove leading dots
result = result.replace(/^\.+/, "");
// Remove trailing dots
result = result.replace(/\.+$/, "");
// Replace '..' with '-'
result = result.replace(/\.\.+/g, "-");
// Remove .lock suffix if present
result = result.replace(/\.lock$/, "");
// Replace multiple consecutive hyphens with single hyphen
result = result.replace(/-+/g, "-");
// Remove leading/trailing hyphens
result = result.replace(/^-+|-+$/g, "");
// If empty after sanitization, use default
if (!result || result === "@") {
return "branch";
}
return result;
}
// Convert to lowercase if needed
if (!options.allowUppercase) {
result = result.toLowerCase();
}
// Replace spaces
if (!options.allowSpaces) {
result = result.replace(/\s+/g, "-");
}
// Build regex for allowed characters
const allowedChars = [
"a-zA-Z0-9",
options.allowDots ? "." : "",
options.allowSpaces ? " " : "",
options.allowSpecialChars ?? "",
].filter(Boolean).join("");
// Remove disallowed characters
const regex = new RegExp(`[^${allowedChars}]`, "g");
result = result.replace(regex, "-");
// Remove leading numbers if needed
if (options.removeLeadingNumbers) {
result = result.replace(/^[0-9]+/, "");
}
// Replace multiple consecutive hyphens with single hyphen
result = result.replace(/-+/g, "-");
// Remove leading/trailing dots and hyphens
result = result.replace(/^[.-]+|[.-]+$/g, "");
// Apply max length
if (options.maxLength && result.length > options.maxLength) {
result = result.substring(0, options.maxLength);
}
// Handle empty result with preset-specific defaults
if (result === "") {
if (preset === "PROJECT_NAME") {
return "project";
}
else if (options.defaultValue) {
return options.defaultValue;
}
}
// For PROJECT_NAME preset, prefix with 'p-' if it starts with a number
if (preset === "PROJECT_NAME" && /^\d/.test(result)) {
result = `p-${result}`;
}
return result;
}
/**
* Sanitizes a string for use as a tmux session name.
*
* @param name - The session name to sanitize
* @returns A sanitized session name suitable for tmux
*
* @example
* ```typescript
* sanitizeTmuxSession("My Project"); // "my-project"
* ```
*/
export const sanitizeTmuxSession = (name) => sanitize(name, "TMUX_SESSION");
/**
* Sanitizes a string for use as a tmux window name.
* Removes quotes and applies appropriate sanitization rules.
*
* @param name - The window name to sanitize
* @returns A sanitized window name suitable for tmux
*
* @example
* ```typescript
* sanitizeTmuxWindow('"My Window"'); // "My Window"
* ```
*/
export const sanitizeTmuxWindow = (name) => {
// First remove quotes specifically, then apply general sanitization
const withoutQuotes = name.replace(/['"]/g, "");
return sanitize(withoutQuotes, "TMUX_WINDOW");
};
/**
* Sanitizes a string for use as a git branch name.
*
* @param name - The branch name to sanitize
* @returns A sanitized branch name that follows git naming conventions
*
* @example
* ```typescript
* sanitizeGitBranch("feature test"); // "feature-test"
* sanitizeGitBranch("bug#123"); // "bug#123"
* ```
*/
export const sanitizeGitBranch = (name) => sanitize(name, "GIT_BRANCH");
/**
* Sanitizes a string for use as a git worktree name.
*
* @param name - The worktree name to sanitize
* @returns A sanitized worktree name
*
* @example
* ```typescript
* sanitizeWorktreeName("Feature Branch"); // "feature-branch"
* ```
*/
export const sanitizeWorktreeName = (name) => sanitize(name, "WORKTREE_NAME");
/**
* Sanitizes a string for use as a project name.
* Handles npm scoped packages and preserves case.
*
* @param name - The project name to sanitize
* @returns A sanitized project name
*
* @example
* ```typescript
* sanitizeProjectName("My Project"); // "My-Project"
* sanitizeProjectName("@myorg/package"); // "package"
* ```
*/
export const sanitizeProjectName = (name) => sanitize(name, "PROJECT_NAME");
//# sourceMappingURL=sanitize.js.map