Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions packages/cli/src/create/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,40 @@ describe('deriveDefaultPackageName', () => {
});

it('should fallback to random name when directory name is invalid', () => {
const result = deriveDefaultPackageName('/home/user/.hidden', undefined, 'vite-plus-app');
// directory name starts with '.', so a random name is generated instead
expect(result).not.toBe('.hidden');
const result = deriveDefaultPackageName('/home/user/!!!', undefined, 'vite-plus-app');
// nothing in the directory name survives sanitization, so a random name is generated instead
expect(result).not.toContain('!');
expect(result.length).toBeGreaterThan(0);
});

it('should sanitize a directory name rather than inventing an unrelated one', () => {
expect(
deriveDefaultPackageName(
'/home/user/ComfyUI-DenoiseHQNodes.feat-1-save-image-node',
undefined,
'vite-plus-app',
),
).toBe('comfyui-denoisehqnodes.feat-1-save-image-node');
});

it('should replace characters that are not valid in a package name', () => {
expect(deriveDefaultPackageName('/home/user/My App (v2)!', undefined, 'vite-plus-app')).toBe(
'my-app-v2',
);
});

it('should strip leading characters npm forbids instead of generating a name', () => {
expect(deriveDefaultPackageName('/home/user/.hidden', undefined, 'vite-plus-app')).toBe(
'hidden',
);
});

it('should sanitize the directory name while keeping the scope', () => {
expect(deriveDefaultPackageName('/home/user/My-App', '@my-scope', 'vite-plus-app')).toBe(
'@my-scope/my-app',
);
});

it('should fallback when directory is filesystem root', () => {
const result = deriveDefaultPackageName('/', undefined, 'vite-plus-app');
// basename of '/' is empty, so a random name is generated
Expand Down
29 changes: 26 additions & 3 deletions packages/cli/src/create/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,37 @@ export function formatDisplayTargetDir(targetDir: string) {
return `./${normalized}`;
}

const MAX_PACKAGE_NAME_LENGTH = 214;

// Turn a directory name into the closest npm-compatible name: lowercase it and
// replace characters npm rejects, so a directory like `My-App.v2` keeps its
// identity as `my-app.v2` instead of being swapped for an unrelated name.
function sanitizePackageNameSegment(dirName: string): string {
return dirName
.toLowerCase()
.replace(/[^a-z0-9\-._]+/g, '-')
.replace(/-{2,}/g, '-')
.replace(/^[-._]+/, '')
.slice(0, MAX_PACKAGE_NAME_LENGTH)
.replace(/[-.]+$/, '');
}

export function deriveDefaultPackageName(
cwd: string,
scope: string | undefined,
fallbackName: string,
): string {
const dirName = path.basename(cwd);
const candidate = scope ? `${scope}/${dirName}` : dirName;
return validateNpmPackageName(candidate).validForNewPackages
? candidate
: getRandomProjectName({ scope, fallbackName });
if (validateNpmPackageName(candidate).validForNewPackages) {
return candidate;
}
const sanitized = sanitizePackageNameSegment(dirName);
if (sanitized) {
const sanitizedCandidate = scope ? `${scope}/${sanitized}` : sanitized;
if (validateNpmPackageName(sanitizedCandidate).validForNewPackages) {
return sanitizedCandidate;
}
}
return getRandomProjectName({ scope, fallbackName });
}