Skip to content

[fix] symlink routes #6796

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 20, 2022
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/silent-jeans-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[fix] symlink routes
3 changes: 2 additions & 1 deletion packages/kit/src/core/generate_manifest/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { s } from '../../utils/misc.js';
import { get_mime_lookup } from '../utils.js';
import { resolve_symlinks } from '../../exports/vite/build/utils.js';

/**
* Generates the data used to write the server-side manifest.js file. This data is used in the Vite
Expand Down Expand Up @@ -65,7 +66,7 @@ export function generate_manifest({ build_data, relative_path, routes, format =
names: ${s(route.names)},
types: ${s(route.types)},
page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${route.page.leaf} }` : 'null'},
endpoint: ${route.endpoint ? loader(`${relative_path}/${build_data.server.vite_manifest[route.endpoint.file].file}`) : 'null'}
endpoint: ${route.endpoint ? loader(`${relative_path}/${resolve_symlinks(build_data.server.vite_manifest, route.endpoint.file).chunk.file}`) : 'null'}
}`;
}).filter(Boolean).join(',\n\t\t\t\t')}
],
Expand Down
13 changes: 8 additions & 5 deletions packages/kit/src/core/sync/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,16 @@ function create_routes_and_nodes(cwd, config, fallback) {

const dir = path.join(cwd, routes_base, id);

const files = fs.readdirSync(dir, {
withFileTypes: true
});
// We can't use withFileTypes because of a NodeJs bug which returns wrong results
// with isDirectory() in case of symlinks: https://github.com/nodejs/node/issues/30646
const files = fs.readdirSync(dir).map((name) => ({
is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
name
}));

// process files first
for (const file of files) {
if (file.isDirectory()) continue;
if (file.is_dir) continue;
if (!file.name.startsWith('+')) continue;
if (!valid_extensions.find((ext) => file.name.endsWith(ext))) continue;

Expand Down Expand Up @@ -213,7 +216,7 @@ function create_routes_and_nodes(cwd, config, fallback) {

// then handle children
for (const file of files) {
if (file.isDirectory()) {
if (file.is_dir) {
walk(depth + 1, path.posix.join(id, file.name), file.name, route);
}
}
Expand Down
10 changes: 8 additions & 2 deletions packages/kit/src/exports/vite/build/build_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { mkdirp, posixify, resolve_entry } from '../../../utils/filesystem.js';
import { get_vite_config, merge_vite_configs } from '../utils.js';
import { load_error_page, load_template } from '../../../core/config/index.js';
import { runtime_directory } from '../../../core/utils.js';
import { create_build, find_deps, get_default_build_config, is_http_method } from './utils.js';
import {
create_build,
find_deps,
get_default_build_config,
is_http_method,
resolve_symlinks
} from './utils.js';
import { s } from '../../../utils/misc.js';

/**
Expand Down Expand Up @@ -285,7 +291,7 @@ export async function build_server(options, client) {

exports.push(
`export const component = async () => (await import('../${
vite_manifest[node.component].file
resolve_symlinks(vite_manifest, node.component).chunk.file
}')).default;`,
`export const file = '${entry.file}';` // TODO what is this?
);
Expand Down
32 changes: 25 additions & 7 deletions packages/kit/src/exports/vite/build/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from 'fs';
import path from 'path';
import * as vite from 'vite';
import { get_aliases } from '../utils.js';

Expand Down Expand Up @@ -44,14 +46,14 @@ export function find_deps(manifest, entry, add_dynamic_css) {
const stylesheets = new Set();

/**
* @param {string} file
* @param {string} current
* @param {boolean} add_js
*/
function traverse(file, add_js) {
if (seen.has(file)) return;
seen.add(file);
function traverse(current, add_js) {
if (seen.has(current)) return;
seen.add(current);

const chunk = manifest[file];
const { chunk } = resolve_symlinks(manifest, current);

if (add_js) imports.add(chunk.file);

Expand All @@ -68,15 +70,31 @@ export function find_deps(manifest, entry, add_dynamic_css) {
}
}

traverse(entry, true);
const { chunk, file } = resolve_symlinks(manifest, entry);

traverse(file, true);

return {
file: manifest[entry].file,
file: chunk.file,
imports: Array.from(imports),
stylesheets: Array.from(stylesheets)
};
}

/**
* @param {import('vite').Manifest} manifest
* @param {string} file
*/
export function resolve_symlinks(manifest, file) {
while (!manifest[file]) {
file = path.relative('.', fs.realpathSync(file));
}

const chunk = manifest[file];

return { chunk, file };
}

/**
* The Vite configuration that we use by default.
* @param {{
Expand Down
1 change: 1 addition & 0 deletions packages/kit/test/apps/basics/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/test/errors.json
!/.env
/src/routes/routing/symlink-from
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && tsc && svelte-check",
"test": "npm run test:dev && npm run test:build",
"test": "node test/setup.js && npm run test:dev && npm run test:build",
"test:dev": "rimraf test/errors.json && cross-env DEV=true playwright test",
"test:build": "rimraf test/errors.json && playwright test"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<a href="/routing/a">a</a>
<a href="/routing/ambiguous/ok.json" rel="external">ok</a>
<a href="/routing/symlink-from">symlinked</a>
<a href="http://localhost:{$page.url.searchParams.get('port')}">elsewhere</a>
<a href="/static.json">static.json</a>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>symlinked</h1>
7 changes: 7 additions & 0 deletions packages/kit/test/apps/basics/test/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import fs from 'fs';

if (process.platform !== 'win32') {
process.chdir('src/routes/routing');
fs.rmSync('symlink-from', { recursive: true, force: true });
fs.symlinkSync('symlink-to', 'symlink-from', 'dir');
}
9 changes: 9 additions & 0 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,15 @@ test.describe('Routing', () => {
expect(await page.textContent('h1')).toBe('404');
expect(await page.textContent('p')).toBe('This is your custom error page saying: "Not Found"');
});

if (process.platform !== 'win32') {
test('Respects symlinks', async ({ page, clicknav }) => {
await page.goto('/routing');
await clicknav('[href="/routing/symlink-from"]');

expect(await page.textContent('h1')).toBe('symlinked');
});
}
});

test.describe('Matchers', () => {
Expand Down