Skip to content

Commit 805b42a

Browse files
fix: svelte-kit sync succeeds when src/routes is missing (#5020)
* fix: svelte-kit sync succeeds when src/routes does not exist * feat: Changeset * Update .changeset/forty-worms-deliver.md Co-authored-by: Maurício Kishi <[email protected]>
1 parent c202a84 commit 805b42a

File tree

3 files changed

+107
-94
lines changed

3 files changed

+107
-94
lines changed

.changeset/forty-worms-deliver.md

Lines changed: 5 additions & 0 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[fix] `svelte-kit sync` gracefully handles a nonexistent routes folder

packages/kit/src/core/sync/create_manifest_data/index.js

Lines changed: 96 additions & 94 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -84,118 +84,120 @@ export default function create_manifest_data({
84
const routes_base = posixify(path.relative(cwd, config.kit.files.routes));
84
const routes_base = posixify(path.relative(cwd, config.kit.files.routes));
85
const valid_extensions = [...config.extensions, ...config.kit.endpointExtensions];
85
const valid_extensions = [...config.extensions, ...config.kit.endpointExtensions];
86

86

87-
list_files(config.kit.files.routes).forEach((file) => {
87+
if (fs.existsSync(config.kit.files.routes)) {
88-
const extension = valid_extensions.find((ext) => file.endsWith(ext));
88+
list_files(config.kit.files.routes).forEach((file) => {
89-
if (!extension) return;
89+
const extension = valid_extensions.find((ext) => file.endsWith(ext));
90-
90+
if (!extension) return;
91-
const id = file
92-
.slice(0, -extension.length)
93-
.replace(/(?:^|\/)index((?:@[a-zA-Z0-9_-]+)?(?:\.[a-z]+)?)?$/, '$1');
94-
const project_relative = `${routes_base}/${file}`;
95-
96-
const segments = id.split('/');
97-
const name = /** @type {string} */ (segments.pop());
98-
99-
if (name === '__layout.reset') {
100-
throw new Error(
101-
'__layout.reset has been removed in favour of named layouts: https://kit.svelte.dev/docs/layouts#named-layouts'
102-
);
103-
}
104

91

105-
if (name === '__error' || layout_pattern.test(name)) {
92+
const id = file
106-
const dir = segments.join('/');
93+
.slice(0, -extension.length)
94+
.replace(/(?:^|\/)index((?:@[a-zA-Z0-9_-]+)?(?:\.[a-z]+)?)?$/, '$1');
95+
const project_relative = `${routes_base}/${file}`;
107

96

108-
if (!tree.has(dir)) {
97+
const segments = id.split('/');
109-
tree.set(dir, {
98+
const name = /** @type {string} */ (segments.pop());
110-
error: undefined,
111-
layouts: {}
112-
});
113-
}
114

99

115-
const group = /** @type {Node} */ (tree.get(dir));
100+
if (name === '__layout.reset') {
101+
throw new Error(
102+
'__layout.reset has been removed in favour of named layouts: https://kit.svelte.dev/docs/layouts#named-layouts'
103+
);
104+
}
116

105

117-
if (name === '__error') {
106+
if (name === '__error' || layout_pattern.test(name)) {
118-
group.error = project_relative;
107+
const dir = segments.join('/');
119-
} else {
120-
const match = /** @type {RegExpMatchArray} */ (layout_pattern.exec(name));
121

108

122-
if (match[1] === DEFAULT) {
109+
if (!tree.has(dir)) {
123-
throw new Error(`${project_relative} cannot use reserved "${DEFAULT}" name`);
110+
tree.set(dir, {
111+
error: undefined,
112+
layouts: {}
113+
});
124
}
114
}
125

115

126-
const layout_id = match[1] || DEFAULT;
116+
const group = /** @type {Node} */ (tree.get(dir));
117+
118+
if (name === '__error') {
119+
group.error = project_relative;
120+
} else {
121+
const match = /** @type {RegExpMatchArray} */ (layout_pattern.exec(name));
127

122

128-
const defined = group.layouts[layout_id];
123+
if (match[1] === DEFAULT) {
129-
if (defined && defined !== default_layout) {
124+
throw new Error(`${project_relative} cannot use reserved "${DEFAULT}" name`);
130-
throw new Error(
125+
}
131-
`Duplicate layout ${project_relative} already defined at ${defined.file}`
126+
132-
);
127+
const layout_id = match[1] || DEFAULT;
128+
129+
const defined = group.layouts[layout_id];
130+
if (defined && defined !== default_layout) {
131+
throw new Error(
132+
`Duplicate layout ${project_relative} already defined at ${defined.file}`
133+
);
134+
}
135+
136+
group.layouts[layout_id] = {
137+
file: project_relative,
138+
name
139+
};
133
}
140
}
134

141

135-
group.layouts[layout_id] = {
142+
return;
136-
file: project_relative,
143+
} else if (dunder_pattern.test(file)) {
137-
name
144+
throw new Error(
138-
};
145+
`Files and directories prefixed with __ are reserved (saw ${project_relative})`
146+
);
139
}
147
}
140

148

141-
return;
149+
if (!config.kit.routes(file)) return;
142-
} else if (dunder_pattern.test(file)) {
143-
throw new Error(
144-
`Files and directories prefixed with __ are reserved (saw ${project_relative})`
145-
);
146-
}
147-
148-
if (!config.kit.routes(file)) return;
149

150

150-
if (/\]\[/.test(id)) {
151+
if (/\]\[/.test(id)) {
151-
throw new Error(`Invalid route ${project_relative} — parameters must be separated`);
152+
throw new Error(`Invalid route ${project_relative} — parameters must be separated`);
152-
}
153+
}
153

154

154-
if (count_occurrences('[', id) !== count_occurrences(']', id)) {
155+
if (count_occurrences('[', id) !== count_occurrences(']', id)) {
155-
throw new Error(`Invalid route ${project_relative} — brackets are unbalanced`);
156+
throw new Error(`Invalid route ${project_relative} — brackets are unbalanced`);
156-
}
157+
}
157

158

158-
if (!units.has(id)) {
159+
if (!units.has(id)) {
159-
units.set(id, {
160+
units.set(id, {
160-
id,
161+
id,
161-
pattern: parse_route_id(id).pattern,
162+
pattern: parse_route_id(id).pattern,
162-
segments: id
163+
segments: id
163-
.split('/')
164+
.split('/')
164-
.filter(Boolean)
165+
.filter(Boolean)
165-
.map((segment) => {
166+
.map((segment) => {
166-
/** @type {Part[]} */
167+
/** @type {Part[]} */
167-
const parts = [];
168+
const parts = [];
168-
segment.split(/\[(.+?)\]/).map((content, i) => {
169+
segment.split(/\[(.+?)\]/).map((content, i) => {
169-
const dynamic = !!(i % 2);
170+
const dynamic = !!(i % 2);
170-
171+
171-
if (!content) return;
172+
if (!content) return;
172-
173+
173-
parts.push({
174+
parts.push({
174-
content,
175+
content,
175-
dynamic,
176+
dynamic,
176-
rest: dynamic && content.startsWith('...'),
177+
rest: dynamic && content.startsWith('...'),
177-
type: (dynamic && content.split('=')[1]) || null
178+
type: (dynamic && content.split('=')[1]) || null
179+
});
178
});
180
});
179-
});
181+
return parts;
180-
return parts;
182+
}),
181-
}),
183+
page: undefined,
182-
page: undefined,
184+
endpoint: undefined
183-
endpoint: undefined
185+
});
184-
});
186+
}
185-
}
186

187

187-
const unit = /** @type {Unit} */ (units.get(id));
188+
const unit = /** @type {Unit} */ (units.get(id));
188

189

189-
if (config.extensions.find((ext) => file.endsWith(ext))) {
190+
if (config.extensions.find((ext) => file.endsWith(ext))) {
190-
const { layouts, errors } = trace(project_relative, file, tree, config.extensions);
191+
const { layouts, errors } = trace(project_relative, file, tree, config.extensions);
191-
unit.page = {
192+
unit.page = {
192-
a: layouts.concat(project_relative),
193+
a: layouts.concat(project_relative),
193-
b: errors
194+
b: errors
194-
};
195+
};
195-
} else {
196+
} else {
196-
unit.endpoint = project_relative;
197+
unit.endpoint = project_relative;
197-
}
198+
}
198-
});
199+
});
200+
}
199

201

200
/** @type {string[]} */
202
/** @type {string[]} */
201
const components = [];
203
const components = [];

packages/kit/src/core/sync/create_manifest_data/index.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -128,6 +128,12 @@ test('creates routes with layout', () => {
128
]);
128
]);
129
});
129
});
130

130

131+
test('succeeds when routes does not exist', () => {
132+
const { components, routes } = create('samples/basic/routes');
133+
assert.equal(components, ['layout.svelte', 'error.svelte']);
134+
assert.equal(routes, []);
135+
});
136+
131
// TODO some characters will need to be URL-encoded in the filename
137
// TODO some characters will need to be URL-encoded in the filename
132
test('encodes invalid characters', () => {
138
test('encodes invalid characters', () => {
133
const { components, routes } = create('samples/encoding');
139
const { components, routes } = create('samples/encoding');

0 commit comments

Comments
 (0)