Skip to content

Commit ae8312c

Browse files
committed
fix: keep slash in scoped package names for cache key
1 parent d84c313 commit ae8312c

4 files changed

Lines changed: 105 additions & 1 deletion

File tree

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@rslib/core": "0.20.1",
4545
"@rslint/core": "^0.3.3",
4646
"@rstest/core": "0.9.5",
47+
"@types/cross-spawn": "^6.0.6",
4748
"@types/fs-extra": "^11.0.4",
4849
"@types/minimist": "^1.2.5",
4950
"@types/node": "24.12.0",
@@ -65,5 +66,8 @@
6566
"publishConfig": {
6667
"access": "public",
6768
"registry": "https://registry.npmjs.org/"
69+
},
70+
"dependencies": {
71+
"cross-spawn": "^7.0.6"
6872
}
6973
}

pnpm-lock.yaml

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/template-manager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ const NPM_TEMPLATE_PREFIX = 'npm:';
88
* Sanitize package name and version to create a valid cache key
99
*/
1010
export const sanitizeCacheKey = (packageName: string, version: string) => {
11-
const normalized = packageName.replace(/[\\/]/g, '_');
11+
// Keep the slash for scoped packages (e.g., @scope/package)
12+
// but replace other slashes that would be invalid in file paths
13+
const normalized = packageName.startsWith('@')
14+
? packageName
15+
: packageName.replace(/[\\/]/g, '_');
1216
const versionLabel = version || 'latest';
1317
return `${normalized}@${versionLabel}`;
1418
};

test/index.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { expect, test } from '@rstest/core';
22
import {
33
checkCancel,
44
create,
5+
isNpmTemplate,
56
multiselect,
7+
resolveCustomTemplate,
8+
resolveNpmTemplate,
9+
sanitizeCacheKey,
610
select,
711
text,
812
} from '../dist/index.js';
@@ -14,3 +18,37 @@ test('should export public APIs', () => {
1418
expect(typeof select).toBe('function');
1519
expect(typeof text).toBe('function');
1620
});
21+
22+
test('should export npm template utilities', () => {
23+
expect(typeof isNpmTemplate).toBe('function');
24+
expect(typeof resolveCustomTemplate).toBe('function');
25+
expect(typeof resolveNpmTemplate).toBe('function');
26+
expect(typeof sanitizeCacheKey).toBe('function');
27+
});
28+
29+
test('should detect npm templates correctly', () => {
30+
// npm: prefix
31+
expect(isNpmTemplate('npm:my-package')).toBe(true);
32+
expect(isNpmTemplate('npm:@scope/package')).toBe(true);
33+
34+
// Scoped packages
35+
expect(isNpmTemplate('@scope/package')).toBe(true);
36+
37+
// Pure package names
38+
expect(isNpmTemplate('my-package')).toBe(true);
39+
expect(isNpmTemplate('my-package-name')).toBe(true);
40+
41+
// Not npm templates
42+
expect(isNpmTemplate('./local-path')).toBe(false);
43+
expect(isNpmTemplate('../relative-path')).toBe(false);
44+
expect(isNpmTemplate('github:user/repo')).toBe(false);
45+
expect(isNpmTemplate('https://example.com')).toBe(false);
46+
expect(isNpmTemplate('/absolute/path')).toBe(false);
47+
});
48+
49+
test('should sanitize cache keys correctly', () => {
50+
expect(sanitizeCacheKey('my-package', '1.0.0')).toBe('my-package@1.0.0');
51+
expect(sanitizeCacheKey('@scope/package', 'latest')).toBe('@scope/package@latest');
52+
expect(sanitizeCacheKey('my-package', '')).toBe('my-package@latest');
53+
expect(sanitizeCacheKey('my/package', '1.0.0')).toBe('my_package@1.0.0');
54+
});

0 commit comments

Comments
 (0)