diff --git a/.changeset/forty-worms-deliver.md b/.changeset/forty-worms-deliver.md new file mode 100644 index 000000000000..d9a8a30eb89a --- /dev/null +++ b/.changeset/forty-worms-deliver.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[fix] `svelte-kit sync` gracefully handles a nonexistent routes folder diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js index c55dce642b70..bd4f3c296a98 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.js @@ -84,118 +84,120 @@ export default function create_manifest_data({ const routes_base = posixify(path.relative(cwd, config.kit.files.routes)); const valid_extensions = [...config.extensions, ...config.kit.endpointExtensions]; - list_files(config.kit.files.routes).forEach((file) => { - const extension = valid_extensions.find((ext) => file.endsWith(ext)); - if (!extension) return; - - const id = file - .slice(0, -extension.length) - .replace(/(?:^|\/)index((?:@[a-zA-Z0-9_-]+)?(?:\.[a-z]+)?)?$/, '$1'); - const project_relative = `${routes_base}/${file}`; - - const segments = id.split('/'); - const name = /** @type {string} */ (segments.pop()); - - if (name === '__layout.reset') { - throw new Error( - '__layout.reset has been removed in favour of named layouts: https://kit.svelte.dev/docs/layouts#named-layouts' - ); - } + if (fs.existsSync(config.kit.files.routes)) { + list_files(config.kit.files.routes).forEach((file) => { + const extension = valid_extensions.find((ext) => file.endsWith(ext)); + if (!extension) return; - if (name === '__error' || layout_pattern.test(name)) { - const dir = segments.join('/'); + const id = file + .slice(0, -extension.length) + .replace(/(?:^|\/)index((?:@[a-zA-Z0-9_-]+)?(?:\.[a-z]+)?)?$/, '$1'); + const project_relative = `${routes_base}/${file}`; - if (!tree.has(dir)) { - tree.set(dir, { - error: undefined, - layouts: {} - }); - } + const segments = id.split('/'); + const name = /** @type {string} */ (segments.pop()); - const group = /** @type {Node} */ (tree.get(dir)); + if (name === '__layout.reset') { + throw new Error( + '__layout.reset has been removed in favour of named layouts: https://kit.svelte.dev/docs/layouts#named-layouts' + ); + } - if (name === '__error') { - group.error = project_relative; - } else { - const match = /** @type {RegExpMatchArray} */ (layout_pattern.exec(name)); + if (name === '__error' || layout_pattern.test(name)) { + const dir = segments.join('/'); - if (match[1] === DEFAULT) { - throw new Error(`${project_relative} cannot use reserved "${DEFAULT}" name`); + if (!tree.has(dir)) { + tree.set(dir, { + error: undefined, + layouts: {} + }); } - const layout_id = match[1] || DEFAULT; + const group = /** @type {Node} */ (tree.get(dir)); + + if (name === '__error') { + group.error = project_relative; + } else { + const match = /** @type {RegExpMatchArray} */ (layout_pattern.exec(name)); - const defined = group.layouts[layout_id]; - if (defined && defined !== default_layout) { - throw new Error( - `Duplicate layout ${project_relative} already defined at ${defined.file}` - ); + if (match[1] === DEFAULT) { + throw new Error(`${project_relative} cannot use reserved "${DEFAULT}" name`); + } + + const layout_id = match[1] || DEFAULT; + + const defined = group.layouts[layout_id]; + if (defined && defined !== default_layout) { + throw new Error( + `Duplicate layout ${project_relative} already defined at ${defined.file}` + ); + } + + group.layouts[layout_id] = { + file: project_relative, + name + }; } - group.layouts[layout_id] = { - file: project_relative, - name - }; + return; + } else if (dunder_pattern.test(file)) { + throw new Error( + `Files and directories prefixed with __ are reserved (saw ${project_relative})` + ); } - return; - } else if (dunder_pattern.test(file)) { - throw new Error( - `Files and directories prefixed with __ are reserved (saw ${project_relative})` - ); - } - - if (!config.kit.routes(file)) return; + if (!config.kit.routes(file)) return; - if (/\]\[/.test(id)) { - throw new Error(`Invalid route ${project_relative} — parameters must be separated`); - } + if (/\]\[/.test(id)) { + throw new Error(`Invalid route ${project_relative} — parameters must be separated`); + } - if (count_occurrences('[', id) !== count_occurrences(']', id)) { - throw new Error(`Invalid route ${project_relative} — brackets are unbalanced`); - } + if (count_occurrences('[', id) !== count_occurrences(']', id)) { + throw new Error(`Invalid route ${project_relative} — brackets are unbalanced`); + } - if (!units.has(id)) { - units.set(id, { - id, - pattern: parse_route_id(id).pattern, - segments: id - .split('/') - .filter(Boolean) - .map((segment) => { - /** @type {Part[]} */ - const parts = []; - segment.split(/\[(.+?)\]/).map((content, i) => { - const dynamic = !!(i % 2); - - if (!content) return; - - parts.push({ - content, - dynamic, - rest: dynamic && content.startsWith('...'), - type: (dynamic && content.split('=')[1]) || null + if (!units.has(id)) { + units.set(id, { + id, + pattern: parse_route_id(id).pattern, + segments: id + .split('/') + .filter(Boolean) + .map((segment) => { + /** @type {Part[]} */ + const parts = []; + segment.split(/\[(.+?)\]/).map((content, i) => { + const dynamic = !!(i % 2); + + if (!content) return; + + parts.push({ + content, + dynamic, + rest: dynamic && content.startsWith('...'), + type: (dynamic && content.split('=')[1]) || null + }); }); - }); - return parts; - }), - page: undefined, - endpoint: undefined - }); - } + return parts; + }), + page: undefined, + endpoint: undefined + }); + } - const unit = /** @type {Unit} */ (units.get(id)); + const unit = /** @type {Unit} */ (units.get(id)); - if (config.extensions.find((ext) => file.endsWith(ext))) { - const { layouts, errors } = trace(project_relative, file, tree, config.extensions); - unit.page = { - a: layouts.concat(project_relative), - b: errors - }; - } else { - unit.endpoint = project_relative; - } - }); + if (config.extensions.find((ext) => file.endsWith(ext))) { + const { layouts, errors } = trace(project_relative, file, tree, config.extensions); + unit.page = { + a: layouts.concat(project_relative), + b: errors + }; + } else { + unit.endpoint = project_relative; + } + }); + } /** @type {string[]} */ const components = []; diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js index 022b7e4e7d41..ac197bee9e3a 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js @@ -128,6 +128,12 @@ test('creates routes with layout', () => { ]); }); +test('succeeds when routes does not exist', () => { + const { components, routes } = create('samples/basic/routes'); + assert.equal(components, ['layout.svelte', 'error.svelte']); + assert.equal(routes, []); +}); + // TODO some characters will need to be URL-encoded in the filename test('encodes invalid characters', () => { const { components, routes } = create('samples/encoding');