function generate_lambda_functions({ builder, publish, split }) {
	builder.mkdirp('.netlify/functions-internal/.svelte-kit');

	/** @type {string[]} */
	const redirects = [];
	builder.writeServer('.netlify/server');

	const replace = {
		'0SERVER': './server/index.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced
	};

	builder.copy(`${files}/esm`, '.netlify', { replace });

	// Configuring the function to use ESM as the output format.
	const fn_config = JSON.stringify({ config: { nodeModuleFormat: 'esm' }, version: 1 });

	builder.log.minor('Generating serverless functions...');

	if (split) {
		const seen = new Set();

		for (let i = 0; i < builder.routes.length; i++) {
			const route = builder.routes[i];
			if (route.prerender === true) continue;

			const routes = [route];

			const parts = [];
			// Netlify's syntax uses '*' and ':param' as "splats" and "placeholders"
			// https://docs.netlify.com/routing/redirects/redirect-options/#splats
			for (const segment of route.segments) {
				if (segment.rest) {
					parts.push('*');
					break; // Netlify redirects don't allow anything after a *
				} else if (segment.dynamic) {
					parts.push(`:${parts.length}`);
				} else {
					parts.push(segment.content);
				}
			}

			const pattern = `/${parts.join('/')}`;
			const name =
				FUNCTION_PREFIX + (parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index');

			// skip routes with identical patterns, they were already folded into another function
			if (seen.has(pattern)) continue;
			seen.add(pattern);

			// figure out which lower priority routes should be considered fallbacks
			for (let j = i + 1; j < builder.routes.length; j += 1) {
				const other = builder.routes[j];
				if (other.prerender === true) continue;

				if (matches(route.segments, other.segments)) {
					routes.push(other);
				}
			}

			const manifest = builder.generateManifest({
				relativePath: '../server',
				routes
			});

			const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;

			writeFileSync(`.netlify/functions-internal/${name}.mjs`, fn);
			writeFileSync(`.netlify/functions-internal/${name}.json`, fn_config);

			const redirect = `/.netlify/functions/${name} 200`;
			redirects.push(`${pattern} ${redirect}`);
			redirects.push(`${pattern === '/' ? '' : pattern}/__data.json ${redirect}`);
		}
	} else {
		const manifest = builder.generateManifest({
			relativePath: '../server'
		});

		const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;

		writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.json`, fn_config);
		writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.mjs`, fn);
		redirects.push(`* /.netlify/functions/${FUNCTION_PREFIX}render 200`);
	}

	// this should happen at the end, after builder.writeClient(...),
	// so that generated redirects are appended to custom redirects
	// rather than replaced by them
	builder.log.minor('Writing redirects...');
	const redirects_file = join(publish, '_redirects');
	if (existsSync('_redirects')) {
		builder.copy('_redirects', redirects_file);
	}
	builder.mkdirp(dirname(redirects_file));
	appendFileSync(redirects_file, `\n\n${redirects.join('\n')}`);
}
@param { object } params
@param {import('@sveltejs/kit').Builder} params.builder
@param { string } params.publish
@param { boolean } params.split