Skip to content

Commit 7cdcde4

Browse files
committed
fix(create): sanitize URL host before using it as a cache path segment
Codex review (high): `getExtractionDir` joined `new URL(...).host` verbatim into `~/.vite-plus/tmp/create-org/<host>/...`. Hosts can carry a port (`localhost:4873`) or an IPv6 literal (`[::1]:4873`), both of which contain `:` / `[` / `]` — characters Windows refuses in path segments. The first `vp create @your-org:foo` against a non-default-port registry would fail with `EINVAL` on `mkdir`. Add a `sanitizeHostForPath(host)` helper that replaces the Windows- illegal characters (`\ / : * ? " < > |` plus `[ ]`) with `_`, and route the cache path through it. Exported for direct testing; three new unit tests cover the plain hostname, the port colon, and the IPv6 bracketed form. Codex's other finding (P2: `create.defaultTemplate` not discovered from a non-monorepo nested invocation) is the deferred behavior the user explicitly chose to leave as-is in this PR — see commit c83d0634. Skip per that earlier directive.
1 parent 4961eb1 commit 7cdcde4

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

packages/cli/src/create/__tests__/org-tarball.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
normalizeEntryName,
1010
parseEntryMode,
1111
resolveBundledPath,
12+
sanitizeHostForPath,
1213
} from '../org-tarball.js';
1314

1415
describe('resolveBundledPath', () => {
@@ -87,6 +88,21 @@ describe('normalizeEntryName', () => {
8788
});
8889
});
8990

91+
describe('sanitizeHostForPath', () => {
92+
it('passes through plain hostnames untouched', () => {
93+
expect(sanitizeHostForPath('registry.npmjs.org')).toBe('registry.npmjs.org');
94+
expect(sanitizeHostForPath('private.example.com')).toBe('private.example.com');
95+
});
96+
97+
it('replaces the port `:` so the cache path is valid on Windows', () => {
98+
expect(sanitizeHostForPath('localhost:4873')).toBe('localhost_4873');
99+
});
100+
101+
it('strips IPv6 brackets and colons from the literal', () => {
102+
expect(sanitizeHostForPath('[::1]:4873')).toBe('___1__4873');
103+
});
104+
});
105+
90106
describe('parseEntryMode', () => {
91107
it('parses octal `755` as 0o755', () => {
92108
expect(parseEntryMode('755')).toBe(0o755);

packages/cli/src/create/org-tarball.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ function getCacheRoot(): string {
1313
return path.join(home, 'tmp', 'create-org');
1414
}
1515

16+
/**
17+
* Replace characters that are illegal in Windows path segments
18+
* (`\ / : * ? " < > |` plus the IPv6 bracket pair `[ ]`). The host
19+
* comes from `new URL(...).host` which can carry a port (`:4873`) or
20+
* IPv6 literal (`[::1]`); both end up in the cache path otherwise.
21+
*/
22+
export function sanitizeHostForPath(host: string): string {
23+
return host.replaceAll(/[\\/:*?"<>|[\]]/g, '_');
24+
}
25+
1626
/**
1727
* Cache extracted tarballs under `<host>/<scope>/create/<version>` so two
1828
* repos resolving the same `<scope>@<version>` through different registries
@@ -22,7 +32,13 @@ function getCacheRoot(): string {
2232
*/
2333
function getExtractionDir(manifest: OrgManifest): string {
2434
const { host } = new URL(manifest.tarballUrl);
25-
return path.join(getCacheRoot(), host, manifest.scope, 'create', manifest.version);
35+
return path.join(
36+
getCacheRoot(),
37+
sanitizeHostForPath(host),
38+
manifest.scope,
39+
'create',
40+
manifest.version,
41+
);
2642
}
2743

2844
function parseIntegrity(integrity: string): { algorithm: string; expected: string } | null {

0 commit comments

Comments
 (0)