Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/forty-worms-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[fix] `svelte-kit sync` gracefully handles a nonexistent routes folder
190 changes: 96 additions & 94 deletions packages/kit/src/core/sync/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down
6 changes: 6 additions & 0 deletions packages/kit/src/core/sync/create_manifest_data/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down