73 lines
1.8 KiB
TypeScript
73 lines
1.8 KiB
TypeScript
/**
|
|
* Name validation for projects and repositories.
|
|
* Rules:
|
|
* 1. All English letters (stored as lowercase)
|
|
* 2. Cannot start with a number
|
|
* 3. Only letters, numbers, hyphens, and underscores
|
|
* 4. Cannot conflict with reserved route names
|
|
*/
|
|
|
|
export const RESERVED_ROUTE_NAMES = new Set([
|
|
'auth',
|
|
'init',
|
|
'user',
|
|
'settings',
|
|
'project',
|
|
'explore',
|
|
'market',
|
|
'docs',
|
|
'notify',
|
|
'api',
|
|
'admin',
|
|
'dashboard',
|
|
'login',
|
|
'register',
|
|
'logout',
|
|
'oauth',
|
|
'webhook',
|
|
'static',
|
|
]);
|
|
|
|
/** Regex: starts with letter, then letters/numbers/hyphens/underscores */
|
|
export const NAME_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
|
|
/** Minimum length */
|
|
export const MIN_NAME_LENGTH = 2;
|
|
/** Maximum length */
|
|
export const MAX_NAME_LENGTH = 100;
|
|
|
|
export type NameValidationResult =
|
|
| { valid: true; message: '' }
|
|
| { valid: false; message: string };
|
|
|
|
/**
|
|
* Validates a project or repository name.
|
|
* Pass the raw input (before any transformation).
|
|
*/
|
|
export function validateName(raw: string): NameValidationResult {
|
|
const trimmed = raw.trim();
|
|
|
|
if (trimmed.length < MIN_NAME_LENGTH) {
|
|
return { valid: false, message: `Must be at least ${MIN_NAME_LENGTH} characters` };
|
|
}
|
|
|
|
if (trimmed.length > MAX_NAME_LENGTH) {
|
|
return { valid: false, message: `Must not exceed ${MAX_NAME_LENGTH} characters` };
|
|
}
|
|
|
|
const lower = trimmed.toLowerCase();
|
|
|
|
if (!NAME_PATTERN.test(lower)) {
|
|
return {
|
|
valid: false,
|
|
message: 'Must start with a letter and contain only letters, numbers, hyphens, and underscores',
|
|
};
|
|
}
|
|
|
|
if (RESERVED_ROUTE_NAMES.has(lower)) {
|
|
return { valid: false, message: `"${lower}" is a reserved name and cannot be used` };
|
|
}
|
|
|
|
return { valid: true, message: '' };
|
|
}
|