From 8943cf1c823372d5f3e48a4134a54e79b597e93a Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 22 Dec 2025 14:09:50 +0100 Subject: [PATCH 1/7] fix(adapter): sanitize wrangler project name for Cloudflare compatibility --- .changeset/sanitize-wrangler-name.md | 11 +++ .../addons/_tests/sveltekit-adapter/test.ts | 20 +++++- .../sv/lib/addons/sveltekit-adapter/index.ts | 4 +- packages/sv/lib/cli/add/utils.ts | 17 +++++ packages/sv/lib/core/tests/wrangler.ts | 67 +++++++++++++++++++ 5 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 .changeset/sanitize-wrangler-name.md create mode 100644 packages/sv/lib/core/tests/wrangler.ts diff --git a/.changeset/sanitize-wrangler-name.md b/.changeset/sanitize-wrangler-name.md new file mode 100644 index 000000000..ce7ed5723 --- /dev/null +++ b/.changeset/sanitize-wrangler-name.md @@ -0,0 +1,11 @@ +--- +'sv': patch +--- + +fix: sanitize wrangler project name to comply with Cloudflare naming requirements + +When using the Cloudflare adapter, project names from `package.json` are now sanitized to be wrangler-compatible: +- Dots, underscores, and special characters are replaced with dashes +- Names are converted to lowercase +- Truncated to 63 characters (DNS subdomain limit) +- Empty results fallback to `undefined-project-name` diff --git a/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts b/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts index f5ea1e458..c88dde648 100644 --- a/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts +++ b/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts @@ -39,8 +39,24 @@ test.concurrent.for(testCases)('adapter $kind.type $variant', async (testCase, { 'adapter-auto only supports some environments' ); } else if (testCase.kind.type === 'cloudflare-workers') { - expect(await readFile(join(cwd, 'wrangler.jsonc'), 'utf8')).toMatch('ASSETS'); + const wranglerContent = await readFile(join(cwd, 'wrangler.jsonc'), 'utf8'); + expect(wranglerContent).toMatch('ASSETS'); + + const nameMatch = wranglerContent.match(/"name":\s*"([^"]+)"/); + expect(nameMatch).toBeTruthy(); + if (nameMatch) { + expect(nameMatch[1]).toMatch(/^[a-z0-9-]+$/); + expect(nameMatch[1]).not.toContain('.'); + } } else if (testCase.kind.type === 'cloudflare-pages') { - expect(await readFile(join(cwd, 'wrangler.jsonc'), 'utf8')).toMatch('pages_build_output_dir'); + const wranglerContent = await readFile(join(cwd, 'wrangler.jsonc'), 'utf8'); + expect(wranglerContent).toMatch('pages_build_output_dir'); + + const nameMatch = wranglerContent.match(/"name":\s*"([^"]+)"/); + expect(nameMatch).toBeTruthy(); + if (nameMatch) { + expect(nameMatch[1]).toMatch(/^[a-z0-9-]+$/); + expect(nameMatch[1]).not.toContain('.'); + } } }); diff --git a/packages/sv/lib/addons/sveltekit-adapter/index.ts b/packages/sv/lib/addons/sveltekit-adapter/index.ts index b5630edcc..b0736ec9a 100644 --- a/packages/sv/lib/addons/sveltekit-adapter/index.ts +++ b/packages/sv/lib/addons/sveltekit-adapter/index.ts @@ -1,7 +1,7 @@ import { defineAddon, defineAddonOptions } from '../../core/index.ts'; import { exports, functions, imports, object, type AstTypes } from '../../core/tooling/js/index.ts'; import { parseJson, parseScript, parseToml } from '../../core/tooling/parsers.ts'; -import { fileExists, readFile } from '../../cli/add/utils.ts'; +import { fileExists, readFile, sanitizeWranglerName } from '../../cli/add/utils.ts'; import { resolveCommand } from 'package-manager-detector'; import * as js from '../../core/tooling/js/index.ts'; @@ -139,7 +139,7 @@ export default defineAddon({ if (!data.name) { const pkg = parseJson(readFile(cwd, files.package)); - data.name = pkg.data.name; + data.name = sanitizeWranglerName(pkg.data.name); } data.compatibility_date ??= new Date().toISOString().split('T')[0]; diff --git a/packages/sv/lib/cli/add/utils.ts b/packages/sv/lib/cli/add/utils.ts index d2e4e8216..5126d6549 100644 --- a/packages/sv/lib/cli/add/utils.ts +++ b/packages/sv/lib/cli/add/utils.ts @@ -147,3 +147,20 @@ export function getHighlighter(): Highlighter { optional: (str) => pc.gray(str) }; } + +/** + * Sanitizes a project name for Cloudflare Wrangler compatibility. + * Wrangler requires names to be alphanumeric, lowercase, dashes only, and max 63 chars. + * @example sanitizeWranglerName("sub.example.com") // "sub-example-com" + * @example sanitizeWranglerName("My_Project.Name") // "my-project-name" + */ +export function sanitizeWranglerName(name: string): string { + const sanitized = name + .toLowerCase() + .replace(/[^a-z0-9-]/g, '-') + .replace(/-+/g, '-') + .slice(0, 63) + .replace(/^-|-$/g, ''); + + return sanitized || 'undefined-project-name'; +} diff --git a/packages/sv/lib/core/tests/wrangler.ts b/packages/sv/lib/core/tests/wrangler.ts new file mode 100644 index 000000000..6f56fcf19 --- /dev/null +++ b/packages/sv/lib/core/tests/wrangler.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from 'vitest'; +import { sanitizeWranglerName } from '../../cli/add/utils.ts'; + +describe('sanitizeWranglerName', () => { + const testCases = [ + // Basic cases + { input: 'my-project', expected: 'my-project' }, + { input: 'myproject', expected: 'myproject' }, + + // Dots + { input: 'sub.example.com', expected: 'sub-example-com' }, + { input: 'my.cool.app', expected: 'my-cool-app' }, + + // Underscores + { input: 'my_project_name', expected: 'my-project-name' }, + + // Mixed cases + { input: 'My_Project.Name', expected: 'my-project-name' }, + { input: 'MyAwesomeApp', expected: 'myawesomeapp' }, + + // Special characters + { input: '@scope/package', expected: 'scope-package' }, + { input: 'hello@world!test', expected: 'hello-world-test' }, + + // Multiple consecutive invalid chars + { input: 'my..project__name', expected: 'my-project-name' }, + + // Leading/trailing invalid chars + { input: '.my-project.', expected: 'my-project' }, + { input: '---test---', expected: 'test' }, + + // Numbers + { input: 'project123', expected: 'project123' }, + { input: '123project', expected: '123project' }, + + // Empty/invalid fallback + { input: '___', expected: 'undefined-project-name' }, + { input: '!@#$%', expected: 'undefined-project-name' }, + { input: '', expected: 'undefined-project-name' }, + + // Length limit (63 chars max) + { input: 'a'.repeat(70), expected: 'a'.repeat(63) }, + { + input: 'my-very-long-project-name-that-exceeds-the-limit-of-63-characters-allowed', + expected: 'my-very-long-project-name-that-exceeds-the-limit-of-63-characte' + }, + + // Truncation trap: slice leaves trailing dash + { input: 'a'.repeat(62) + '-b', expected: 'a'.repeat(62) }, + + // Spaces + { input: 'my cool project', expected: 'my-cool-project' }, + { input: ' spaced out ', expected: 'spaced-out' }, + + // Exact boundary (off-by-one check) + { input: 'a'.repeat(63), expected: 'a'.repeat(63) }, + + // Unicode / accents / emojis (replaced with dashes) + { input: 'piñata', expected: 'pi-ata' }, + { input: 'café', expected: 'caf' }, + { input: 'cool 🚀 app', expected: 'cool-app' } + ]; + + it.each(testCases)('sanitizes "$input" to "$expected"', ({ input, expected }) => { + expect(sanitizeWranglerName(input)).toBe(expected); + }); +}); From 64799c7ed4b5f53a73d30b4c5a69e51433dd6d14 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 22 Dec 2025 14:23:07 +0100 Subject: [PATCH 2/7] refactor: remove redundant assertions --- packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts b/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts index c88dde648..64788e66b 100644 --- a/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts +++ b/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts @@ -46,7 +46,6 @@ test.concurrent.for(testCases)('adapter $kind.type $variant', async (testCase, { expect(nameMatch).toBeTruthy(); if (nameMatch) { expect(nameMatch[1]).toMatch(/^[a-z0-9-]+$/); - expect(nameMatch[1]).not.toContain('.'); } } else if (testCase.kind.type === 'cloudflare-pages') { const wranglerContent = await readFile(join(cwd, 'wrangler.jsonc'), 'utf8'); @@ -56,7 +55,6 @@ test.concurrent.for(testCases)('adapter $kind.type $variant', async (testCase, { expect(nameMatch).toBeTruthy(); if (nameMatch) { expect(nameMatch[1]).toMatch(/^[a-z0-9-]+$/); - expect(nameMatch[1]).not.toContain('.'); } } }); From 861eab066c6b98402377ddb99ab18ceef4fa2602 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 22 Dec 2025 14:27:09 +0100 Subject: [PATCH 3/7] docs: add DNS limit explanation --- packages/sv/lib/cli/add/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv/lib/cli/add/utils.ts b/packages/sv/lib/cli/add/utils.ts index 5126d6549..a7d92e24b 100644 --- a/packages/sv/lib/cli/add/utils.ts +++ b/packages/sv/lib/cli/add/utils.ts @@ -150,7 +150,7 @@ export function getHighlighter(): Highlighter { /** * Sanitizes a project name for Cloudflare Wrangler compatibility. - * Wrangler requires names to be alphanumeric, lowercase, dashes only, and max 63 chars. + * Wrangler requires names to be alphanumeric, lowercase, dashes only, and max 63 chars (DNS subdomain label length limit). * @example sanitizeWranglerName("sub.example.com") // "sub-example-com" * @example sanitizeWranglerName("My_Project.Name") // "my-project-name" */ From afbcee243d5cdecb093742654347cae2ce7e7af2 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 22 Dec 2025 18:37:00 +0100 Subject: [PATCH 4/7] refacto to use 1 sanitize! --- packages/sv/lib/cli/add/utils.ts | 17 ------ packages/sv/lib/core/sanitize.ts | 27 +++++++++ packages/sv/lib/core/tests/sanitize.ts | 78 ++++++++++++++++++++++++++ packages/sv/lib/core/tests/wrangler.ts | 67 ---------------------- packages/sv/lib/create/index.ts | 12 +--- 5 files changed, 107 insertions(+), 94 deletions(-) create mode 100644 packages/sv/lib/core/sanitize.ts create mode 100644 packages/sv/lib/core/tests/sanitize.ts delete mode 100644 packages/sv/lib/core/tests/wrangler.ts diff --git a/packages/sv/lib/cli/add/utils.ts b/packages/sv/lib/cli/add/utils.ts index a7d92e24b..d2e4e8216 100644 --- a/packages/sv/lib/cli/add/utils.ts +++ b/packages/sv/lib/cli/add/utils.ts @@ -147,20 +147,3 @@ export function getHighlighter(): Highlighter { optional: (str) => pc.gray(str) }; } - -/** - * Sanitizes a project name for Cloudflare Wrangler compatibility. - * Wrangler requires names to be alphanumeric, lowercase, dashes only, and max 63 chars (DNS subdomain label length limit). - * @example sanitizeWranglerName("sub.example.com") // "sub-example-com" - * @example sanitizeWranglerName("My_Project.Name") // "my-project-name" - */ -export function sanitizeWranglerName(name: string): string { - const sanitized = name - .toLowerCase() - .replace(/[^a-z0-9-]/g, '-') - .replace(/-+/g, '-') - .slice(0, 63) - .replace(/^-|-$/g, ''); - - return sanitized || 'undefined-project-name'; -} diff --git a/packages/sv/lib/core/sanitize.ts b/packages/sv/lib/core/sanitize.ts new file mode 100644 index 000000000..a6d04c0b3 --- /dev/null +++ b/packages/sv/lib/core/sanitize.ts @@ -0,0 +1,27 @@ +/** + * @param name - The name to sanitize. + * @param kind - The kind of name to sanitize. + * - `package` for package.json + * - `wrangler` for Cloudflare Wrangler compatibility + * @returns The sanitized name. + */ +export function sanitizeName(name: string, kind: 'package' | 'wrangler'): string { + let sanitized = name.trim().toLowerCase().replace(/\s+/g, '-'); + if (kind === 'package') { + const hasLeadingAt = sanitized.startsWith('@'); + sanitized = sanitized + .replace(/\s+/g, '-') + .replace(/^[._]/, '') + .replace(/[^a-z0-9~./-]+/g, '-'); + if (hasLeadingAt) sanitized = '@' + sanitized.slice(1); + } else if (kind === 'wrangler') { + sanitized = sanitized + .replace(/[^a-z0-9-]/g, '-') + .replace(/-+/g, '-') + .slice(0, 63) + .replace(/^-|-$/g, ''); + } else { + throw new Error(`Invalid kind: ${kind}`); + } + return sanitized || 'undefined-sv-name'; +} diff --git a/packages/sv/lib/core/tests/sanitize.ts b/packages/sv/lib/core/tests/sanitize.ts new file mode 100644 index 000000000..1497a22d3 --- /dev/null +++ b/packages/sv/lib/core/tests/sanitize.ts @@ -0,0 +1,78 @@ +import { describe, it, expect } from 'vitest'; +import { sanitizeName } from '../sanitize.ts'; + +const testCases: Array<{ input: string; expected: string; expectedPackage?: string }> = [ + // Basic cases + { input: 'my-project', expected: 'my-project' }, + { input: 'myproject', expected: 'myproject' }, + + // Dots + { input: 'sub.example.com', expected: 'sub-example-com', expectedPackage: 'sub.example.com' }, + { input: 'my.cool.app', expected: 'my-cool-app', expectedPackage: 'my.cool.app' }, + + // Underscores + { input: 'my_project_name', expected: 'my-project-name' }, + + // Mixed cases + { input: 'My_Project.Name', expected: 'my-project-name', expectedPackage: 'my-project.name' }, + { input: 'MyAwesomeApp', expected: 'myawesomeapp' }, + + // Special characters + { input: '@scope/package', expected: 'scope-package', expectedPackage: '@scope/package' }, + { input: 'hello@world!test', expected: 'hello-world-test' }, + + // Multiple consecutive invalid chars + { input: 'my..project__name', expected: 'my-project-name', expectedPackage: 'my..project-name' }, + + // Leading/trailing invalid chars + { input: '.my-project.', expected: 'my-project', expectedPackage: 'my-project.' }, + { input: '---test---', expected: 'test', expectedPackage: '---test---' }, + + // Numbers + { input: 'project123', expected: 'project123' }, + { input: '123project', expected: '123project' }, + + // Empty/invalid fallback + { input: '___', expected: 'undefined-sv-name', expectedPackage: '-' }, + { input: '!@#$%', expected: 'undefined-sv-name', expectedPackage: '-' }, + { input: '', expected: 'undefined-sv-name' }, + + // Length limit (63 chars max) + { input: 'a'.repeat(70), expected: 'a'.repeat(63), expectedPackage: 'a'.repeat(70) }, + { + input: 'my-very-long-project-name-that-exceeds-the-limit-of-63-characters-allowed', + expected: 'my-very-long-project-name-that-exceeds-the-limit-of-63-characte', + expectedPackage: 'my-very-long-project-name-that-exceeds-the-limit-of-63-characters-allowed' + }, + + // Truncation trap: slice leaves trailing dash + { + input: 'a'.repeat(62) + '-b', + expected: 'a'.repeat(62), + expectedPackage: 'a'.repeat(62) + '-b' + }, + + // Spaces + { input: 'my cool project', expected: 'my-cool-project' }, + { input: ' spaced out ', expected: 'spaced-out' }, + + // Exact boundary (off-by-one check) + { input: 'a'.repeat(63), expected: 'a'.repeat(63) }, + + // Unicode / accents / emojis (replaced with dashes) + { input: 'piñata', expected: 'pi-ata' }, + { input: 'café', expected: 'caf', expectedPackage: 'caf-' }, + { input: 'cool 🚀 app', expected: 'cool-app', expectedPackage: 'cool---app' } +]; + +describe('sanitizeName wrangler', () => { + it.each(testCases)('sanitizes $input to $expected', ({ input, expected }) => { + expect(sanitizeName(input, 'wrangler')).toBe(expected); + }); +}); + +describe('sanitizeName package', () => { + it.each(testCases)('sanitizes $input to $expected', ({ input, expected, expectedPackage }) => { + expect(sanitizeName(input, 'package')).toBe(expectedPackage ?? expected); + }); +}); diff --git a/packages/sv/lib/core/tests/wrangler.ts b/packages/sv/lib/core/tests/wrangler.ts deleted file mode 100644 index 6f56fcf19..000000000 --- a/packages/sv/lib/core/tests/wrangler.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { sanitizeWranglerName } from '../../cli/add/utils.ts'; - -describe('sanitizeWranglerName', () => { - const testCases = [ - // Basic cases - { input: 'my-project', expected: 'my-project' }, - { input: 'myproject', expected: 'myproject' }, - - // Dots - { input: 'sub.example.com', expected: 'sub-example-com' }, - { input: 'my.cool.app', expected: 'my-cool-app' }, - - // Underscores - { input: 'my_project_name', expected: 'my-project-name' }, - - // Mixed cases - { input: 'My_Project.Name', expected: 'my-project-name' }, - { input: 'MyAwesomeApp', expected: 'myawesomeapp' }, - - // Special characters - { input: '@scope/package', expected: 'scope-package' }, - { input: 'hello@world!test', expected: 'hello-world-test' }, - - // Multiple consecutive invalid chars - { input: 'my..project__name', expected: 'my-project-name' }, - - // Leading/trailing invalid chars - { input: '.my-project.', expected: 'my-project' }, - { input: '---test---', expected: 'test' }, - - // Numbers - { input: 'project123', expected: 'project123' }, - { input: '123project', expected: '123project' }, - - // Empty/invalid fallback - { input: '___', expected: 'undefined-project-name' }, - { input: '!@#$%', expected: 'undefined-project-name' }, - { input: '', expected: 'undefined-project-name' }, - - // Length limit (63 chars max) - { input: 'a'.repeat(70), expected: 'a'.repeat(63) }, - { - input: 'my-very-long-project-name-that-exceeds-the-limit-of-63-characters-allowed', - expected: 'my-very-long-project-name-that-exceeds-the-limit-of-63-characte' - }, - - // Truncation trap: slice leaves trailing dash - { input: 'a'.repeat(62) + '-b', expected: 'a'.repeat(62) }, - - // Spaces - { input: 'my cool project', expected: 'my-cool-project' }, - { input: ' spaced out ', expected: 'spaced-out' }, - - // Exact boundary (off-by-one check) - { input: 'a'.repeat(63), expected: 'a'.repeat(63) }, - - // Unicode / accents / emojis (replaced with dashes) - { input: 'piñata', expected: 'pi-ata' }, - { input: 'café', expected: 'caf' }, - { input: 'cool 🚀 app', expected: 'cool-app' } - ]; - - it.each(testCases)('sanitizes "$input" to "$expected"', ({ input, expected }) => { - expect(sanitizeWranglerName(input)).toBe(expected); - }); -}); diff --git a/packages/sv/lib/create/index.ts b/packages/sv/lib/create/index.ts index 59b1ba8fb..849222414 100644 --- a/packages/sv/lib/create/index.ts +++ b/packages/sv/lib/create/index.ts @@ -1,6 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { mkdirp, copy, dist, getSharedFiles } from './utils.ts'; +import { sanitizeName } from '../core/sanitize.ts'; export type TemplateType = (typeof templateTypes)[number]; export type LanguageType = (typeof languageTypes)[number]; @@ -89,7 +90,7 @@ function write_common_files(cwd: string, options: Options, name: string) { pkg.dependencies = sort_keys(pkg.dependencies); pkg.devDependencies = sort_keys(pkg.devDependencies); - pkg.name = to_valid_package_name(name); + pkg.name = sanitizeName(name, 'package'); fs.writeFileSync(pkg_file, JSON.stringify(pkg, null, '\t') + '\n'); } @@ -158,12 +159,3 @@ function sort_files(files: Common['files']) { return same || different ? 0 : f1_more_generic ? -1 : 1; }); } - -function to_valid_package_name(name: string) { - return name - .trim() - .toLowerCase() - .replace(/\s+/g, '-') - .replace(/^[._]/, '') - .replace(/[^a-z0-9~.-]+/g, '-'); -} From 662eebbc2201507dfe60fde14470cc0bf331874e Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 22 Dec 2025 18:38:30 +0100 Subject: [PATCH 5/7] this test is meaningless as we fix these name in test --- .../addons/_tests/sveltekit-adapter/test.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts b/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts index 64788e66b..f5ea1e458 100644 --- a/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts +++ b/packages/sv/lib/addons/_tests/sveltekit-adapter/test.ts @@ -39,22 +39,8 @@ test.concurrent.for(testCases)('adapter $kind.type $variant', async (testCase, { 'adapter-auto only supports some environments' ); } else if (testCase.kind.type === 'cloudflare-workers') { - const wranglerContent = await readFile(join(cwd, 'wrangler.jsonc'), 'utf8'); - expect(wranglerContent).toMatch('ASSETS'); - - const nameMatch = wranglerContent.match(/"name":\s*"([^"]+)"/); - expect(nameMatch).toBeTruthy(); - if (nameMatch) { - expect(nameMatch[1]).toMatch(/^[a-z0-9-]+$/); - } + expect(await readFile(join(cwd, 'wrangler.jsonc'), 'utf8')).toMatch('ASSETS'); } else if (testCase.kind.type === 'cloudflare-pages') { - const wranglerContent = await readFile(join(cwd, 'wrangler.jsonc'), 'utf8'); - expect(wranglerContent).toMatch('pages_build_output_dir'); - - const nameMatch = wranglerContent.match(/"name":\s*"([^"]+)"/); - expect(nameMatch).toBeTruthy(); - if (nameMatch) { - expect(nameMatch[1]).toMatch(/^[a-z0-9-]+$/); - } + expect(await readFile(join(cwd, 'wrangler.jsonc'), 'utf8')).toMatch('pages_build_output_dir'); } }); From 126932d8979e4e190fe2a30f9874ecedb34c60c9 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 22 Dec 2025 18:39:07 +0100 Subject: [PATCH 6/7] like this --- packages/sv/lib/addons/sveltekit-adapter/index.ts | 5 +++-- packages/sv/lib/core/sanitize.ts | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/sv/lib/addons/sveltekit-adapter/index.ts b/packages/sv/lib/addons/sveltekit-adapter/index.ts index b0736ec9a..ffe6f1ffb 100644 --- a/packages/sv/lib/addons/sveltekit-adapter/index.ts +++ b/packages/sv/lib/addons/sveltekit-adapter/index.ts @@ -1,7 +1,8 @@ import { defineAddon, defineAddonOptions } from '../../core/index.ts'; import { exports, functions, imports, object, type AstTypes } from '../../core/tooling/js/index.ts'; import { parseJson, parseScript, parseToml } from '../../core/tooling/parsers.ts'; -import { fileExists, readFile, sanitizeWranglerName } from '../../cli/add/utils.ts'; +import { fileExists, readFile } from '../../cli/add/utils.ts'; +import { sanitizeName } from '../../core/sanitize.ts'; import { resolveCommand } from 'package-manager-detector'; import * as js from '../../core/tooling/js/index.ts'; @@ -139,7 +140,7 @@ export default defineAddon({ if (!data.name) { const pkg = parseJson(readFile(cwd, files.package)); - data.name = sanitizeWranglerName(pkg.data.name); + data.name = sanitizeName(pkg.data.name, 'wrangler'); } data.compatibility_date ??= new Date().toISOString().split('T')[0]; diff --git a/packages/sv/lib/core/sanitize.ts b/packages/sv/lib/core/sanitize.ts index a6d04c0b3..00d13795f 100644 --- a/packages/sv/lib/core/sanitize.ts +++ b/packages/sv/lib/core/sanitize.ts @@ -1,27 +1,27 @@ /** * @param name - The name to sanitize. - * @param kind - The kind of name to sanitize. + * @param style - The kind of name to sanitize. * - `package` for package.json * - `wrangler` for Cloudflare Wrangler compatibility * @returns The sanitized name. */ -export function sanitizeName(name: string, kind: 'package' | 'wrangler'): string { +export function sanitizeName(name: string, style: 'package' | 'wrangler'): string { let sanitized = name.trim().toLowerCase().replace(/\s+/g, '-'); - if (kind === 'package') { + if (style === 'package') { const hasLeadingAt = sanitized.startsWith('@'); sanitized = sanitized .replace(/\s+/g, '-') .replace(/^[._]/, '') .replace(/[^a-z0-9~./-]+/g, '-'); if (hasLeadingAt) sanitized = '@' + sanitized.slice(1); - } else if (kind === 'wrangler') { + } else if (style === 'wrangler') { sanitized = sanitized .replace(/[^a-z0-9-]/g, '-') .replace(/-+/g, '-') .slice(0, 63) .replace(/^-|-$/g, ''); } else { - throw new Error(`Invalid kind: ${kind}`); + throw new Error(`Invalid kind: ${style}`); } return sanitized || 'undefined-sv-name'; } From eab1fd82c06779b413a5e5716ffa5dca90b8f17c Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 22 Dec 2025 19:03:50 +0100 Subject: [PATCH 7/7] smaller changeset --- .changeset/sanitize-wrangler-name.md | 8 +------- packages/sv/lib/core/sanitize.ts | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.changeset/sanitize-wrangler-name.md b/.changeset/sanitize-wrangler-name.md index ce7ed5723..22d09fd20 100644 --- a/.changeset/sanitize-wrangler-name.md +++ b/.changeset/sanitize-wrangler-name.md @@ -2,10 +2,4 @@ 'sv': patch --- -fix: sanitize wrangler project name to comply with Cloudflare naming requirements - -When using the Cloudflare adapter, project names from `package.json` are now sanitized to be wrangler-compatible: -- Dots, underscores, and special characters are replaced with dashes -- Names are converted to lowercase -- Truncated to 63 characters (DNS subdomain limit) -- Empty results fallback to `undefined-project-name` +fix(adapter-cloudflare): sanitize wrangler project name to comply with Cloudflare naming requirements diff --git a/packages/sv/lib/core/sanitize.ts b/packages/sv/lib/core/sanitize.ts index 00d13795f..c42461054 100644 --- a/packages/sv/lib/core/sanitize.ts +++ b/packages/sv/lib/core/sanitize.ts @@ -1,6 +1,6 @@ /** * @param name - The name to sanitize. - * @param style - The kind of name to sanitize. + * @param style - The sanitization style. * - `package` for package.json * - `wrangler` for Cloudflare Wrangler compatibility * @returns The sanitized name.