function write_all_types(config, manifest_data) {
	if (!ts) return;

	const types_dir = `${config.kit.outDir}/types`;

	// empty out files that no longer need to exist
	const routes_dir = posixify(path.relative('.', config.kit.files.routes)).replace(/\.\.\//g, '');
	const expected_directories = new Set(
		manifest_data.routes.map((route) => path.join(routes_dir, route.id))
	);

	if (fs.existsSync(types_dir)) {
		for (const file of walk(types_dir)) {
			const dir = path.dirname(file);
			if (!expected_directories.has(dir)) {
				rimraf(path.join(types_dir, file));
			}
		}
	}

	// Read/write meta data on each invocation, not once per node process,
	// it could be invoked by another process in the meantime.
	const meta_data_file = `${types_dir}/route_meta_data.json`;
	const has_meta_data = fs.existsSync(meta_data_file);
	const meta_data = has_meta_data
		? /** @type {Record<string, string[]>} */ (JSON.parse(fs.readFileSync(meta_data_file, 'utf-8')))
		: {};
	const routes_map = create_routes_map(manifest_data);
	// For each directory, write $types.d.ts
	for (const route of manifest_data.routes) {
		if (!route.leaf && !route.layout && !route.endpoint) continue; // nothing to do

		const outdir = path.join(config.kit.outDir, 'types', routes_dir, route.id);

		// check if the types are out of date
		/** @type {string[]} */
		const input_files = [];

		/** @type {import('types').PageNode | null} */
		let node = route.leaf;
		while (node) {
			if (node.universal) input_files.push(node.universal);
			if (node.server) input_files.push(node.server);
			node = node.parent ?? null;
		}

		/** @type {import('types').PageNode | null} */
		node = route.layout;
		while (node) {
			if (node.universal) input_files.push(node.universal);
			if (node.server) input_files.push(node.server);
			node = node.parent ?? null;
		}

		if (route.endpoint) {
			input_files.push(route.endpoint.file);
		}

		try {
			fs.mkdirSync(outdir, { recursive: true });
		} catch {}

		const output_files = compact(
			fs.readdirSync(outdir).map((name) => {
				const stats = fs.statSync(path.join(outdir, name));
				if (stats.isDirectory()) return;
				return {
					name,
					updated: stats.mtimeMs
				};
			})
		);

		const source_last_updated = Math.max(
			// ctimeMs includes move operations whereas mtimeMs does not
			...input_files.map((file) => fs.statSync(file).ctimeMs)
		);
		const types_last_updated = Math.max(...output_files.map((file) => file.updated));

		const should_generate =
			// source files were generated more recently than the types
			source_last_updated > types_last_updated ||
			// no meta data file exists yet
			!has_meta_data ||
			// some file was deleted
			!meta_data[route.id]?.every((file) => input_files.includes(file));

		if (should_generate) {
			// track which old files end up being surplus to requirements
			const to_delete = new Set(output_files.map((file) => file.name));
			update_types(config, routes_map, route, to_delete);
			meta_data[route.id] = input_files;
		}
	}

	fs.writeFileSync(meta_data_file, JSON.stringify(meta_data, null, '\t'));
}
Creates types for the whole manifest
@param {import('types').ValidatedConfig} config
@param {import('types').ManifestData} manifest_data