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/remove-empty-type-dirs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

fix: remove empty type output directories for removed routes
24 changes: 24 additions & 0 deletions packages/kit/src/core/sync/write_types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ const is_whitespace = (/** @type {string} */ char) => /\s/.test(char);
* @typedef {Map<import('types').PageNode, {route: import('types').RouteData, proxies: Proxies}>} RoutesMap
*/

/**
* Recursively remove empty directories under `root`, leaving `root` itself.
* @param {string} dir
* @param {string} root
*/
function remove_empty_directories(dir, root = dir) {
if (!fs.existsSync(dir)) return;

for (const name of fs.readdirSync(dir)) {
const child = path.join(dir, name);
if (fs.statSync(child).isDirectory()) {
remove_empty_directories(child, root);
}
}

if (path.resolve(dir) === path.resolve(root)) return;

if (fs.readdirSync(dir).length === 0) {
fs.rmdirSync(dir);
}
}

/**
* Creates types for the whole manifest
* @param {import('types').ValidatedConfig} config
Expand All @@ -52,6 +74,8 @@ export function write_all_types(config, manifest_data, root) {
fs.rmSync(path.join(types_dir, file), { force: true, recursive: true });
}
}
// Stale files are removed above; empty route directories are left behind.
remove_empty_directories(types_dir);
}

// Read/write meta data on each invocation, not once per node process,
Expand Down
64 changes: 63 additions & 1 deletion packages/kit/src/core/sync/write_types/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import process from 'node:process';
import { assert, describe, expect, test } from 'vitest';
import { afterEach, assert, beforeEach, describe, expect, test } from 'vitest';
import create_manifest_data from '../create_manifest_data/index.js';
import { tweak_types, write_all_types } from './index.js';
import { write_app_types } from '../write_app_types.js';
Expand Down Expand Up @@ -311,6 +311,68 @@ test('Leaves satisfies operator untouched', () => {
assert.equal(rewritten?.code, source);
});

describe('Cleans up unused files', () => {
/** @type {string} */
let root;

beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'kit-write-types-'));

for (const dir of ['', 'about']) {
const route_dir = path.join(root, 'src/routes', dir);
fs.mkdirSync(route_dir, { recursive: true });
fs.writeFileSync(path.join(route_dir, '+page.svelte'), '');
}
});

afterEach(() => {
fs.rmSync(root, { force: true, recursive: true });
});

function sync() {
const config = validate_config({});
config.files.assets = path.join(root, 'static');
config.files.params = path.join(root, 'src/params');
config.files.routes = path.join(root, 'src/routes');
config.outDir = path.join(root, '.svelte-kit');

write_all_types(config, create_manifest_data(config, root), root);

return path.join(config.outDir, 'types');
}

test('removes empty output directories for removed routes', () => {
const types_dir = sync();
const route_types_dir = path.join(types_dir, 'src/routes/about');
const types_file = path.join(route_types_dir, '$types.d.ts');
assert.ok(fs.existsSync(types_file));

fs.rmSync(path.join(root, 'src/routes/about'), { recursive: true });
sync();

assert.ok(!fs.existsSync(types_file));
assert.ok(!fs.existsSync(route_types_dir));
});

test('keeps parent directories that still have nested routes', () => {
const nested = path.join(root, 'src/routes/about/team');
fs.mkdirSync(nested, { recursive: true });
fs.writeFileSync(path.join(nested, '+page.svelte'), '');

const types_dir = sync();
const about_dir = path.join(types_dir, 'src/routes/about');
const team_types = path.join(about_dir, 'team/$types.d.ts');
assert.ok(fs.existsSync(team_types));

fs.rmSync(path.join(root, 'src/routes/about/team'), { recursive: true });
sync();

assert.ok(!fs.existsSync(team_types));
assert.ok(!fs.existsSync(path.join(about_dir, 'team')));
assert.ok(fs.existsSync(path.join(about_dir, '$types.d.ts')));
});
});

test('prunes stale entries from route_meta_data.json when routes are deleted', () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sveltekit-prune-test-'));
try {
Expand Down
Loading