feat: docker compose maybe
This commit is contained in:
7
node_modules/@sveltejs/adapter-auto/LICENSE
generated
vendored
Normal file
7
node_modules/@sveltejs/adapter-auto/LICENSE
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
11
node_modules/@sveltejs/adapter-auto/README.md
generated
vendored
Normal file
11
node_modules/@sveltejs/adapter-auto/README.md
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# adapter-auto
|
||||
|
||||
Automatically chooses the SvelteKit adapter for your current environment, if possible.
|
||||
|
||||
## Docs
|
||||
|
||||
[Docs](https://kit.svelte.dev/docs/adapter-auto)
|
||||
|
||||
## Changelog
|
||||
|
||||
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-auto/CHANGELOG.md).
|
34
node_modules/@sveltejs/adapter-auto/adapters.js
generated
vendored
Normal file
34
node_modules/@sveltejs/adapter-auto/adapters.js
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// List of adapters to check for. `version` is used to pin the installed adapter version and should point
|
||||
// to the latest version of the adapter that is compatible with adapter-auto's current peerDependency version of SvelteKit.
|
||||
export const adapters = [
|
||||
{
|
||||
name: 'Vercel',
|
||||
test: () => !!process.env.VERCEL,
|
||||
module: '@sveltejs/adapter-vercel',
|
||||
version: '2'
|
||||
},
|
||||
{
|
||||
name: 'Cloudflare Pages',
|
||||
test: () => !!process.env.CF_PAGES,
|
||||
module: '@sveltejs/adapter-cloudflare',
|
||||
version: '2'
|
||||
},
|
||||
{
|
||||
name: 'Netlify',
|
||||
test: () => !!process.env.NETLIFY,
|
||||
module: '@sveltejs/adapter-netlify',
|
||||
version: '2'
|
||||
},
|
||||
{
|
||||
name: 'Azure Static Web Apps',
|
||||
test: () => process.env.GITHUB_ACTION_REPOSITORY === 'Azure/static-web-apps-deploy',
|
||||
module: 'svelte-adapter-azure-swa',
|
||||
version: '0.13'
|
||||
},
|
||||
{
|
||||
name: 'AWS via SST',
|
||||
test: () => !!process.env.SST,
|
||||
module: 'svelte-kit-sst',
|
||||
version: '2'
|
||||
}
|
||||
];
|
3
node_modules/@sveltejs/adapter-auto/index.d.ts
generated
vendored
Normal file
3
node_modules/@sveltejs/adapter-auto/index.d.ts
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { Adapter } from '@sveltejs/kit';
|
||||
|
||||
export default function plugin(): Adapter;
|
120
node_modules/@sveltejs/adapter-auto/index.js
generated
vendored
Normal file
120
node_modules/@sveltejs/adapter-auto/index.js
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { resolve } from 'import-meta-resolve';
|
||||
import { adapters } from './adapters.js';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { existsSync } from 'node:fs';
|
||||
|
||||
/** @type {Record<string, (name: string, version: string) => string>} */
|
||||
const commands = {
|
||||
npm: (name, version) => `npm install -D ${name}@${version}`,
|
||||
pnpm: (name, version) => `pnpm add -D ${name}@${version}`,
|
||||
yarn: (name, version) => `yarn add -D ${name}@${version}`
|
||||
};
|
||||
|
||||
function detect_lockfile() {
|
||||
let dir = process.cwd();
|
||||
|
||||
do {
|
||||
if (existsSync(join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
||||
if (existsSync(join(dir, 'yarn.lock'))) return 'yarn';
|
||||
if (existsSync(join(dir, 'package-lock.json'))) return 'npm';
|
||||
} while (dir !== (dir = dirname(dir)));
|
||||
|
||||
return 'npm';
|
||||
}
|
||||
|
||||
function detect_package_manager() {
|
||||
const manager = detect_lockfile();
|
||||
|
||||
try {
|
||||
execSync(`${manager} --version`);
|
||||
return manager;
|
||||
} catch {
|
||||
return 'npm';
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} name */
|
||||
async function import_from_cwd(name) {
|
||||
const cwd = pathToFileURL(process.cwd()).href;
|
||||
const url = await resolve(name, cwd + '/x.js');
|
||||
|
||||
return import(url);
|
||||
}
|
||||
|
||||
/** @typedef {import('@sveltejs/kit').Adapter} Adapter */
|
||||
|
||||
/**
|
||||
* @returns {Promise<Adapter | undefined>} The corresponding adapter for the current environment if found otherwise undefined
|
||||
*/
|
||||
async function get_adapter() {
|
||||
const match = adapters.find((candidate) => candidate.test());
|
||||
|
||||
if (!match) return;
|
||||
|
||||
/** @type {{ default: () => Adapter }} */
|
||||
let module;
|
||||
|
||||
try {
|
||||
module = await import_from_cwd(match.module);
|
||||
} catch (error) {
|
||||
if (
|
||||
error.code === 'ERR_MODULE_NOT_FOUND' &&
|
||||
error.message.startsWith(`Cannot find package '${match.module}'`)
|
||||
) {
|
||||
const package_manager = detect_package_manager();
|
||||
const command = commands[package_manager](match.module, match.version);
|
||||
|
||||
try {
|
||||
console.log(`Installing ${match.module}...`);
|
||||
|
||||
execSync(command, {
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: undefined
|
||||
}
|
||||
});
|
||||
|
||||
module = await import_from_cwd(match.module);
|
||||
|
||||
console.log(`Successfully installed ${match.module}.`);
|
||||
console.warn(
|
||||
`\nIf you plan on staying on this deployment platform, consider replacing @sveltejs/adapter-auto with ${match.module}. This will give you faster and more robust installs, and more control over deployment configuration.\n`
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Could not install ${match.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`,
|
||||
{ cause: e }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const adapter = module.default();
|
||||
|
||||
return {
|
||||
...adapter,
|
||||
adapt: (builder) => {
|
||||
builder.log.info(`Detected environment: ${match.name}. Using ${match.module}`);
|
||||
return adapter.adapt(builder);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @type {() => Adapter} */
|
||||
export default () => ({
|
||||
name: '@sveltejs/adapter-auto',
|
||||
adapt: async (builder) => {
|
||||
const adapter = await get_adapter();
|
||||
|
||||
if (adapter) return adapter.adapt(builder);
|
||||
|
||||
builder.log.warn(
|
||||
'Could not detect a supported production environment. See https://kit.svelte.dev/docs/adapters to learn how to configure your app to run on the platform of your choosing'
|
||||
);
|
||||
}
|
||||
});
|
43
node_modules/@sveltejs/adapter-auto/package.json
generated
vendored
Normal file
43
node_modules/@sveltejs/adapter-auto/package.json
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@sveltejs/adapter-auto",
|
||||
"version": "2.1.1",
|
||||
"description": "Automatically chooses the SvelteKit adapter for your current environment, if possible.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sveltejs/kit",
|
||||
"directory": "packages/adapter-auto"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://kit.svelte.dev",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"types": "index.d.ts",
|
||||
"files": [
|
||||
"files",
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"adapters.js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.18.6",
|
||||
"typescript": "^4.9.4",
|
||||
"@sveltejs/kit": "^1.27.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"import-meta-resolve": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sveltejs/kit": "^1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
|
||||
"format": "pnpm lint --write",
|
||||
"check": "tsc"
|
||||
}
|
||||
}
|
7
node_modules/@sveltejs/kit/LICENSE
generated
vendored
Normal file
7
node_modules/@sveltejs/kit/LICENSE
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
18
node_modules/@sveltejs/kit/README.md
generated
vendored
Normal file
18
node_modules/@sveltejs/kit/README.md
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# The fastest way to build Svelte apps
|
||||
|
||||
This is the [SvelteKit](https://kit.svelte.dev) framework and CLI.
|
||||
|
||||
The quickest way to get started is via the [create-svelte](https://github.com/sveltejs/kit/tree/master/packages/create-svelte) package:
|
||||
|
||||
```bash
|
||||
npm create svelte@latest my-app
|
||||
cd my-app
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
See the [documentation](https://kit.svelte.dev/docs) to learn more.
|
||||
|
||||
## Changelog
|
||||
|
||||
[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md).
|
100
node_modules/@sveltejs/kit/package.json
generated
vendored
Normal file
100
node_modules/@sveltejs/kit/package.json
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "@sveltejs/kit",
|
||||
"version": "1.27.5",
|
||||
"description": "The fastest way to build Svelte apps",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sveltejs/kit",
|
||||
"directory": "packages/kit"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://kit.svelte.dev",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.5.0",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"cookie": "^0.5.0",
|
||||
"devalue": "^4.3.1",
|
||||
"esm-env": "^1.0.0",
|
||||
"kleur": "^4.1.5",
|
||||
"magic-string": "^0.30.0",
|
||||
"mrmime": "^1.0.1",
|
||||
"sade": "^1.8.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"sirv": "^2.0.2",
|
||||
"tiny-glob": "^0.2.9",
|
||||
"undici": "~5.26.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.30.0",
|
||||
"@types/connect": "^3.4.35",
|
||||
"@types/node": "^16.18.6",
|
||||
"@types/sade": "^1.7.4",
|
||||
"@types/set-cookie-parser": "^2.4.2",
|
||||
"dts-buddy": "^0.2.4",
|
||||
"marked": "^9.0.0",
|
||||
"rollup": "^3.29.4",
|
||||
"svelte": "^4.2.2",
|
||||
"svelte-preprocess": "^5.0.4",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^0.34.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"svelte-kit": "svelte-kit.js"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"!src/**/*.spec.js",
|
||||
"!src/core/**/fixtures",
|
||||
"!src/core/**/test",
|
||||
"types",
|
||||
"svelte-kit.js",
|
||||
"postinstall.js"
|
||||
],
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./src/exports/index.js"
|
||||
},
|
||||
"./node": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./src/exports/node/index.js"
|
||||
},
|
||||
"./node/polyfills": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./src/exports/node/polyfills.js"
|
||||
},
|
||||
"./hooks": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./src/exports/hooks/index.js"
|
||||
},
|
||||
"./vite": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./src/exports/vite/index.js"
|
||||
}
|
||||
},
|
||||
"types": "types/index.d.ts",
|
||||
"engines": {
|
||||
"node": "^16.14 || >=18"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
|
||||
"check": "tsc",
|
||||
"check:all": "tsc && pnpm -r --filter=\"./**\" check",
|
||||
"format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
|
||||
"test": "pnpm test:unit && pnpm test:integration",
|
||||
"test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test",
|
||||
"test:cross-platform:dev": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:dev",
|
||||
"test:cross-platform:build": "pnpm test:unit && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:build",
|
||||
"test:unit": "vitest --config kit.vitest.config.js run",
|
||||
"postinstall": "node postinstall.js",
|
||||
"generate:version": "node scripts/generate-version.js",
|
||||
"generate:types": "node scripts/generate-dts.js"
|
||||
}
|
||||
}
|
52
node_modules/@sveltejs/kit/postinstall.js
generated
vendored
Normal file
52
node_modules/@sveltejs/kit/postinstall.js
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
import { load_config } from './src/core/config/index.js';
|
||||
import * as sync from './src/core/sync/sync.js';
|
||||
import glob from 'tiny-glob/sync.js';
|
||||
import fs from 'node:fs';
|
||||
|
||||
try {
|
||||
const cwd = process.env.INIT_CWD ?? process.cwd();
|
||||
process.chdir(cwd);
|
||||
|
||||
if (fs.existsSync('package.json')) {
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
|
||||
const workspaces = [];
|
||||
|
||||
if (pkg.workspaces) {
|
||||
// Find all npm and Yarn workspace glob patterns
|
||||
// https://classic.yarnpkg.com/blog/2018/02/15/nohoist/
|
||||
// https://docs.npmjs.com/cli/v9/configuring-npm/package-json#workspaces
|
||||
const patterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
|
||||
|
||||
for (const pattern of patterns) {
|
||||
workspaces.push(
|
||||
...glob(pattern, { cwd, absolute: true }).filter((path) =>
|
||||
fs.statSync(path).isDirectory()
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
workspaces.push(cwd);
|
||||
}
|
||||
|
||||
for (const cwd of workspaces) {
|
||||
process.chdir(cwd);
|
||||
|
||||
if (!fs.existsSync('package.json')) continue;
|
||||
if (!fs.existsSync('svelte.config.js')) continue;
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
if (!pkg.dependencies?.['@sveltejs/kit'] && !pkg.devDependencies?.['@sveltejs/kit']) continue;
|
||||
|
||||
try {
|
||||
const config = await load_config();
|
||||
await sync.all(config, 'development');
|
||||
} catch (error) {
|
||||
console.error('Error while trying to sync SvelteKit config');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
44
node_modules/@sveltejs/kit/src/cli.js
generated
vendored
Normal file
44
node_modules/@sveltejs/kit/src/cli.js
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import colors from 'kleur';
|
||||
import sade from 'sade';
|
||||
import { load_config } from './core/config/index.js';
|
||||
import { coalesce_to_error } from './utils/error.js';
|
||||
|
||||
/** @param {unknown} e */
|
||||
function handle_error(e) {
|
||||
const error = coalesce_to_error(e);
|
||||
|
||||
if (error.name === 'SyntaxError') throw error;
|
||||
|
||||
console.error(colors.bold().red(`> ${error.message}`));
|
||||
if (error.stack) {
|
||||
console.error(colors.gray(error.stack.split('\n').slice(1).join('\n')));
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
||||
const prog = sade('svelte-kit').version(pkg.version);
|
||||
|
||||
prog
|
||||
.command('sync')
|
||||
.describe('Synchronise generated type definitions')
|
||||
.option('--mode', 'Specify a mode for loading environment variables', 'development')
|
||||
.action(async ({ mode }) => {
|
||||
if (!fs.existsSync('svelte.config.js')) {
|
||||
console.warn(`Missing ${path.resolve('svelte.config.js')} — skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await load_config();
|
||||
const sync = await import('./core/sync/sync.js');
|
||||
await sync.all_types(config, mode);
|
||||
} catch (error) {
|
||||
handle_error(error);
|
||||
}
|
||||
});
|
||||
|
||||
prog.parse(process.argv, { unknown: (arg) => `Unknown option: ${arg}` });
|
19
node_modules/@sveltejs/kit/src/constants.js
generated
vendored
Normal file
19
node_modules/@sveltejs/kit/src/constants.js
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* A fake asset path used in `vite dev` and `vite preview`, so that we can
|
||||
* serve local assets while verifying that requests are correctly prefixed
|
||||
*/
|
||||
export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets';
|
||||
|
||||
export const GENERATED_COMMENT = '// this file is generated — do not edit it\n';
|
||||
|
||||
export const ENDPOINT_METHODS = new Set([
|
||||
'GET',
|
||||
'POST',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'DELETE',
|
||||
'OPTIONS',
|
||||
'HEAD'
|
||||
]);
|
||||
|
||||
export const PAGE_METHODS = new Set(['GET', 'POST', 'HEAD']);
|
223
node_modules/@sveltejs/kit/src/core/adapt/builder.js
generated
vendored
Normal file
223
node_modules/@sveltejs/kit/src/core/adapt/builder.js
generated
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
import { existsSync, statSync, createReadStream, createWriteStream } from 'node:fs';
|
||||
import { extname, resolve } from 'node:path';
|
||||
import { pipeline } from 'node:stream';
|
||||
import { promisify } from 'node:util';
|
||||
import zlib from 'node:zlib';
|
||||
import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
|
||||
import { generate_manifest } from '../generate_manifest/index.js';
|
||||
import { get_route_segments } from '../../utils/routing.js';
|
||||
import { get_env } from '../../exports/vite/utils.js';
|
||||
import generate_fallback from '../postbuild/fallback.js';
|
||||
import { write } from '../sync/utils.js';
|
||||
import { list_files } from '../utils.js';
|
||||
|
||||
const pipe = promisify(pipeline);
|
||||
const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.wasm'];
|
||||
|
||||
/**
|
||||
* Creates the Builder which is passed to adapters for building the application.
|
||||
* @param {{
|
||||
* config: import('types').ValidatedConfig;
|
||||
* build_data: import('types').BuildData;
|
||||
* server_metadata: import('types').ServerMetadata;
|
||||
* route_data: import('types').RouteData[];
|
||||
* prerendered: import('types').Prerendered;
|
||||
* prerender_map: import('types').PrerenderMap;
|
||||
* log: import('types').Logger;
|
||||
* vite_config: import('vite').ResolvedConfig;
|
||||
* }} opts
|
||||
* @returns {import('@sveltejs/kit').Builder}
|
||||
*/
|
||||
export function create_builder({
|
||||
config,
|
||||
build_data,
|
||||
server_metadata,
|
||||
route_data,
|
||||
prerendered,
|
||||
prerender_map,
|
||||
log,
|
||||
vite_config
|
||||
}) {
|
||||
/** @type {Map<import('@sveltejs/kit').RouteDefinition, import('types').RouteData>} */
|
||||
const lookup = new Map();
|
||||
|
||||
/**
|
||||
* Rather than exposing the internal `RouteData` type, which is subject to change,
|
||||
* we expose a stable type that adapters can use to group/filter routes
|
||||
*/
|
||||
const routes = route_data.map((route) => {
|
||||
const { config, methods, page, api } = /** @type {import('types').ServerMetadataRoute} */ (
|
||||
server_metadata.routes.get(route.id)
|
||||
);
|
||||
|
||||
/** @type {import('@sveltejs/kit').RouteDefinition} */
|
||||
const facade = {
|
||||
id: route.id,
|
||||
api,
|
||||
page,
|
||||
segments: get_route_segments(route.id).map((segment) => ({
|
||||
dynamic: segment.includes('['),
|
||||
rest: segment.includes('[...'),
|
||||
content: segment
|
||||
})),
|
||||
pattern: route.pattern,
|
||||
prerender: prerender_map.get(route.id) ?? false,
|
||||
methods,
|
||||
config
|
||||
};
|
||||
|
||||
lookup.set(facade, route);
|
||||
|
||||
return facade;
|
||||
});
|
||||
|
||||
return {
|
||||
log,
|
||||
rimraf,
|
||||
mkdirp,
|
||||
copy,
|
||||
|
||||
config,
|
||||
prerendered,
|
||||
routes,
|
||||
|
||||
async compress(directory) {
|
||||
if (!existsSync(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = list_files(directory, (file) => extensions.includes(extname(file))).map(
|
||||
(file) => resolve(directory, file)
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
files.flatMap((file) => [compress_file(file, 'gz'), compress_file(file, 'br')])
|
||||
);
|
||||
},
|
||||
|
||||
async createEntries(fn) {
|
||||
const seen = new Set();
|
||||
|
||||
for (let i = 0; i < route_data.length; i += 1) {
|
||||
const route = route_data[i];
|
||||
if (prerender_map.get(route.id) === true) continue;
|
||||
const { id, filter, complete } = fn(routes[i]);
|
||||
|
||||
if (seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
|
||||
const group = [route];
|
||||
|
||||
// figure out which lower priority routes should be considered fallbacks
|
||||
for (let j = i + 1; j < route_data.length; j += 1) {
|
||||
if (prerender_map.get(routes[j].id) === true) continue;
|
||||
if (filter(routes[j])) {
|
||||
group.push(route_data[j]);
|
||||
}
|
||||
}
|
||||
|
||||
const filtered = new Set(group);
|
||||
|
||||
// heuristic: if /foo/[bar] is included, /foo/[bar].json should
|
||||
// also be included, since the page likely needs the endpoint
|
||||
// TODO is this still necessary, given the new way of doing things?
|
||||
filtered.forEach((route) => {
|
||||
if (route.page) {
|
||||
const endpoint = route_data.find((candidate) => candidate.id === route.id + '.json');
|
||||
|
||||
if (endpoint) {
|
||||
filtered.add(endpoint);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (filtered.size > 0) {
|
||||
await complete({
|
||||
generateManifest: ({ relativePath }) =>
|
||||
generate_manifest({
|
||||
build_data,
|
||||
relative_path: relativePath,
|
||||
routes: Array.from(filtered)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async generateFallback(dest) {
|
||||
const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`;
|
||||
const env = get_env(config.kit.env, vite_config.mode);
|
||||
|
||||
const fallback = await generate_fallback({
|
||||
manifest_path,
|
||||
env: { ...env.private, ...env.public }
|
||||
});
|
||||
|
||||
write(dest, fallback);
|
||||
},
|
||||
|
||||
generateManifest({ relativePath, routes: subset }) {
|
||||
return generate_manifest({
|
||||
build_data,
|
||||
relative_path: relativePath,
|
||||
routes: subset
|
||||
? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route)))
|
||||
: route_data.filter((route) => prerender_map.get(route.id) !== true)
|
||||
});
|
||||
},
|
||||
|
||||
getBuildDirectory(name) {
|
||||
return `${config.kit.outDir}/${name}`;
|
||||
},
|
||||
|
||||
getClientDirectory() {
|
||||
return `${config.kit.outDir}/output/client`;
|
||||
},
|
||||
|
||||
getServerDirectory() {
|
||||
return `${config.kit.outDir}/output/server`;
|
||||
},
|
||||
|
||||
getAppPath() {
|
||||
return build_data.app_path;
|
||||
},
|
||||
|
||||
writeClient(dest) {
|
||||
return copy(`${config.kit.outDir}/output/client`, dest, {
|
||||
// avoid making vite build artefacts public
|
||||
filter: (basename) => basename !== '.vite'
|
||||
});
|
||||
},
|
||||
|
||||
writePrerendered(dest) {
|
||||
const source = `${config.kit.outDir}/output/prerendered`;
|
||||
return [...copy(`${source}/pages`, dest), ...copy(`${source}/dependencies`, dest)];
|
||||
},
|
||||
|
||||
writeServer(dest) {
|
||||
return copy(`${config.kit.outDir}/output/server`, dest);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {'gz' | 'br'} format
|
||||
*/
|
||||
async function compress_file(file, format = 'gz') {
|
||||
const compress =
|
||||
format == 'br'
|
||||
? zlib.createBrotliCompress({
|
||||
params: {
|
||||
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
|
||||
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
|
||||
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: statSync(file).size
|
||||
}
|
||||
})
|
||||
: zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION });
|
||||
|
||||
const source = createReadStream(file);
|
||||
const destination = createWriteStream(`${file}.${format}`);
|
||||
|
||||
await pipe(source, compress, destination);
|
||||
}
|
40
node_modules/@sveltejs/kit/src/core/adapt/index.js
generated
vendored
Normal file
40
node_modules/@sveltejs/kit/src/core/adapt/index.js
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
import colors from 'kleur';
|
||||
import { create_builder } from './builder.js';
|
||||
|
||||
/**
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {import('types').BuildData} build_data
|
||||
* @param {import('types').ServerMetadata} server_metadata
|
||||
* @param {import('types').Prerendered} prerendered
|
||||
* @param {import('types').PrerenderMap} prerender_map
|
||||
* @param {import('types').Logger} log
|
||||
* @param {import('vite').ResolvedConfig} vite_config
|
||||
*/
|
||||
export async function adapt(
|
||||
config,
|
||||
build_data,
|
||||
server_metadata,
|
||||
prerendered,
|
||||
prerender_map,
|
||||
log,
|
||||
vite_config
|
||||
) {
|
||||
const { name, adapt } = config.kit.adapter;
|
||||
|
||||
console.log(colors.bold().cyan(`\n> Using ${name}`));
|
||||
|
||||
const builder = create_builder({
|
||||
config,
|
||||
build_data,
|
||||
server_metadata,
|
||||
route_data: build_data.manifest_data.routes.filter((route) => route.page || route.endpoint),
|
||||
prerendered,
|
||||
prerender_map,
|
||||
log,
|
||||
vite_config
|
||||
});
|
||||
|
||||
await adapt(builder);
|
||||
|
||||
log.success('done');
|
||||
}
|
70
node_modules/@sveltejs/kit/src/core/config/default-error.html
generated
vendored
Normal file
70
node_modules/@sveltejs/kit/src/core/config/default-error.html
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>%sveltekit.error.message%</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
--bg: white;
|
||||
--fg: #222;
|
||||
--divider: #ccc;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 32rem;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-weight: 200;
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
top: -0.05rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
border-left: 1px solid var(--divider);
|
||||
padding: 0 0 0 1rem;
|
||||
margin: 0 0 0 1rem;
|
||||
min-height: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message h1 {
|
||||
font-weight: 400;
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
--bg: #222;
|
||||
--fg: #ddd;
|
||||
--divider: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error">
|
||||
<span class="status">%sveltekit.status%</span>
|
||||
<div class="message">
|
||||
<h1>%sveltekit.error.message%</h1>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
109
node_modules/@sveltejs/kit/src/core/config/index.js
generated
vendored
Normal file
109
node_modules/@sveltejs/kit/src/core/config/index.js
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import * as url from 'node:url';
|
||||
import options from './options.js';
|
||||
|
||||
/**
|
||||
* Loads the template (src/app.html by default) and validates that it has the
|
||||
* required content.
|
||||
* @param {string} cwd
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
*/
|
||||
export function load_template(cwd, { kit }) {
|
||||
const { env, files } = kit;
|
||||
|
||||
const relative = path.relative(cwd, files.appTemplate);
|
||||
|
||||
if (!fs.existsSync(files.appTemplate)) {
|
||||
throw new Error(`${relative} does not exist`);
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(files.appTemplate, 'utf8');
|
||||
|
||||
const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
|
||||
expected_tags.forEach((tag) => {
|
||||
if (contents.indexOf(tag) === -1) {
|
||||
throw new Error(`${relative} is missing ${tag}`);
|
||||
}
|
||||
});
|
||||
|
||||
for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
|
||||
if (!match[1].startsWith(env.publicPrefix)) {
|
||||
throw new Error(
|
||||
`Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the error page (src/error.html by default) if it exists.
|
||||
* Falls back to a generic error page content.
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
*/
|
||||
export function load_error_page(config) {
|
||||
let { errorTemplate } = config.kit.files;
|
||||
|
||||
// Don't do this inside resolving the config, because that would mean
|
||||
// adding/removing error.html isn't detected and would require a restart.
|
||||
if (!fs.existsSync(config.kit.files.errorTemplate)) {
|
||||
errorTemplate = url.fileURLToPath(new URL('./default-error.html', import.meta.url));
|
||||
}
|
||||
|
||||
return fs.readFileSync(errorTemplate, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and validates svelte.config.js
|
||||
* @param {{ cwd?: string }} options
|
||||
* @returns {Promise<import('types').ValidatedConfig>}
|
||||
*/
|
||||
export async function load_config({ cwd = process.cwd() } = {}) {
|
||||
const config_file = path.join(cwd, 'svelte.config.js');
|
||||
|
||||
if (!fs.existsSync(config_file)) {
|
||||
return process_config({}, { cwd });
|
||||
}
|
||||
|
||||
const config = await import(`${url.pathToFileURL(config_file).href}?ts=${Date.now()}`);
|
||||
|
||||
return process_config(config.default, { cwd });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').Config} config
|
||||
* @returns {import('types').ValidatedConfig}
|
||||
*/
|
||||
function process_config(config, { cwd = process.cwd() } = {}) {
|
||||
const validated = validate_config(config);
|
||||
|
||||
validated.kit.outDir = path.resolve(cwd, validated.kit.outDir);
|
||||
|
||||
for (const key in validated.kit.files) {
|
||||
if (key === 'hooks') {
|
||||
validated.kit.files.hooks.client = path.resolve(cwd, validated.kit.files.hooks.client);
|
||||
validated.kit.files.hooks.server = path.resolve(cwd, validated.kit.files.hooks.server);
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
validated.kit.files[key] = path.resolve(cwd, validated.kit.files[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').Config} config
|
||||
* @returns {import('types').ValidatedConfig}
|
||||
*/
|
||||
export function validate_config(config) {
|
||||
if (typeof config !== 'object') {
|
||||
throw new Error(
|
||||
'svelte.config.js must have a configuration object as its default export. See https://kit.svelte.dev/docs/configuration'
|
||||
);
|
||||
}
|
||||
|
||||
return options(config, 'config');
|
||||
}
|
438
node_modules/@sveltejs/kit/src/core/config/options.js
generated
vendored
Normal file
438
node_modules/@sveltejs/kit/src/core/config/options.js
generated
vendored
Normal file
@ -0,0 +1,438 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
/** @typedef {import('./types.js').Validator} Validator */
|
||||
|
||||
const directives = object({
|
||||
'child-src': string_array(),
|
||||
'default-src': string_array(),
|
||||
'frame-src': string_array(),
|
||||
'worker-src': string_array(),
|
||||
'connect-src': string_array(),
|
||||
'font-src': string_array(),
|
||||
'img-src': string_array(),
|
||||
'manifest-src': string_array(),
|
||||
'media-src': string_array(),
|
||||
'object-src': string_array(),
|
||||
'prefetch-src': string_array(),
|
||||
'script-src': string_array(),
|
||||
'script-src-elem': string_array(),
|
||||
'script-src-attr': string_array(),
|
||||
'style-src': string_array(),
|
||||
'style-src-elem': string_array(),
|
||||
'style-src-attr': string_array(),
|
||||
'base-uri': string_array(),
|
||||
sandbox: string_array(),
|
||||
'form-action': string_array(),
|
||||
'frame-ancestors': string_array(),
|
||||
'navigate-to': string_array(),
|
||||
'report-uri': string_array(),
|
||||
'report-to': string_array(),
|
||||
'require-trusted-types-for': string_array(),
|
||||
'trusted-types': string_array(),
|
||||
'upgrade-insecure-requests': boolean(false),
|
||||
'require-sri-for': string_array(),
|
||||
'block-all-mixed-content': boolean(false),
|
||||
'plugin-types': string_array(),
|
||||
referrer: string_array()
|
||||
});
|
||||
|
||||
/** @type {Validator} */
|
||||
const options = object(
|
||||
{
|
||||
extensions: validate(['.svelte'], (input, keypath) => {
|
||||
if (!Array.isArray(input) || !input.every((page) => typeof page === 'string')) {
|
||||
throw new Error(`${keypath} must be an array of strings`);
|
||||
}
|
||||
|
||||
input.forEach((extension) => {
|
||||
if (extension[0] !== '.') {
|
||||
throw new Error(`Each member of ${keypath} must start with '.' — saw '${extension}'`);
|
||||
}
|
||||
|
||||
if (!/^(\.[a-z0-9]+)+$/i.test(extension)) {
|
||||
throw new Error(`File extensions must be alphanumeric — saw '${extension}'`);
|
||||
}
|
||||
});
|
||||
|
||||
return input;
|
||||
}),
|
||||
|
||||
kit: object({
|
||||
adapter: validate(null, (input, keypath) => {
|
||||
if (typeof input !== 'object' || !input.adapt) {
|
||||
let message = `${keypath} should be an object with an "adapt" method`;
|
||||
|
||||
if (Array.isArray(input) || typeof input === 'string') {
|
||||
// for the early adapter adopters
|
||||
message += ', rather than the name of an adapter';
|
||||
}
|
||||
|
||||
throw new Error(`${message}. See https://kit.svelte.dev/docs/adapters`);
|
||||
}
|
||||
|
||||
return input;
|
||||
}),
|
||||
|
||||
alias: validate({}, (input, keypath) => {
|
||||
if (typeof input !== 'object') {
|
||||
throw new Error(`${keypath} should be an object`);
|
||||
}
|
||||
|
||||
for (const key in input) {
|
||||
assert_string(input[key], `${keypath}.${key}`);
|
||||
}
|
||||
|
||||
return input;
|
||||
}),
|
||||
|
||||
appDir: validate('_app', (input, keypath) => {
|
||||
assert_string(input, keypath);
|
||||
|
||||
if (input) {
|
||||
if (input.startsWith('/') || input.endsWith('/')) {
|
||||
throw new Error(
|
||||
"config.kit.appDir cannot start or end with '/'. See https://kit.svelte.dev/docs/configuration"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`${keypath} cannot be empty`);
|
||||
}
|
||||
|
||||
return input;
|
||||
}),
|
||||
|
||||
csp: object({
|
||||
mode: list(['auto', 'hash', 'nonce']),
|
||||
directives,
|
||||
reportOnly: directives
|
||||
}),
|
||||
|
||||
csrf: object({
|
||||
checkOrigin: boolean(true)
|
||||
}),
|
||||
|
||||
dangerZone: object({
|
||||
// TODO 2.0: Remove this
|
||||
trackServerFetches: boolean(false)
|
||||
}),
|
||||
|
||||
embedded: boolean(false),
|
||||
|
||||
env: object({
|
||||
dir: string(process.cwd()),
|
||||
publicPrefix: string('PUBLIC_'),
|
||||
privatePrefix: string('')
|
||||
}),
|
||||
|
||||
files: object({
|
||||
assets: string('static'),
|
||||
hooks: object({
|
||||
client: string(join('src', 'hooks.client')),
|
||||
server: string(join('src', 'hooks.server'))
|
||||
}),
|
||||
lib: string(join('src', 'lib')),
|
||||
params: string(join('src', 'params')),
|
||||
routes: string(join('src', 'routes')),
|
||||
serviceWorker: string(join('src', 'service-worker')),
|
||||
appTemplate: string(join('src', 'app.html')),
|
||||
errorTemplate: string(join('src', 'error.html'))
|
||||
}),
|
||||
|
||||
inlineStyleThreshold: number(0),
|
||||
|
||||
moduleExtensions: string_array(['.js', '.ts']),
|
||||
|
||||
outDir: string('.svelte-kit'),
|
||||
|
||||
output: object({
|
||||
preloadStrategy: list(['modulepreload', 'preload-js', 'preload-mjs'], 'modulepreload')
|
||||
}),
|
||||
|
||||
paths: object({
|
||||
base: validate('', (input, keypath) => {
|
||||
assert_string(input, keypath);
|
||||
|
||||
if (input !== '' && (input.endsWith('/') || !input.startsWith('/'))) {
|
||||
throw new Error(
|
||||
`${keypath} option must either be the empty string or a root-relative path that starts but doesn't end with '/'. See https://kit.svelte.dev/docs/configuration#paths`
|
||||
);
|
||||
}
|
||||
|
||||
return input;
|
||||
}),
|
||||
assets: validate('', (input, keypath) => {
|
||||
assert_string(input, keypath);
|
||||
|
||||
if (input) {
|
||||
if (!/^[a-z]+:\/\//.test(input)) {
|
||||
throw new Error(
|
||||
`${keypath} option must be an absolute path, if specified. See https://kit.svelte.dev/docs/configuration#paths`
|
||||
);
|
||||
}
|
||||
|
||||
if (input.endsWith('/')) {
|
||||
throw new Error(
|
||||
`${keypath} option must not end with '/'. See https://kit.svelte.dev/docs/configuration#paths`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}),
|
||||
relative: validate(undefined, (input, keypath) => {
|
||||
if (typeof input !== 'boolean') {
|
||||
throw new Error(`${keypath} option must be a boolean or undefined`);
|
||||
}
|
||||
|
||||
return input;
|
||||
})
|
||||
}),
|
||||
|
||||
prerender: object({
|
||||
concurrency: number(1),
|
||||
crawl: boolean(true),
|
||||
entries: validate(['*'], (input, keypath) => {
|
||||
if (!Array.isArray(input) || !input.every((page) => typeof page === 'string')) {
|
||||
throw new Error(`${keypath} must be an array of strings`);
|
||||
}
|
||||
|
||||
input.forEach((page) => {
|
||||
if (page !== '*' && page[0] !== '/') {
|
||||
throw new Error(
|
||||
`Each member of ${keypath} must be either '*' or an absolute path beginning with '/' — saw '${page}'`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return input;
|
||||
}),
|
||||
|
||||
handleHttpError: validate(
|
||||
(/** @type {any} */ { message }) => {
|
||||
throw new Error(
|
||||
message +
|
||||
'\nTo suppress or handle this error, implement `handleHttpError` in https://kit.svelte.dev/docs/configuration#prerender'
|
||||
);
|
||||
},
|
||||
(input, keypath) => {
|
||||
if (typeof input === 'function') return input;
|
||||
if (['fail', 'warn', 'ignore'].includes(input)) return input;
|
||||
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
|
||||
}
|
||||
),
|
||||
|
||||
handleMissingId: validate(
|
||||
(/** @type {any} */ { message }) => {
|
||||
throw new Error(
|
||||
message +
|
||||
'\nTo suppress or handle this error, implement `handleMissingId` in https://kit.svelte.dev/docs/configuration#prerender'
|
||||
);
|
||||
},
|
||||
(input, keypath) => {
|
||||
if (typeof input === 'function') return input;
|
||||
if (['fail', 'warn', 'ignore'].includes(input)) return input;
|
||||
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
|
||||
}
|
||||
),
|
||||
|
||||
handleEntryGeneratorMismatch: validate(
|
||||
(/** @type {any} */ { message }) => {
|
||||
throw new Error(
|
||||
message +
|
||||
'\nTo suppress or handle this error, implement `handleEntryGeneratorMismatch` in https://kit.svelte.dev/docs/configuration#prerender'
|
||||
);
|
||||
},
|
||||
(input, keypath) => {
|
||||
if (typeof input === 'function') return input;
|
||||
if (['fail', 'warn', 'ignore'].includes(input)) return input;
|
||||
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
|
||||
}
|
||||
),
|
||||
|
||||
origin: validate('http://sveltekit-prerender', (input, keypath) => {
|
||||
assert_string(input, keypath);
|
||||
|
||||
let origin;
|
||||
|
||||
try {
|
||||
origin = new URL(input).origin;
|
||||
} catch (e) {
|
||||
throw new Error(`${keypath} must be a valid origin`);
|
||||
}
|
||||
|
||||
if (input !== origin) {
|
||||
throw new Error(`${keypath} must be a valid origin (${origin} rather than ${input})`);
|
||||
}
|
||||
|
||||
return origin;
|
||||
})
|
||||
}),
|
||||
|
||||
serviceWorker: object({
|
||||
register: boolean(true),
|
||||
files: fun((filename) => !/\.DS_Store/.test(filename))
|
||||
}),
|
||||
|
||||
typescript: object({
|
||||
config: fun((config) => config)
|
||||
}),
|
||||
|
||||
version: object({
|
||||
name: string(Date.now().toString()),
|
||||
pollInterval: number(0)
|
||||
})
|
||||
})
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {Record<string, Validator>} children
|
||||
* @param {boolean} [allow_unknown]
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function object(children, allow_unknown = false) {
|
||||
return (input, keypath) => {
|
||||
/** @type {Record<string, any>} */
|
||||
const output = {};
|
||||
|
||||
if ((input && typeof input !== 'object') || Array.isArray(input)) {
|
||||
throw new Error(`${keypath} should be an object`);
|
||||
}
|
||||
|
||||
for (const key in input) {
|
||||
if (!(key in children)) {
|
||||
if (allow_unknown) {
|
||||
output[key] = input[key];
|
||||
} else {
|
||||
let message = `Unexpected option ${keypath}.${key}`;
|
||||
|
||||
// special case
|
||||
if (keypath === 'config.kit' && key in options) {
|
||||
message += ` (did you mean config.${key}?)`;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in children) {
|
||||
const validator = children[key];
|
||||
output[key] = validator(input && input[key], `${keypath}.${key}`);
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} fallback
|
||||
* @param {(value: any, keypath: string) => any} fn
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function validate(fallback, fn) {
|
||||
return (input, keypath) => {
|
||||
return input === undefined ? fallback : fn(input, keypath);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | null} fallback
|
||||
* @param {boolean} allow_empty
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function string(fallback, allow_empty = true) {
|
||||
return validate(fallback, (input, keypath) => {
|
||||
assert_string(input, keypath);
|
||||
|
||||
if (!allow_empty && input === '') {
|
||||
throw new Error(`${keypath} cannot be empty`);
|
||||
}
|
||||
|
||||
return input;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[] | undefined} [fallback]
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function string_array(fallback) {
|
||||
return validate(fallback, (input, keypath) => {
|
||||
if (!Array.isArray(input) || input.some((value) => typeof value !== 'string')) {
|
||||
throw new Error(`${keypath} must be an array of strings, if specified`);
|
||||
}
|
||||
|
||||
return input;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} fallback
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function number(fallback) {
|
||||
return validate(fallback, (input, keypath) => {
|
||||
if (typeof input !== 'number') {
|
||||
throw new Error(`${keypath} should be a number, if specified`);
|
||||
}
|
||||
return input;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} fallback
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function boolean(fallback) {
|
||||
return validate(fallback, (input, keypath) => {
|
||||
if (typeof input !== 'boolean') {
|
||||
throw new Error(`${keypath} should be true or false, if specified`);
|
||||
}
|
||||
return input;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} options
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function list(options, fallback = options[0]) {
|
||||
return validate(fallback, (input, keypath) => {
|
||||
if (!options.includes(input)) {
|
||||
// prettier-ignore
|
||||
const msg = options.length > 2
|
||||
? `${keypath} should be one of ${options.slice(0, -1).map(input => `"${input}"`).join(', ')} or "${options[options.length - 1]}"`
|
||||
: `${keypath} should be either "${options[0]}" or "${options[1]}"`;
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
return input;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(...args: any) => any} fallback
|
||||
* @returns {Validator}
|
||||
*/
|
||||
function fun(fallback) {
|
||||
return validate(fallback, (input, keypath) => {
|
||||
if (typeof input !== 'function') {
|
||||
throw new Error(`${keypath} should be a function, if specified`);
|
||||
}
|
||||
return input;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {string} keypath
|
||||
*/
|
||||
function assert_string(input, keypath) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new Error(`${keypath} should be a string, if specified`);
|
||||
}
|
||||
}
|
||||
|
||||
export default options;
|
1
node_modules/@sveltejs/kit/src/core/config/types.d.ts
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/core/config/types.d.ts
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export type Validator<T = any> = (input: T, keypath: string) => T;
|
152
node_modules/@sveltejs/kit/src/core/env.js
generated
vendored
Normal file
152
node_modules/@sveltejs/kit/src/core/env.js
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
import { GENERATED_COMMENT } from '../constants.js';
|
||||
import { dedent } from './sync/utils.js';
|
||||
import { runtime_base } from './utils.js';
|
||||
|
||||
/**
|
||||
* @typedef {'public' | 'private'} EnvType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {Record<string, string>} env
|
||||
* @returns {string}
|
||||
*/
|
||||
export function create_static_module(id, env) {
|
||||
/** @type {string[]} */
|
||||
const declarations = [];
|
||||
|
||||
for (const key in env) {
|
||||
if (!valid_identifier.test(key) || reserved.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const comment = `/** @type {import('${id}').${key}} */`;
|
||||
const declaration = `export const ${key} = ${JSON.stringify(env[key])};`;
|
||||
|
||||
declarations.push(`${comment}\n${declaration}`);
|
||||
}
|
||||
|
||||
return GENERATED_COMMENT + declarations.join('\n\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EnvType} type
|
||||
* @param {Record<string, string> | undefined} dev_values If in a development mode, values to pre-populate the module with.
|
||||
*/
|
||||
export function create_dynamic_module(type, dev_values) {
|
||||
if (dev_values) {
|
||||
const keys = Object.entries(dev_values).map(
|
||||
([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`
|
||||
);
|
||||
return `export const env = {\n${keys.join(',\n')}\n}`;
|
||||
}
|
||||
return `export { ${type}_env as env } from '${runtime_base}/shared-server.js';`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EnvType} id
|
||||
* @param {import('types').Env} env
|
||||
* @returns {string}
|
||||
*/
|
||||
export function create_static_types(id, env) {
|
||||
const declarations = Object.keys(env[id])
|
||||
.filter((k) => valid_identifier.test(k))
|
||||
.map((k) => `export const ${k}: string;`);
|
||||
|
||||
return dedent`
|
||||
declare module '$env/static/${id}' {
|
||||
${declarations.join('\n')}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EnvType} id
|
||||
* @param {import('types').Env} env
|
||||
* @param {{
|
||||
* public_prefix: string;
|
||||
* private_prefix: string;
|
||||
* }} prefixes
|
||||
* @returns {string}
|
||||
*/
|
||||
export function create_dynamic_types(id, env, { public_prefix, private_prefix }) {
|
||||
const properties = Object.keys(env[id])
|
||||
.filter((k) => valid_identifier.test(k))
|
||||
.map((k) => `${k}: string;`);
|
||||
|
||||
const public_prefixed = `[key: \`${public_prefix}\${string}\`]`;
|
||||
const private_prefixed = `[key: \`${private_prefix}\${string}\`]`;
|
||||
|
||||
if (id === 'private') {
|
||||
if (public_prefix) {
|
||||
properties.push(`${public_prefixed}: undefined;`);
|
||||
}
|
||||
properties.push(`${private_prefixed}: string | undefined;`);
|
||||
} else {
|
||||
if (private_prefix) {
|
||||
properties.push(`${private_prefixed}: undefined;`);
|
||||
}
|
||||
properties.push(`${public_prefixed}: string | undefined;`);
|
||||
}
|
||||
|
||||
return dedent`
|
||||
declare module '$env/dynamic/${id}' {
|
||||
export const env: {
|
||||
${properties.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
export const reserved = new Set([
|
||||
'do',
|
||||
'if',
|
||||
'in',
|
||||
'for',
|
||||
'let',
|
||||
'new',
|
||||
'try',
|
||||
'var',
|
||||
'case',
|
||||
'else',
|
||||
'enum',
|
||||
'eval',
|
||||
'null',
|
||||
'this',
|
||||
'true',
|
||||
'void',
|
||||
'with',
|
||||
'await',
|
||||
'break',
|
||||
'catch',
|
||||
'class',
|
||||
'const',
|
||||
'false',
|
||||
'super',
|
||||
'throw',
|
||||
'while',
|
||||
'yield',
|
||||
'delete',
|
||||
'export',
|
||||
'import',
|
||||
'public',
|
||||
'return',
|
||||
'static',
|
||||
'switch',
|
||||
'typeof',
|
||||
'default',
|
||||
'extends',
|
||||
'finally',
|
||||
'package',
|
||||
'private',
|
||||
'continue',
|
||||
'debugger',
|
||||
'function',
|
||||
'arguments',
|
||||
'interface',
|
||||
'protected',
|
||||
'implements',
|
||||
'instanceof'
|
||||
]);
|
||||
|
||||
export const valid_identifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
122
node_modules/@sveltejs/kit/src/core/generate_manifest/index.js
generated
vendored
Normal file
122
node_modules/@sveltejs/kit/src/core/generate_manifest/index.js
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
import { s } from '../../utils/misc.js';
|
||||
import { get_mime_lookup } from '../utils.js';
|
||||
import { resolve_symlinks } from '../../exports/vite/build/utils.js';
|
||||
import { compact } from '../../utils/array.js';
|
||||
import { join_relative } from '../../utils/filesystem.js';
|
||||
import { dedent } from '../sync/utils.js';
|
||||
|
||||
/**
|
||||
* Generates the data used to write the server-side manifest.js file. This data is used in the Vite
|
||||
* build process, to power routing, etc.
|
||||
* @param {{
|
||||
* build_data: import('types').BuildData;
|
||||
* relative_path: string;
|
||||
* routes: import('types').RouteData[];
|
||||
* }} opts
|
||||
*/
|
||||
export function generate_manifest({ build_data, relative_path, routes }) {
|
||||
/**
|
||||
* @type {Map<any, number>} The new index of each node in the filtered nodes array
|
||||
*/
|
||||
const reindexed = new Map();
|
||||
/**
|
||||
* All nodes actually used in the routes definition (prerendered routes are omitted).
|
||||
* Root layout/error is always included as they are needed for 404 and root errors.
|
||||
* @type {Set<any>}
|
||||
*/
|
||||
const used_nodes = new Set([0, 1]);
|
||||
|
||||
for (const route of routes) {
|
||||
if (route.page) {
|
||||
for (const i of route.page.layouts) used_nodes.add(i);
|
||||
for (const i of route.page.errors) used_nodes.add(i);
|
||||
used_nodes.add(route.page.leaf);
|
||||
}
|
||||
}
|
||||
|
||||
const node_paths = compact(
|
||||
build_data.manifest_data.nodes.map((_, i) => {
|
||||
if (used_nodes.has(i)) {
|
||||
reindexed.set(i, reindexed.size);
|
||||
return join_relative(relative_path, `/nodes/${i}.js`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/** @type {(path: string) => string} */
|
||||
const loader = (path) => `__memo(() => import('${path}'))`;
|
||||
|
||||
const assets = build_data.manifest_data.assets.map((asset) => asset.file);
|
||||
if (build_data.service_worker) {
|
||||
assets.push(build_data.service_worker);
|
||||
}
|
||||
|
||||
const matchers = new Set();
|
||||
|
||||
/** @param {Array<number | undefined>} indexes */
|
||||
function get_nodes(indexes) {
|
||||
const string = indexes.map((n) => reindexed.get(n) ?? '').join(',');
|
||||
|
||||
// since JavaScript ignores trailing commas, we need to insert a dummy
|
||||
// comma so that the array has the correct length if the last item
|
||||
// is undefined
|
||||
return `[${string},]`;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
// String representation of
|
||||
/** @template {import('@sveltejs/kit').SSRManifest} T */
|
||||
const manifest_expr = dedent`
|
||||
{
|
||||
appDir: ${s(build_data.app_dir)},
|
||||
appPath: ${s(build_data.app_path)},
|
||||
assets: new Set(${s(assets)}),
|
||||
mimeTypes: ${s(get_mime_lookup(build_data.manifest_data))},
|
||||
_: {
|
||||
client: ${s(build_data.client)},
|
||||
nodes: [
|
||||
${(node_paths).map(loader).join(',\n')}
|
||||
],
|
||||
routes: [
|
||||
${routes.map(route => {
|
||||
if (!route.page && !route.endpoint) return;
|
||||
|
||||
route.params.forEach(param => {
|
||||
if (param.matcher) matchers.add(param.matcher);
|
||||
});
|
||||
|
||||
return dedent`
|
||||
{
|
||||
id: ${s(route.id)},
|
||||
pattern: ${route.pattern},
|
||||
params: ${s(route.params)},
|
||||
page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${reindexed.get(route.page.leaf)} }` : 'null'},
|
||||
endpoint: ${route.endpoint ? loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, route.endpoint.file).chunk.file)) : 'null'}
|
||||
}
|
||||
`;
|
||||
}).filter(Boolean).join(',\n')}
|
||||
],
|
||||
matchers: async () => {
|
||||
${Array.from(
|
||||
matchers,
|
||||
type => `const { match: ${type} } = await import ('${(join_relative(relative_path, `/entries/matchers/${type}.js`))}')`
|
||||
).join('\n')}
|
||||
return { ${Array.from(matchers).join(', ')} };
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Memoize the loaders to prevent Node from doing unnecessary work
|
||||
// on every dynamic import call
|
||||
return dedent`
|
||||
(() => {
|
||||
function __memo(fn) {
|
||||
let value;
|
||||
return () => value ??= (value = fn());
|
||||
}
|
||||
|
||||
return ${manifest_expr}
|
||||
})()
|
||||
`;
|
||||
}
|
174
node_modules/@sveltejs/kit/src/core/postbuild/analyse.js
generated
vendored
Normal file
174
node_modules/@sveltejs/kit/src/core/postbuild/analyse.js
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { get_option } from '../../utils/options.js';
|
||||
import {
|
||||
validate_layout_exports,
|
||||
validate_layout_server_exports,
|
||||
validate_page_exports,
|
||||
validate_page_server_exports,
|
||||
validate_server_exports
|
||||
} from '../../utils/exports.js';
|
||||
import { load_config } from '../config/index.js';
|
||||
import { forked } from '../../utils/fork.js';
|
||||
import { should_polyfill } from '../../utils/platform.js';
|
||||
import { installPolyfills } from '../../exports/node/polyfills.js';
|
||||
import { resolvePath } from '../../exports/index.js';
|
||||
import { ENDPOINT_METHODS } from '../../constants.js';
|
||||
import { filter_private_env, filter_public_env } from '../../utils/env.js';
|
||||
|
||||
export default forked(import.meta.url, analyse);
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* manifest_path: string;
|
||||
* env: Record<string, string>
|
||||
* }} opts
|
||||
*/
|
||||
async function analyse({ manifest_path, env }) {
|
||||
/** @type {import('@sveltejs/kit').SSRManifest} */
|
||||
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
|
||||
|
||||
/** @type {import('types').ValidatedKitConfig} */
|
||||
const config = (await load_config()).kit;
|
||||
|
||||
const server_root = join(config.outDir, 'output');
|
||||
|
||||
/** @type {import('types').ServerInternalModule} */
|
||||
const internal = await import(pathToFileURL(`${server_root}/server/internal.js`).href);
|
||||
|
||||
if (should_polyfill) {
|
||||
installPolyfills();
|
||||
}
|
||||
|
||||
// configure `import { building } from '$app/environment'` —
|
||||
// essential we do this before analysing the code
|
||||
internal.set_building(true);
|
||||
|
||||
// set env, in case it's used in initialisation
|
||||
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
|
||||
internal.set_private_env(filter_private_env(env, { public_prefix, private_prefix }));
|
||||
internal.set_public_env(filter_public_env(env, { public_prefix, private_prefix }));
|
||||
|
||||
/** @type {import('types').ServerMetadata} */
|
||||
const metadata = {
|
||||
nodes: [],
|
||||
routes: new Map()
|
||||
};
|
||||
|
||||
// analyse nodes
|
||||
for (const loader of manifest._.nodes) {
|
||||
const node = await loader();
|
||||
|
||||
metadata.nodes[node.index] = {
|
||||
has_server_load: node.server?.load !== undefined || node.server?.trailingSlash !== undefined
|
||||
};
|
||||
}
|
||||
|
||||
// analyse routes
|
||||
for (const route of manifest._.routes) {
|
||||
/** @type {Array<'GET' | 'POST'>} */
|
||||
const page_methods = [];
|
||||
|
||||
/** @type {(import('types').HttpMethod | '*')[]} */
|
||||
const api_methods = [];
|
||||
|
||||
/** @type {import('types').PrerenderOption | undefined} */
|
||||
let prerender = undefined;
|
||||
/** @type {any} */
|
||||
let config = undefined;
|
||||
/** @type {import('types').PrerenderEntryGenerator | undefined} */
|
||||
let entries = undefined;
|
||||
|
||||
if (route.endpoint) {
|
||||
const mod = await route.endpoint();
|
||||
if (mod.prerender !== undefined) {
|
||||
validate_server_exports(mod, route.id);
|
||||
|
||||
if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
|
||||
throw new Error(
|
||||
`Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
|
||||
);
|
||||
}
|
||||
|
||||
prerender = mod.prerender;
|
||||
}
|
||||
|
||||
Object.values(mod).forEach((/** @type {import('types').HttpMethod} */ method) => {
|
||||
if (mod[method] && ENDPOINT_METHODS.has(method)) {
|
||||
api_methods.push(method);
|
||||
} else if (mod.fallback) {
|
||||
api_methods.push('*');
|
||||
}
|
||||
});
|
||||
|
||||
config = mod.config;
|
||||
entries = mod.entries;
|
||||
}
|
||||
|
||||
if (route.page) {
|
||||
const nodes = await Promise.all(
|
||||
[...route.page.layouts, route.page.leaf].map((n) => {
|
||||
if (n !== undefined) return manifest._.nodes[n]();
|
||||
})
|
||||
);
|
||||
|
||||
const layouts = nodes.slice(0, -1);
|
||||
const page = nodes.at(-1);
|
||||
|
||||
for (const layout of layouts) {
|
||||
if (layout) {
|
||||
validate_layout_server_exports(layout.server, layout.server_id);
|
||||
validate_layout_exports(layout.universal, layout.universal_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (page) {
|
||||
page_methods.push('GET');
|
||||
if (page.server?.actions) page_methods.push('POST');
|
||||
|
||||
validate_page_server_exports(page.server, page.server_id);
|
||||
validate_page_exports(page.universal, page.universal_id);
|
||||
}
|
||||
|
||||
prerender = get_option(nodes, 'prerender') ?? false;
|
||||
|
||||
config = get_config(nodes);
|
||||
entries ??= get_option(nodes, 'entries');
|
||||
}
|
||||
|
||||
metadata.routes.set(route.id, {
|
||||
config,
|
||||
methods: Array.from(new Set([...page_methods, ...api_methods])),
|
||||
page: {
|
||||
methods: page_methods
|
||||
},
|
||||
api: {
|
||||
methods: api_methods
|
||||
},
|
||||
prerender,
|
||||
entries:
|
||||
entries && (await entries()).map((entry_object) => resolvePath(route.id, entry_object))
|
||||
});
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a shallow merge (first level) of the config object
|
||||
* @param {Array<import('types').SSRNode | undefined>} nodes
|
||||
*/
|
||||
function get_config(nodes) {
|
||||
let current = {};
|
||||
for (const node of nodes) {
|
||||
const config = node?.universal?.config ?? node?.server?.config;
|
||||
if (config) {
|
||||
current = {
|
||||
...current,
|
||||
...config
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(current).length ? current : undefined;
|
||||
}
|
243
node_modules/@sveltejs/kit/src/core/postbuild/crawl.js
generated
vendored
Normal file
243
node_modules/@sveltejs/kit/src/core/postbuild/crawl.js
generated
vendored
Normal file
@ -0,0 +1,243 @@
|
||||
import { resolve } from '../../utils/url.js';
|
||||
import { decode } from './entities.js';
|
||||
|
||||
const DOCTYPE = 'DOCTYPE';
|
||||
const CDATA_OPEN = '[CDATA[';
|
||||
const CDATA_CLOSE = ']]>';
|
||||
const COMMENT_OPEN = '--';
|
||||
const COMMENT_CLOSE = '-->';
|
||||
|
||||
const TAG_OPEN = /[a-zA-Z]/;
|
||||
const TAG_CHAR = /[a-zA-Z0-9]/;
|
||||
const ATTRIBUTE_NAME = /[^\t\n\f />"'=]/;
|
||||
|
||||
const WHITESPACE = /[\s\n\r]/;
|
||||
|
||||
const CRAWLABLE_META_NAME_ATTRS = new Set([
|
||||
'og:url',
|
||||
'og:image',
|
||||
'og:image:url',
|
||||
'og:image:secure_url',
|
||||
'og:video',
|
||||
'og:video:url',
|
||||
'og:video:secure_url',
|
||||
'og:audio',
|
||||
'og:audio:url',
|
||||
'og:audio:secure_url',
|
||||
'twitter:image'
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @param {string} base
|
||||
*/
|
||||
export function crawl(html, base) {
|
||||
/** @type {string[]} */
|
||||
const ids = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
const hrefs = [];
|
||||
|
||||
let i = 0;
|
||||
main: while (i < html.length) {
|
||||
const char = html[i];
|
||||
|
||||
if (char === '<') {
|
||||
if (html[i + 1] === '!') {
|
||||
i += 2;
|
||||
|
||||
if (html.slice(i, i + DOCTYPE.length).toUpperCase() === DOCTYPE) {
|
||||
i += DOCTYPE.length;
|
||||
while (i < html.length) {
|
||||
if (html[i++] === '>') {
|
||||
continue main;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip cdata
|
||||
if (html.slice(i, i + CDATA_OPEN.length) === CDATA_OPEN) {
|
||||
i += CDATA_OPEN.length;
|
||||
while (i < html.length) {
|
||||
if (html.slice(i, i + CDATA_CLOSE.length) === CDATA_CLOSE) {
|
||||
i += CDATA_CLOSE.length;
|
||||
continue main;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// skip comments
|
||||
if (html.slice(i, i + COMMENT_OPEN.length) === COMMENT_OPEN) {
|
||||
i += COMMENT_OPEN.length;
|
||||
while (i < html.length) {
|
||||
if (html.slice(i, i + COMMENT_CLOSE.length) === COMMENT_CLOSE) {
|
||||
i += COMMENT_CLOSE.length;
|
||||
continue main;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse opening tags
|
||||
const start = ++i;
|
||||
if (TAG_OPEN.test(html[start])) {
|
||||
while (i < html.length) {
|
||||
if (!TAG_CHAR.test(html[i])) {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
const tag = html.slice(start, i).toUpperCase();
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const attributes = {};
|
||||
|
||||
if (tag === 'SCRIPT' || tag === 'STYLE') {
|
||||
while (i < html.length) {
|
||||
if (
|
||||
html[i] === '<' &&
|
||||
html[i + 1] === '/' &&
|
||||
html.slice(i + 2, i + 2 + tag.length).toUpperCase() === tag
|
||||
) {
|
||||
continue main;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < html.length) {
|
||||
const start = i;
|
||||
|
||||
const char = html[start];
|
||||
if (char === '>') break;
|
||||
|
||||
if (ATTRIBUTE_NAME.test(char)) {
|
||||
i += 1;
|
||||
|
||||
while (i < html.length) {
|
||||
if (!ATTRIBUTE_NAME.test(html[i])) {
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
const name = html.slice(start, i).toLowerCase();
|
||||
|
||||
while (WHITESPACE.test(html[i])) i += 1;
|
||||
|
||||
if (html[i] === '=') {
|
||||
i += 1;
|
||||
while (WHITESPACE.test(html[i])) i += 1;
|
||||
|
||||
let value;
|
||||
|
||||
if (html[i] === "'" || html[i] === '"') {
|
||||
const quote = html[i++];
|
||||
|
||||
const start = i;
|
||||
let escaped = false;
|
||||
|
||||
while (i < html.length) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else {
|
||||
const char = html[i];
|
||||
|
||||
if (html[i] === quote) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (char === '\\') {
|
||||
escaped = true;
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
value = html.slice(start, i);
|
||||
} else {
|
||||
const start = i;
|
||||
while (html[i] !== '>' && !WHITESPACE.test(html[i])) i += 1;
|
||||
value = html.slice(start, i);
|
||||
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
value = decode(value);
|
||||
attributes[name] = value;
|
||||
} else {
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
const { href, id, name, property, rel, src, srcset, content } = attributes;
|
||||
|
||||
if (href) {
|
||||
if (tag === 'BASE') {
|
||||
base = resolve(base, href);
|
||||
} else if (!rel || !/\bexternal\b/i.test(rel)) {
|
||||
hrefs.push(resolve(base, href));
|
||||
}
|
||||
}
|
||||
|
||||
if (id) {
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
if (name && tag === 'A') {
|
||||
ids.push(name);
|
||||
}
|
||||
|
||||
if (src) {
|
||||
hrefs.push(resolve(base, src));
|
||||
}
|
||||
|
||||
if (srcset) {
|
||||
let value = srcset;
|
||||
const candidates = [];
|
||||
let insideURL = true;
|
||||
value = value.trim();
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (value[i] === ',' && (!insideURL || (insideURL && WHITESPACE.test(value[i + 1])))) {
|
||||
candidates.push(value.slice(0, i));
|
||||
value = value.substring(i + 1).trim();
|
||||
i = 0;
|
||||
insideURL = true;
|
||||
} else if (WHITESPACE.test(value[i])) {
|
||||
insideURL = false;
|
||||
}
|
||||
}
|
||||
candidates.push(value);
|
||||
for (const candidate of candidates) {
|
||||
const src = candidate.split(WHITESPACE)[0];
|
||||
if (src) hrefs.push(resolve(base, src));
|
||||
}
|
||||
}
|
||||
|
||||
if (tag === 'META' && content) {
|
||||
const attr = name ?? property;
|
||||
|
||||
if (attr && CRAWLABLE_META_NAME_ATTRS.has(attr)) {
|
||||
hrefs.push(resolve(base, content));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return { ids, hrefs };
|
||||
}
|
2252
node_modules/@sveltejs/kit/src/core/postbuild/entities.js
generated
vendored
Normal file
2252
node_modules/@sveltejs/kit/src/core/postbuild/entities.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
54
node_modules/@sveltejs/kit/src/core/postbuild/fallback.js
generated
vendored
Normal file
54
node_modules/@sveltejs/kit/src/core/postbuild/fallback.js
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { installPolyfills } from '../../exports/node/polyfills.js';
|
||||
import { load_config } from '../config/index.js';
|
||||
import { forked } from '../../utils/fork.js';
|
||||
|
||||
export default forked(import.meta.url, generate_fallback);
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* manifest_path: string;
|
||||
* env: Record<string, string>
|
||||
* }} opts
|
||||
*/
|
||||
async function generate_fallback({ manifest_path, env }) {
|
||||
/** @type {import('types').ValidatedKitConfig} */
|
||||
const config = (await load_config()).kit;
|
||||
|
||||
installPolyfills();
|
||||
|
||||
const server_root = join(config.outDir, 'output');
|
||||
|
||||
/** @type {import('types').ServerInternalModule} */
|
||||
const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href);
|
||||
|
||||
/** @type {import('types').ServerModule} */
|
||||
const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
|
||||
|
||||
/** @type {import('@sveltejs/kit').SSRManifest} */
|
||||
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
|
||||
|
||||
set_building(true);
|
||||
|
||||
const server = new Server(manifest);
|
||||
await server.init({ env });
|
||||
|
||||
const response = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
|
||||
getClientAddress: () => {
|
||||
throw new Error('Cannot read clientAddress during prerendering');
|
||||
},
|
||||
prerendering: {
|
||||
fallback: true,
|
||||
dependencies: new Map()
|
||||
},
|
||||
read: (file) => readFileSync(join(config.files.assets, file))
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
throw new Error(`Could not create a fallback page — failed with status ${response.status}`);
|
||||
}
|
483
node_modules/@sveltejs/kit/src/core/postbuild/prerender.js
generated
vendored
Normal file
483
node_modules/@sveltejs/kit/src/core/postbuild/prerender.js
generated
vendored
Normal file
@ -0,0 +1,483 @@
|
||||
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { installPolyfills } from '../../exports/node/polyfills.js';
|
||||
import { mkdirp, posixify, walk } from '../../utils/filesystem.js';
|
||||
import { should_polyfill } from '../../utils/platform.js';
|
||||
import { decode_uri, is_root_relative, resolve } from '../../utils/url.js';
|
||||
import { escape_html_attr } from '../../utils/escape.js';
|
||||
import { logger } from '../utils.js';
|
||||
import { load_config } from '../config/index.js';
|
||||
import { get_route_segments } from '../../utils/routing.js';
|
||||
import { queue } from './queue.js';
|
||||
import { crawl } from './crawl.js';
|
||||
import { forked } from '../../utils/fork.js';
|
||||
import * as devalue from 'devalue';
|
||||
|
||||
export default forked(import.meta.url, prerender);
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* out: string;
|
||||
* manifest_path: string;
|
||||
* metadata: import('types').ServerMetadata;
|
||||
* verbose: boolean;
|
||||
* env: Record<string, string>
|
||||
* }} opts
|
||||
*/
|
||||
async function prerender({ out, manifest_path, metadata, verbose, env }) {
|
||||
/** @type {import('@sveltejs/kit').SSRManifest} */
|
||||
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
|
||||
|
||||
/** @type {import('types').ServerInternalModule} */
|
||||
const internal = await import(pathToFileURL(`${out}/server/internal.js`).href);
|
||||
|
||||
/** @type {import('types').ServerModule} */
|
||||
const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
|
||||
|
||||
// configure `import { building } from '$app/environment'` —
|
||||
// essential we do this before analysing the code
|
||||
internal.set_building(true);
|
||||
|
||||
/**
|
||||
* @template {{message: string}} T
|
||||
* @template {Omit<T, 'message'>} K
|
||||
* @param {import('types').Logger} log
|
||||
* @param {'fail' | 'warn' | 'ignore' | ((details: T) => void)} input
|
||||
* @param {(details: K) => string} format
|
||||
* @returns {(details: K) => void}
|
||||
*/
|
||||
function normalise_error_handler(log, input, format) {
|
||||
switch (input) {
|
||||
case 'fail':
|
||||
return (details) => {
|
||||
throw new Error(format(details));
|
||||
};
|
||||
case 'warn':
|
||||
return (details) => {
|
||||
log.error(format(details));
|
||||
};
|
||||
case 'ignore':
|
||||
return () => {};
|
||||
default:
|
||||
// @ts-expect-error TS thinks T might be of a different kind, but it's not
|
||||
return (details) => input({ ...details, message: format(details) });
|
||||
}
|
||||
}
|
||||
|
||||
const OK = 2;
|
||||
const REDIRECT = 3;
|
||||
|
||||
/** @type {import('types').Prerendered} */
|
||||
const prerendered = {
|
||||
pages: new Map(),
|
||||
assets: new Map(),
|
||||
redirects: new Map(),
|
||||
paths: []
|
||||
};
|
||||
|
||||
/** @type {import('types').PrerenderMap} */
|
||||
const prerender_map = new Map();
|
||||
|
||||
for (const [id, { prerender }] of metadata.routes) {
|
||||
if (prerender !== undefined) {
|
||||
prerender_map.set(id, prerender);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const prerendered_routes = new Set();
|
||||
|
||||
/** @type {import('types').ValidatedKitConfig} */
|
||||
const config = (await load_config()).kit;
|
||||
|
||||
/** @type {import('types').Logger} */
|
||||
const log = logger({ verbose });
|
||||
|
||||
if (should_polyfill) {
|
||||
installPolyfills();
|
||||
}
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
const saved = new Map();
|
||||
|
||||
const server = new Server(manifest);
|
||||
await server.init({ env });
|
||||
|
||||
const handle_http_error = normalise_error_handler(
|
||||
log,
|
||||
config.prerender.handleHttpError,
|
||||
({ status, path, referrer, referenceType }) => {
|
||||
const message =
|
||||
status === 404 && !path.startsWith(config.paths.base)
|
||||
? `${path} does not begin with \`base\`, which is configured in \`paths.base\` and can be imported from \`$app/paths\` - see https://kit.svelte.dev/docs/configuration#paths for more info`
|
||||
: path;
|
||||
|
||||
return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
|
||||
}
|
||||
);
|
||||
|
||||
const handle_missing_id = normalise_error_handler(
|
||||
log,
|
||||
config.prerender.handleMissingId,
|
||||
({ path, id, referrers }) => {
|
||||
return (
|
||||
`The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path} - see the \`handleMissingId\` option in https://kit.svelte.dev/docs/configuration#prerender for more info:` +
|
||||
referrers.map((l) => `\n - ${l}`).join('')
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const handle_entry_generator_mismatch = normalise_error_handler(
|
||||
log,
|
||||
config.prerender.handleEntryGeneratorMismatch,
|
||||
({ generatedFromId, entry, matchedId }) => {
|
||||
return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId} - see the \`handleEntryGeneratorMismatch\` option in https://kit.svelte.dev/docs/configuration#prerender for more info.`;
|
||||
}
|
||||
);
|
||||
|
||||
const q = queue(config.prerender.concurrency);
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {boolean} is_html
|
||||
*/
|
||||
function output_filename(path, is_html) {
|
||||
const file = path.slice(config.paths.base.length + 1) || 'index.html';
|
||||
|
||||
if (is_html && !file.endsWith('.html')) {
|
||||
return file + (file.endsWith('/') ? 'index.html' : '.html');
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
const files = new Set(walk(`${out}/client`).map(posixify));
|
||||
|
||||
const immutable = `${config.appDir}/immutable`;
|
||||
if (existsSync(`${out}/server/${immutable}`)) {
|
||||
for (const file of walk(`${out}/server/${immutable}`)) {
|
||||
files.add(posixify(`${config.appDir}/immutable/${file}`));
|
||||
}
|
||||
}
|
||||
const seen = new Set();
|
||||
const written = new Set();
|
||||
|
||||
/** @type {Map<string, Set<string>>} */
|
||||
const expected_hashlinks = new Map();
|
||||
|
||||
/** @type {Map<string, string[]>} */
|
||||
const actual_hashlinks = new Map();
|
||||
|
||||
/**
|
||||
* @param {string | null} referrer
|
||||
* @param {string} decoded
|
||||
* @param {string} [encoded]
|
||||
* @param {string} [generated_from_id]
|
||||
*/
|
||||
function enqueue(referrer, decoded, encoded, generated_from_id) {
|
||||
if (seen.has(decoded)) return;
|
||||
seen.add(decoded);
|
||||
|
||||
const file = decoded.slice(config.paths.base.length + 1);
|
||||
if (files.has(file)) return;
|
||||
|
||||
return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer, generated_from_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} decoded
|
||||
* @param {string} encoded
|
||||
* @param {string?} referrer
|
||||
* @param {string} [generated_from_id]
|
||||
*/
|
||||
async function visit(decoded, encoded, referrer, generated_from_id) {
|
||||
if (!decoded.startsWith(config.paths.base)) {
|
||||
handle_http_error({ status: 404, path: decoded, referrer, referenceType: 'linked' });
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {Map<string, import('types').PrerenderDependency>} */
|
||||
const dependencies = new Map();
|
||||
|
||||
const response = await server.respond(new Request(config.prerender.origin + encoded), {
|
||||
getClientAddress() {
|
||||
throw new Error('Cannot read clientAddress during prerendering');
|
||||
},
|
||||
prerendering: {
|
||||
dependencies
|
||||
},
|
||||
read: (file) => {
|
||||
// stuff we just wrote
|
||||
const filepath = saved.get(file);
|
||||
if (filepath) return readFileSync(filepath);
|
||||
|
||||
// stuff in `static`
|
||||
return readFileSync(join(config.files.assets, file));
|
||||
}
|
||||
});
|
||||
|
||||
const encoded_id = response.headers.get('x-sveltekit-routeid');
|
||||
const decoded_id = encoded_id && decode_uri(encoded_id);
|
||||
if (
|
||||
decoded_id !== null &&
|
||||
generated_from_id !== undefined &&
|
||||
decoded_id !== generated_from_id
|
||||
) {
|
||||
handle_entry_generator_mismatch({
|
||||
generatedFromId: generated_from_id,
|
||||
entry: decoded,
|
||||
matchedId: decoded_id
|
||||
});
|
||||
}
|
||||
|
||||
const body = Buffer.from(await response.arrayBuffer());
|
||||
|
||||
save('pages', response, body, decoded, encoded, referrer, 'linked');
|
||||
|
||||
for (const [dependency_path, result] of dependencies) {
|
||||
// this seems circuitous, but using new URL allows us to not care
|
||||
// whether dependency_path is encoded or not
|
||||
const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
|
||||
const decoded_dependency_path = decode_uri(encoded_dependency_path);
|
||||
|
||||
const headers = Object.fromEntries(result.response.headers);
|
||||
|
||||
const prerender = headers['x-sveltekit-prerender'];
|
||||
if (prerender) {
|
||||
const encoded_route_id = headers['x-sveltekit-routeid'];
|
||||
if (encoded_route_id != null) {
|
||||
const route_id = decode_uri(encoded_route_id);
|
||||
const existing_value = prerender_map.get(route_id);
|
||||
if (existing_value !== 'auto') {
|
||||
prerender_map.set(route_id, prerender === 'true' ? true : 'auto');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
|
||||
|
||||
save(
|
||||
'dependencies',
|
||||
result.response,
|
||||
body,
|
||||
decoded_dependency_path,
|
||||
encoded_dependency_path,
|
||||
decoded,
|
||||
'fetched'
|
||||
);
|
||||
}
|
||||
|
||||
// avoid triggering `filterSerializeResponseHeaders` guard
|
||||
const headers = Object.fromEntries(response.headers);
|
||||
|
||||
if (config.prerender.crawl && headers['content-type'] === 'text/html') {
|
||||
const { ids, hrefs } = crawl(body.toString(), decoded);
|
||||
|
||||
actual_hashlinks.set(decoded, ids);
|
||||
|
||||
for (const href of hrefs) {
|
||||
if (!is_root_relative(href)) continue;
|
||||
|
||||
const { pathname, search, hash } = new URL(href, 'http://localhost');
|
||||
|
||||
if (search) {
|
||||
// TODO warn that query strings have no effect on statically-exported pages
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
const key = decode_uri(pathname + hash);
|
||||
|
||||
if (!expected_hashlinks.has(key)) {
|
||||
expected_hashlinks.set(key, new Set());
|
||||
}
|
||||
|
||||
/** @type {Set<string>} */ (expected_hashlinks.get(key)).add(decoded);
|
||||
}
|
||||
|
||||
enqueue(decoded, decode_uri(pathname), pathname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {'pages' | 'dependencies'} category
|
||||
* @param {Response} response
|
||||
* @param {string | Uint8Array} body
|
||||
* @param {string} decoded
|
||||
* @param {string} encoded
|
||||
* @param {string | null} referrer
|
||||
* @param {'linked' | 'fetched'} referenceType
|
||||
*/
|
||||
function save(category, response, body, decoded, encoded, referrer, referenceType) {
|
||||
const response_type = Math.floor(response.status / 100);
|
||||
const headers = Object.fromEntries(response.headers);
|
||||
|
||||
const type = headers['content-type'];
|
||||
const is_html = response_type === REDIRECT || type === 'text/html';
|
||||
|
||||
const file = output_filename(decoded, is_html);
|
||||
const dest = `${config.outDir}/output/prerendered/${category}/${file}`;
|
||||
|
||||
if (written.has(file)) return;
|
||||
|
||||
const encoded_route_id = response.headers.get('x-sveltekit-routeid');
|
||||
const route_id = encoded_route_id != null ? decode_uri(encoded_route_id) : null;
|
||||
if (route_id !== null) prerendered_routes.add(route_id);
|
||||
|
||||
if (response_type === REDIRECT) {
|
||||
const location = headers['location'];
|
||||
|
||||
if (location) {
|
||||
const resolved = resolve(encoded, location);
|
||||
if (is_root_relative(resolved)) {
|
||||
enqueue(decoded, decode_uri(resolved), resolved);
|
||||
}
|
||||
|
||||
if (!headers['x-sveltekit-normalize']) {
|
||||
mkdirp(dirname(dest));
|
||||
|
||||
log.warn(`${response.status} ${decoded} -> ${location}`);
|
||||
|
||||
writeFileSync(
|
||||
dest,
|
||||
`<script>location.href=${devalue.uneval(
|
||||
location
|
||||
)};</script><meta http-equiv="refresh" content=${escape_html_attr(
|
||||
`0;url=${location}`
|
||||
)}>`
|
||||
);
|
||||
|
||||
written.add(file);
|
||||
|
||||
if (!prerendered.redirects.has(decoded)) {
|
||||
prerendered.redirects.set(decoded, {
|
||||
status: response.status,
|
||||
location: resolved
|
||||
});
|
||||
|
||||
prerendered.paths.push(decoded);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn(`location header missing on redirect received from ${decoded}`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
if (existsSync(dest) && statSync(dest).isDirectory()) {
|
||||
throw new Error(
|
||||
`Cannot save ${decoded} as it is already a directory. See https://kit.svelte.dev/docs/page-options#prerender-route-conflicts for more information`
|
||||
);
|
||||
}
|
||||
|
||||
const dir = dirname(dest);
|
||||
|
||||
if (existsSync(dir) && !statSync(dir).isDirectory()) {
|
||||
const parent = decoded.split('/').slice(0, -1).join('/');
|
||||
throw new Error(
|
||||
`Cannot save ${decoded} as ${parent} is already a file. See https://kit.svelte.dev/docs/page-options#prerender-route-conflicts for more information`
|
||||
);
|
||||
}
|
||||
|
||||
mkdirp(dir);
|
||||
|
||||
log.info(`${response.status} ${decoded}`);
|
||||
writeFileSync(dest, body);
|
||||
written.add(file);
|
||||
|
||||
if (is_html) {
|
||||
prerendered.pages.set(decoded, {
|
||||
file
|
||||
});
|
||||
} else {
|
||||
prerendered.assets.set(decoded, {
|
||||
type
|
||||
});
|
||||
}
|
||||
|
||||
prerendered.paths.push(decoded);
|
||||
} else if (response_type !== OK) {
|
||||
handle_http_error({ status: response.status, path: decoded, referrer, referenceType });
|
||||
}
|
||||
|
||||
manifest.assets.add(file);
|
||||
saved.set(file, dest);
|
||||
}
|
||||
|
||||
/** @type {Array<{ id: string, entries: Array<string>}>} */
|
||||
const route_level_entries = [];
|
||||
for (const [id, { entries }] of metadata.routes.entries()) {
|
||||
if (entries) {
|
||||
route_level_entries.push({ id, entries });
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
config.prerender.entries.length > 1 ||
|
||||
config.prerender.entries[0] !== '*' ||
|
||||
route_level_entries.length > 0 ||
|
||||
prerender_map.size > 0
|
||||
) {
|
||||
// Only log if we're actually going to do something to not confuse users
|
||||
log.info('Prerendering');
|
||||
}
|
||||
|
||||
for (const entry of config.prerender.entries) {
|
||||
if (entry === '*') {
|
||||
for (const [id, prerender] of prerender_map) {
|
||||
if (prerender) {
|
||||
if (id.includes('[')) continue;
|
||||
const path = `/${get_route_segments(id).join('/')}`;
|
||||
enqueue(null, config.paths.base + path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enqueue(null, config.paths.base + entry);
|
||||
}
|
||||
}
|
||||
|
||||
for (const { id, entries } of route_level_entries) {
|
||||
for (const entry of entries) {
|
||||
enqueue(null, config.paths.base + entry, undefined, id);
|
||||
}
|
||||
}
|
||||
|
||||
await q.done();
|
||||
|
||||
// handle invalid fragment links
|
||||
for (const [key, referrers] of expected_hashlinks) {
|
||||
const index = key.indexOf('#');
|
||||
const path = key.slice(0, index);
|
||||
const id = key.slice(index + 1);
|
||||
|
||||
const hashlinks = actual_hashlinks.get(path);
|
||||
// ignore fragment links to pages that were not prerendered
|
||||
if (!hashlinks) continue;
|
||||
|
||||
if (!hashlinks.includes(id)) {
|
||||
handle_missing_id({ id, path, referrers: Array.from(referrers) });
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
const not_prerendered = [];
|
||||
|
||||
for (const [route_id, prerender] of prerender_map) {
|
||||
if (prerender === true && !prerendered_routes.has(route_id)) {
|
||||
not_prerendered.push(route_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (not_prerendered.length > 0) {
|
||||
throw new Error(
|
||||
`The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${not_prerendered.map(
|
||||
(id) => ` - ${id}`
|
||||
)}\n\nSee https://kit.svelte.dev/docs/page-options#prerender-troubleshooting for info on how to solve this`
|
||||
);
|
||||
}
|
||||
|
||||
return { prerendered, prerender_map };
|
||||
}
|
80
node_modules/@sveltejs/kit/src/core/postbuild/queue.js
generated
vendored
Normal file
80
node_modules/@sveltejs/kit/src/core/postbuild/queue.js
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @typedef {{
|
||||
* fn: () => Promise<any>,
|
||||
* fulfil: (value: any) => void,
|
||||
* reject: (error: Error) => void
|
||||
* }} Task
|
||||
*/
|
||||
|
||||
/** @param {number} concurrency */
|
||||
export function queue(concurrency) {
|
||||
/** @type {Task[]} */
|
||||
const tasks = [];
|
||||
|
||||
let current = 0;
|
||||
|
||||
/** @type {(value?: any) => void} */
|
||||
let fulfil;
|
||||
|
||||
/** @type {(error: Error) => void} */
|
||||
let reject;
|
||||
|
||||
let closed = false;
|
||||
|
||||
const done = new Promise((f, r) => {
|
||||
fulfil = f;
|
||||
reject = r;
|
||||
});
|
||||
|
||||
done.catch(() => {
|
||||
// this is necessary in case a catch handler is never added
|
||||
// to the done promise by the user
|
||||
});
|
||||
|
||||
function dequeue() {
|
||||
if (current < concurrency) {
|
||||
const task = tasks.shift();
|
||||
|
||||
if (task) {
|
||||
current += 1;
|
||||
const promise = Promise.resolve(task.fn());
|
||||
|
||||
promise
|
||||
.then(task.fulfil, (err) => {
|
||||
task.reject(err);
|
||||
reject(err);
|
||||
})
|
||||
.then(() => {
|
||||
current -= 1;
|
||||
dequeue();
|
||||
});
|
||||
} else if (current === 0) {
|
||||
closed = true;
|
||||
fulfil();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/** @param {() => any} fn */
|
||||
add: (fn) => {
|
||||
if (closed) throw new Error('Cannot add tasks to a queue that has ended');
|
||||
|
||||
const promise = new Promise((fulfil, reject) => {
|
||||
tasks.push({ fn, fulfil, reject });
|
||||
});
|
||||
|
||||
dequeue();
|
||||
return promise;
|
||||
},
|
||||
|
||||
done: () => {
|
||||
if (current === 0) {
|
||||
closed = true;
|
||||
fulfil();
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
};
|
||||
}
|
0
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/conflict.js
generated
vendored
Normal file
0
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/conflict.js
generated
vendored
Normal file
570
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js
generated
vendored
Normal file
570
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js
generated
vendored
Normal file
@ -0,0 +1,570 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import colors from 'kleur';
|
||||
import { lookup } from 'mrmime';
|
||||
import { list_files, runtime_directory } from '../../utils.js';
|
||||
import { posixify } from '../../../utils/filesystem.js';
|
||||
import { parse_route_id } from '../../../utils/routing.js';
|
||||
import { sort_routes } from './sort.js';
|
||||
|
||||
/**
|
||||
* Generates the manifest data used for the client-side manifest and types generation.
|
||||
* @param {{
|
||||
* config: import('types').ValidatedConfig;
|
||||
* fallback?: string;
|
||||
* cwd?: string;
|
||||
* }} opts
|
||||
* @returns {import('types').ManifestData}
|
||||
*/
|
||||
export default function create_manifest_data({
|
||||
config,
|
||||
fallback = `${runtime_directory}/components`,
|
||||
cwd = process.cwd()
|
||||
}) {
|
||||
const assets = create_assets(config);
|
||||
const matchers = create_matchers(config, cwd);
|
||||
const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);
|
||||
|
||||
for (const route of routes) {
|
||||
for (const param of route.params) {
|
||||
if (param.matcher && !matchers[param.matcher]) {
|
||||
throw new Error(`No matcher found for parameter '${param.matcher}' in route ${route.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
assets,
|
||||
matchers,
|
||||
nodes,
|
||||
routes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
*/
|
||||
export function create_assets(config) {
|
||||
return list_files(config.kit.files.assets).map((file) => ({
|
||||
file,
|
||||
size: fs.statSync(path.resolve(config.kit.files.assets, file)).size,
|
||||
type: lookup(file) || null
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {string} cwd
|
||||
*/
|
||||
function create_matchers(config, cwd) {
|
||||
const params_base = path.relative(cwd, config.kit.files.params);
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const matchers = {};
|
||||
if (fs.existsSync(config.kit.files.params)) {
|
||||
for (const file of fs.readdirSync(config.kit.files.params)) {
|
||||
const ext = path.extname(file);
|
||||
if (!config.kit.moduleExtensions.includes(ext)) continue;
|
||||
const type = file.slice(0, -ext.length);
|
||||
|
||||
if (/^\w+$/.test(type)) {
|
||||
const matcher_file = path.join(params_base, file);
|
||||
|
||||
// Disallow same matcher with different extensions
|
||||
if (matchers[type]) {
|
||||
throw new Error(`Duplicate matchers: ${matcher_file} and ${matchers[type]}`);
|
||||
} else {
|
||||
matchers[type] = matcher_file;
|
||||
}
|
||||
} else {
|
||||
// Allow for matcher test collocation
|
||||
if (type.endsWith('.test') || type.endsWith('.spec')) continue;
|
||||
|
||||
throw new Error(
|
||||
`Matcher names can only have underscores and alphanumeric characters — "${file}" is invalid`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {string} cwd
|
||||
* @param {string} fallback
|
||||
*/
|
||||
function create_routes_and_nodes(cwd, config, fallback) {
|
||||
/** @type {import('types').RouteData[]} */
|
||||
const routes = [];
|
||||
|
||||
const routes_base = posixify(path.relative(cwd, config.kit.files.routes));
|
||||
|
||||
const valid_extensions = [...config.extensions, ...config.kit.moduleExtensions];
|
||||
|
||||
/** @type {import('types').PageNode[]} */
|
||||
const nodes = [];
|
||||
|
||||
if (fs.existsSync(config.kit.files.routes)) {
|
||||
/**
|
||||
* @param {number} depth
|
||||
* @param {string} id
|
||||
* @param {string} segment
|
||||
* @param {import('types').RouteData | null} parent
|
||||
*/
|
||||
const walk = (depth, id, segment, parent) => {
|
||||
const unescaped = id.replace(/\[([ux])\+([^\]]+)\]/gi, (match, type, code) => {
|
||||
if (match !== match.toLowerCase()) {
|
||||
throw new Error(`Character escape sequence in ${id} must be lowercase`);
|
||||
}
|
||||
|
||||
if (!/[0-9a-f]+/.test(code)) {
|
||||
throw new Error(`Invalid character escape sequence in ${id}`);
|
||||
}
|
||||
|
||||
if (type === 'x') {
|
||||
if (code.length !== 2) {
|
||||
throw new Error(`Hexadecimal escape sequence in ${id} must be two characters`);
|
||||
}
|
||||
|
||||
return String.fromCharCode(parseInt(code, 16));
|
||||
} else {
|
||||
if (code.length < 4 || code.length > 6) {
|
||||
throw new Error(
|
||||
`Unicode escape sequence in ${id} must be between four and six characters`
|
||||
);
|
||||
}
|
||||
|
||||
return String.fromCharCode(parseInt(code, 16));
|
||||
}
|
||||
});
|
||||
|
||||
if (/\]\[/.test(unescaped)) {
|
||||
throw new Error(`Invalid route ${id} — parameters must be separated`);
|
||||
}
|
||||
|
||||
if (count_occurrences('[', id) !== count_occurrences(']', id)) {
|
||||
throw new Error(`Invalid route ${id} — brackets are unbalanced`);
|
||||
}
|
||||
|
||||
if (/#/.test(segment)) {
|
||||
// Vite will barf on files with # in them
|
||||
throw new Error(`Route ${id} should be renamed to ${id.replace(/#/g, '[x+23]')}`);
|
||||
}
|
||||
|
||||
if (/\[\.\.\.\w+\]\/\[\[/.test(id)) {
|
||||
throw new Error(
|
||||
`Invalid route ${id} — an [[optional]] route segment cannot follow a [...rest] route segment`
|
||||
);
|
||||
}
|
||||
|
||||
if (/\[\[\.\.\./.test(id)) {
|
||||
throw new Error(
|
||||
`Invalid route ${id} — a rest route segment is always optional, remove the outer square brackets`
|
||||
);
|
||||
}
|
||||
|
||||
const { pattern, params } = parse_route_id(id);
|
||||
|
||||
/** @type {import('types').RouteData} */
|
||||
const route = {
|
||||
id,
|
||||
parent,
|
||||
|
||||
segment,
|
||||
pattern,
|
||||
params,
|
||||
|
||||
layout: null,
|
||||
error: null,
|
||||
leaf: null,
|
||||
page: null,
|
||||
endpoint: null
|
||||
};
|
||||
|
||||
// important to do this before walking children, so that child
|
||||
// routes appear later
|
||||
routes.push(route);
|
||||
|
||||
// if we don't do this, the route map becomes unwieldy to console.log
|
||||
Object.defineProperty(route, 'parent', { enumerable: false });
|
||||
|
||||
const dir = path.join(cwd, routes_base, id);
|
||||
|
||||
// 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.is_dir) continue;
|
||||
|
||||
const ext = valid_extensions.find((ext) => file.name.endsWith(ext));
|
||||
if (!ext) continue;
|
||||
|
||||
if (!file.name.startsWith('+')) {
|
||||
const name = file.name.slice(0, -ext.length);
|
||||
// check if it is a valid route filename but missing the + prefix
|
||||
const typo =
|
||||
/^(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/.test(name) ||
|
||||
/^(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/.test(
|
||||
name
|
||||
);
|
||||
if (typo) {
|
||||
console.log(
|
||||
colors
|
||||
.bold()
|
||||
.yellow(
|
||||
`Missing route file prefix. Did you mean +${file.name}?` +
|
||||
` at ${path.join(dir, file.name)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file.name.endsWith('.d.ts')) {
|
||||
let name = file.name.slice(0, -5);
|
||||
const ext = valid_extensions.find((ext) => name.endsWith(ext));
|
||||
if (ext) name = name.slice(0, -ext.length);
|
||||
|
||||
const valid =
|
||||
/^\+(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/.test(name) ||
|
||||
/^\+(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/.test(
|
||||
name
|
||||
);
|
||||
|
||||
if (valid) continue;
|
||||
}
|
||||
|
||||
const project_relative = posixify(path.relative(cwd, path.join(dir, file.name)));
|
||||
|
||||
const item = analyze(
|
||||
project_relative,
|
||||
file.name,
|
||||
config.extensions,
|
||||
config.kit.moduleExtensions
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {string} existing_file
|
||||
*/
|
||||
function duplicate_files_error(type, existing_file) {
|
||||
return new Error(
|
||||
`Multiple ${type} files found in ${routes_base}${route.id} : ${path.basename(
|
||||
existing_file
|
||||
)} and ${file.name}`
|
||||
);
|
||||
}
|
||||
|
||||
if (item.kind === 'component') {
|
||||
if (item.is_error) {
|
||||
route.error = {
|
||||
depth,
|
||||
component: project_relative
|
||||
};
|
||||
} else if (item.is_layout) {
|
||||
if (!route.layout) {
|
||||
route.layout = { depth, child_pages: [] };
|
||||
} else if (route.layout.component) {
|
||||
throw duplicate_files_error('layout component', route.layout.component);
|
||||
}
|
||||
|
||||
route.layout.component = project_relative;
|
||||
if (item.uses_layout !== undefined) route.layout.parent_id = item.uses_layout;
|
||||
} else {
|
||||
if (!route.leaf) {
|
||||
route.leaf = { depth };
|
||||
} else if (route.leaf.component) {
|
||||
throw duplicate_files_error('page component', route.leaf.component);
|
||||
}
|
||||
|
||||
route.leaf.component = project_relative;
|
||||
if (item.uses_layout !== undefined) route.leaf.parent_id = item.uses_layout;
|
||||
}
|
||||
} else if (item.is_layout) {
|
||||
if (!route.layout) {
|
||||
route.layout = { depth, child_pages: [] };
|
||||
} else if (route.layout[item.kind]) {
|
||||
throw duplicate_files_error(
|
||||
item.kind + ' layout module',
|
||||
/** @type {string} */ (route.layout[item.kind])
|
||||
);
|
||||
}
|
||||
|
||||
route.layout[item.kind] = project_relative;
|
||||
} else if (item.is_page) {
|
||||
if (!route.leaf) {
|
||||
route.leaf = { depth };
|
||||
} else if (route.leaf[item.kind]) {
|
||||
throw duplicate_files_error(
|
||||
item.kind + ' page module',
|
||||
/** @type {string} */ (route.leaf[item.kind])
|
||||
);
|
||||
}
|
||||
|
||||
route.leaf[item.kind] = project_relative;
|
||||
} else {
|
||||
if (route.endpoint) {
|
||||
throw duplicate_files_error('endpoint', route.endpoint.file);
|
||||
}
|
||||
|
||||
route.endpoint = {
|
||||
file: project_relative
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// then handle children
|
||||
for (const file of files) {
|
||||
if (file.is_dir) {
|
||||
walk(depth + 1, path.posix.join(id, file.name), file.name, route);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walk(0, '/', '', null);
|
||||
|
||||
if (routes.length === 1) {
|
||||
const root = routes[0];
|
||||
if (!root.leaf && !root.error && !root.layout && !root.endpoint) {
|
||||
throw new Error(
|
||||
'No routes found. If you are using a custom src/routes directory, make sure it is specified in svelte.config.js'
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If there's no routes directory, we'll just create a single empty route. This ensures the root layout and
|
||||
// error components are included in the manifest, which is needed for subsequent build/dev commands to work
|
||||
routes.push({
|
||||
id: '/',
|
||||
segment: '',
|
||||
pattern: /^$/,
|
||||
params: [],
|
||||
parent: null,
|
||||
layout: null,
|
||||
error: null,
|
||||
leaf: null,
|
||||
page: null,
|
||||
endpoint: null
|
||||
});
|
||||
}
|
||||
|
||||
prevent_conflicts(routes);
|
||||
|
||||
const root = routes[0];
|
||||
|
||||
if (!root.layout?.component) {
|
||||
if (!root.layout) root.layout = { depth: 0, child_pages: [] };
|
||||
root.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`));
|
||||
}
|
||||
|
||||
if (!root.error?.component) {
|
||||
if (!root.error) root.error = { depth: 0 };
|
||||
root.error.component = posixify(path.relative(cwd, `${fallback}/error.svelte`));
|
||||
}
|
||||
|
||||
// we do layouts/errors first as they are more likely to be reused,
|
||||
// and smaller indexes take fewer bytes. also, this guarantees that
|
||||
// the default error/layout are 0/1
|
||||
for (const route of routes) {
|
||||
if (route.layout) {
|
||||
if (!route.layout?.component) {
|
||||
route.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`));
|
||||
}
|
||||
nodes.push(route.layout);
|
||||
}
|
||||
if (route.error) nodes.push(route.error);
|
||||
}
|
||||
|
||||
for (const route of routes) {
|
||||
if (route.leaf) nodes.push(route.leaf);
|
||||
}
|
||||
|
||||
const indexes = new Map(nodes.map((node, i) => [node, i]));
|
||||
|
||||
for (const route of routes) {
|
||||
if (!route.leaf) continue;
|
||||
|
||||
route.page = {
|
||||
layouts: [],
|
||||
errors: [],
|
||||
leaf: /** @type {number} */ (indexes.get(route.leaf))
|
||||
};
|
||||
|
||||
/** @type {import('types').RouteData | null} */
|
||||
let current_route = route;
|
||||
let current_node = route.leaf;
|
||||
let parent_id = route.leaf.parent_id;
|
||||
|
||||
while (current_route) {
|
||||
if (parent_id === undefined || current_route.segment === parent_id) {
|
||||
if (current_route.layout || current_route.error) {
|
||||
route.page.layouts.unshift(
|
||||
current_route.layout ? indexes.get(current_route.layout) : undefined
|
||||
);
|
||||
route.page.errors.unshift(
|
||||
current_route.error ? indexes.get(current_route.error) : undefined
|
||||
);
|
||||
}
|
||||
|
||||
if (current_route.layout) {
|
||||
/** @type {import('types').PageNode[]} */ (current_route.layout.child_pages).push(
|
||||
route.leaf
|
||||
);
|
||||
current_node.parent = current_node = current_route.layout;
|
||||
parent_id = current_node.parent_id;
|
||||
} else {
|
||||
parent_id = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
current_route = current_route.parent;
|
||||
}
|
||||
|
||||
if (parent_id !== undefined) {
|
||||
throw new Error(`${current_node.component} references missing segment "${parent_id}"`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodes,
|
||||
routes: sort_routes(routes)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} project_relative
|
||||
* @param {string} file
|
||||
* @param {string[]} component_extensions
|
||||
* @param {string[]} module_extensions
|
||||
* @returns {import('./types.js').RouteFile}
|
||||
*/
|
||||
function analyze(project_relative, file, component_extensions, module_extensions) {
|
||||
const component_extension = component_extensions.find((ext) => file.endsWith(ext));
|
||||
if (component_extension) {
|
||||
const name = file.slice(0, -component_extension.length);
|
||||
const pattern = /^\+(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/;
|
||||
const match = pattern.exec(name);
|
||||
if (!match) {
|
||||
throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'component',
|
||||
is_page: !!match[1],
|
||||
is_layout: !!match[3],
|
||||
is_error: !!match[5],
|
||||
uses_layout: match[2] ?? match[4]
|
||||
};
|
||||
}
|
||||
|
||||
const module_extension = module_extensions.find((ext) => file.endsWith(ext));
|
||||
if (module_extension) {
|
||||
const name = file.slice(0, -module_extension.length);
|
||||
const pattern =
|
||||
/^\+(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/;
|
||||
const match = pattern.exec(name);
|
||||
if (!match) {
|
||||
throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
|
||||
} else if (match[3] || match[6]) {
|
||||
throw new Error(
|
||||
// prettier-ignore
|
||||
`Only Svelte files can reference named layouts. Remove '${match[3] || match[6]}' from ${file} (at ${project_relative})`
|
||||
);
|
||||
}
|
||||
|
||||
const kind = match[1] || match[4] || match[7] ? 'server' : 'universal';
|
||||
|
||||
return {
|
||||
kind,
|
||||
is_page: !!match[2],
|
||||
is_layout: !!match[5]
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Files and directories prefixed with + are reserved (saw ${project_relative})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} needle
|
||||
* @param {string} haystack
|
||||
*/
|
||||
function count_occurrences(needle, haystack) {
|
||||
let count = 0;
|
||||
for (let i = 0; i < haystack.length; i += 1) {
|
||||
if (haystack[i] === needle) count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** @param {import('types').RouteData[]} routes */
|
||||
function prevent_conflicts(routes) {
|
||||
/** @type {Map<string, string>} */
|
||||
const lookup = new Map();
|
||||
|
||||
for (const route of routes) {
|
||||
if (!route.leaf && !route.endpoint) continue;
|
||||
|
||||
const normalized = normalize_route_id(route.id);
|
||||
|
||||
// find all permutations created by optional parameters
|
||||
const split = normalized.split(/<\?(.+?)>/g);
|
||||
|
||||
let permutations = [/** @type {string} */ (split[0])];
|
||||
|
||||
// turn `x/[[optional]]/y` into `x/y` and `x/[required]/y`
|
||||
for (let i = 1; i < split.length; i += 2) {
|
||||
const matcher = split[i];
|
||||
const next = split[i + 1];
|
||||
|
||||
permutations = permutations.reduce((a, b) => {
|
||||
a.push(b + next);
|
||||
if (!(matcher === '*' && b.endsWith('//'))) a.push(b + `<${matcher}>${next}`);
|
||||
return a;
|
||||
}, /** @type {string[]} */ ([]));
|
||||
}
|
||||
|
||||
for (const permutation of permutations) {
|
||||
// remove leading/trailing/duplicated slashes caused by prior
|
||||
// manipulation of optional parameters and (groups)
|
||||
const key = permutation
|
||||
.replace(/\/{2,}/, '/')
|
||||
.replace(/^\//, '')
|
||||
.replace(/\/$/, '');
|
||||
|
||||
if (lookup.has(key)) {
|
||||
throw new Error(
|
||||
`The "${lookup.get(key)}" and "${route.id}" routes conflict with each other`
|
||||
);
|
||||
}
|
||||
|
||||
lookup.set(key, route.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} id */
|
||||
function normalize_route_id(id) {
|
||||
return (
|
||||
id
|
||||
// remove groups
|
||||
.replace(/(?<=^|\/)\(.+?\)(?=$|\/)/g, '')
|
||||
|
||||
.replace(/\[[ux]\+([0-9a-f]+)\]/g, (_, x) =>
|
||||
String.fromCharCode(parseInt(x, 16)).replace(/\//g, '%2f')
|
||||
)
|
||||
|
||||
// replace `[param]` with `<*>`, `[param=x]` with `<x>`, and `[[param]]` with `<?*>`
|
||||
.replace(
|
||||
/\[(?:(\[)|(\.\.\.))?.+?(=.+?)?\]\]?/g,
|
||||
(_, optional, rest, matcher) => `<${optional ? '?' : ''}${rest ?? ''}${matcher ?? '*'}>`
|
||||
)
|
||||
);
|
||||
}
|
161
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/sort.js
generated
vendored
Normal file
161
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/sort.js
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
import { get_route_segments } from '../../../utils/routing.js';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* type: 'static' | 'required' | 'optional' | 'rest';
|
||||
* content: string;
|
||||
* matched: boolean;
|
||||
* }} Part
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Part[]} Segment
|
||||
*/
|
||||
|
||||
const EMPTY = { type: 'static', content: '', matched: false };
|
||||
|
||||
/** @param {import('types').RouteData[]} routes */
|
||||
export function sort_routes(routes) {
|
||||
/** @type {Map<string, Part[]>} */
|
||||
const segment_cache = new Map();
|
||||
|
||||
/** @param {string} segment */
|
||||
function get_parts(segment) {
|
||||
if (!segment_cache.has(segment)) {
|
||||
segment_cache.set(segment, split(segment));
|
||||
}
|
||||
|
||||
return segment_cache.get(segment);
|
||||
}
|
||||
|
||||
/** @param {string} id */
|
||||
function split(id) {
|
||||
/** @type {Part[]} */
|
||||
const parts = [];
|
||||
|
||||
let i = 0;
|
||||
while (i <= id.length) {
|
||||
const start = id.indexOf('[', i);
|
||||
if (start === -1) {
|
||||
parts.push({ type: 'static', content: id.slice(i), matched: false });
|
||||
break;
|
||||
}
|
||||
|
||||
parts.push({ type: 'static', content: id.slice(i, start), matched: false });
|
||||
|
||||
const type = id[start + 1] === '[' ? 'optional' : id[start + 1] === '.' ? 'rest' : 'required';
|
||||
const delimiter = type === 'optional' ? ']]' : ']';
|
||||
const end = id.indexOf(delimiter, start);
|
||||
|
||||
if (end === -1) {
|
||||
throw new Error(`Invalid route ID ${id}`);
|
||||
}
|
||||
|
||||
const content = id.slice(start, (i = end + delimiter.length));
|
||||
|
||||
parts.push({
|
||||
type,
|
||||
content,
|
||||
matched: content.includes('=')
|
||||
});
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
return routes.sort((route_a, route_b) => {
|
||||
const segments_a = split_route_id(route_a.id).map(get_parts);
|
||||
const segments_b = split_route_id(route_b.id).map(get_parts);
|
||||
|
||||
for (let i = 0; i < Math.max(segments_a.length, segments_b.length); i += 1) {
|
||||
const segment_a = segments_a[i] ?? [EMPTY];
|
||||
const segment_b = segments_b[i] ?? [EMPTY];
|
||||
|
||||
for (let j = 0; j < Math.max(segment_a.length, segment_b.length); j += 1) {
|
||||
const a = segment_a[j];
|
||||
const b = segment_b[j];
|
||||
|
||||
// first part of each segment is always static
|
||||
// (though it may be the empty string), then
|
||||
// it alternates between dynamic and static
|
||||
// (i.e. [foo][bar] is disallowed)
|
||||
const dynamic = j % 2 === 1;
|
||||
|
||||
if (dynamic) {
|
||||
if (!a) return -1;
|
||||
if (!b) return +1;
|
||||
|
||||
// get the next static chunk, so we can handle [...rest] edge cases
|
||||
const next_a = segment_a[j + 1].content || segments_a[i + 1]?.[0].content;
|
||||
const next_b = segment_b[j + 1].content || segments_b[i + 1]?.[0].content;
|
||||
|
||||
// `[...rest]/x` outranks `[...rest]`
|
||||
if (a.type === 'rest' && b.type === 'rest') {
|
||||
if (next_a && next_b) continue;
|
||||
if (next_a) return -1;
|
||||
if (next_b) return +1;
|
||||
}
|
||||
|
||||
// `[...rest]/x` outranks `[required]` or `[required]/[required]`
|
||||
// but not `[required]/x`
|
||||
if (a.type === 'rest') {
|
||||
return next_a && !next_b ? -1 : +1;
|
||||
}
|
||||
|
||||
if (b.type === 'rest') {
|
||||
return next_b && !next_a ? +1 : -1;
|
||||
}
|
||||
|
||||
// part with matcher outranks one without
|
||||
if (a.matched !== b.matched) {
|
||||
return a.matched ? -1 : +1;
|
||||
}
|
||||
|
||||
if (a.type !== b.type) {
|
||||
// `[...rest]` has already been accounted for, so here
|
||||
// we're comparing between `[required]` and `[[optional]]`
|
||||
if (a.type === 'required') return -1;
|
||||
if (b.type === 'required') return +1;
|
||||
}
|
||||
} else if (a.content !== b.content) {
|
||||
// shallower path outranks deeper path
|
||||
if (a === EMPTY) return -1;
|
||||
if (b === EMPTY) return +1;
|
||||
|
||||
return sort_static(a.content, b.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return route_a.id < route_b.id ? +1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {string} id */
|
||||
function split_route_id(id) {
|
||||
return get_route_segments(
|
||||
id
|
||||
// remove all [[optional]] parts unless they're at the very end
|
||||
.replace(/\[\[[^\]]+\]\](?!$)/g, '')
|
||||
).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort two strings lexicographically, except `foobar` outranks `foo`
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
*/
|
||||
function sort_static(a, b) {
|
||||
if (a === b) return 0;
|
||||
|
||||
for (let i = 0; true; i += 1) {
|
||||
const char_a = a[i];
|
||||
const char_b = b[i];
|
||||
|
||||
if (char_a !== char_b) {
|
||||
if (char_a === undefined) return +1;
|
||||
if (char_b === undefined) return -1;
|
||||
return char_a < char_b ? -1 : +1;
|
||||
}
|
||||
}
|
||||
}
|
37
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/types.d.ts
generated
vendored
Normal file
37
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/types.d.ts
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
import { PageNode } from 'types';
|
||||
|
||||
interface Part {
|
||||
dynamic: boolean;
|
||||
optional: boolean;
|
||||
rest: boolean;
|
||||
type: string | null;
|
||||
}
|
||||
|
||||
interface RouteTreeNode {
|
||||
error: PageNode | undefined;
|
||||
layout: PageNode | undefined;
|
||||
}
|
||||
|
||||
export type RouteTree = Map<string, RouteTreeNode>;
|
||||
|
||||
interface RouteComponent {
|
||||
kind: 'component';
|
||||
is_page: boolean;
|
||||
is_layout: boolean;
|
||||
is_error: boolean;
|
||||
uses_layout: string | undefined;
|
||||
}
|
||||
|
||||
interface RouteSharedModule {
|
||||
kind: 'universal';
|
||||
is_page: boolean;
|
||||
is_layout: boolean;
|
||||
}
|
||||
|
||||
interface RouteServerModule {
|
||||
kind: 'server';
|
||||
is_page: boolean;
|
||||
is_layout: boolean;
|
||||
}
|
||||
|
||||
export type RouteFile = RouteComponent | RouteSharedModule | RouteServerModule;
|
78
node_modules/@sveltejs/kit/src/core/sync/sync.js
generated
vendored
Normal file
78
node_modules/@sveltejs/kit/src/core/sync/sync.js
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
import path from 'node:path';
|
||||
import create_manifest_data from './create_manifest_data/index.js';
|
||||
import { write_client_manifest } from './write_client_manifest.js';
|
||||
import { write_root } from './write_root.js';
|
||||
import { write_tsconfig } from './write_tsconfig.js';
|
||||
import { write_types, write_all_types } from './write_types/index.js';
|
||||
import { write_ambient } from './write_ambient.js';
|
||||
import { write_server } from './write_server.js';
|
||||
|
||||
/**
|
||||
* Initialize SvelteKit's generated files.
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {string} mode
|
||||
*/
|
||||
export function init(config, mode) {
|
||||
write_tsconfig(config.kit);
|
||||
write_ambient(config.kit, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update SvelteKit's generated files
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
*/
|
||||
export async function create(config) {
|
||||
const manifest_data = create_manifest_data({ config });
|
||||
|
||||
const output = path.join(config.kit.outDir, 'generated');
|
||||
|
||||
write_client_manifest(config.kit, manifest_data, `${output}/client`);
|
||||
write_server(config, output);
|
||||
write_root(manifest_data, output);
|
||||
await write_all_types(config, manifest_data);
|
||||
|
||||
return { manifest_data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update SvelteKit's generated files in response to a single file content update.
|
||||
* Do not call this when the file in question was created/deleted.
|
||||
*
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
* @param {string} file
|
||||
*/
|
||||
export async function update(config, manifest_data, file) {
|
||||
await write_types(config, manifest_data, file);
|
||||
|
||||
return { manifest_data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Run sync.init and sync.create in series, returning the result from sync.create.
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {string} mode The Vite mode
|
||||
*/
|
||||
export async function all(config, mode) {
|
||||
init(config, mode);
|
||||
return await create(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run sync.init and then generate all type files.
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {string} mode The Vite mode
|
||||
*/
|
||||
export async function all_types(config, mode) {
|
||||
init(config, mode);
|
||||
const manifest_data = create_manifest_data({ config });
|
||||
await write_all_types(config, manifest_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate __SERVER__/internal.js in response to src/{app.html,error.html,service-worker.js} changing
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
*/
|
||||
export function server(config) {
|
||||
write_server(config, path.join(config.kit.outDir, 'generated'));
|
||||
}
|
6
node_modules/@sveltejs/kit/src/core/sync/ts.js
generated
vendored
Normal file
6
node_modules/@sveltejs/kit/src/core/sync/ts.js
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/** @type {import('typescript')} */
|
||||
// @ts-ignore
|
||||
export let ts = undefined;
|
||||
try {
|
||||
ts = (await import('typescript')).default;
|
||||
} catch {}
|
75
node_modules/@sveltejs/kit/src/core/sync/utils.js
generated
vendored
Normal file
75
node_modules/@sveltejs/kit/src/core/sync/utils.js
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { VERSION } from 'svelte/compiler';
|
||||
import { mkdirp } from '../../utils/filesystem.js';
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
const previous_contents = new Map();
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {string} code
|
||||
*/
|
||||
export function write_if_changed(file, code) {
|
||||
if (code !== previous_contents.get(file)) {
|
||||
write(file, code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {string} code
|
||||
*/
|
||||
export function write(file, code) {
|
||||
previous_contents.set(file, code);
|
||||
mkdirp(path.dirname(file));
|
||||
fs.writeFileSync(file, code);
|
||||
}
|
||||
|
||||
/** @type {WeakMap<TemplateStringsArray, { strings: string[], indents: string[] }>} */
|
||||
const dedent_map = new WeakMap();
|
||||
|
||||
/**
|
||||
* Allows indenting template strings without the extra indentation ending up in the result.
|
||||
* Still allows indentation of lines relative to one another in the template string.
|
||||
* @param {TemplateStringsArray} strings
|
||||
* @param {any[]} values
|
||||
*/
|
||||
export function dedent(strings, ...values) {
|
||||
let dedented = dedent_map.get(strings);
|
||||
|
||||
if (!dedented) {
|
||||
const indentation = /** @type {RegExpExecArray} */ (/\n?([ \t]*)/.exec(strings[0]))[1];
|
||||
const pattern = new RegExp(`^${indentation}`, 'gm');
|
||||
|
||||
dedented = {
|
||||
strings: strings.map((str) => str.replace(pattern, '')),
|
||||
indents: []
|
||||
};
|
||||
|
||||
let current = '\n';
|
||||
|
||||
for (let i = 0; i < values.length; i += 1) {
|
||||
const string = dedented.strings[i];
|
||||
const match = /\n([ \t]*)$/.exec(string);
|
||||
|
||||
if (match) current = match[0];
|
||||
dedented.indents[i] = current;
|
||||
}
|
||||
|
||||
dedent_map.set(strings, dedented);
|
||||
}
|
||||
|
||||
let str = dedented.strings[0];
|
||||
for (let i = 0; i < values.length; i += 1) {
|
||||
str += String(values[i]).replace(/\n/g, dedented.indents[i]) + dedented.strings[i + 1];
|
||||
}
|
||||
|
||||
str = str.trim();
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
export function isSvelte5Plus() {
|
||||
return Number(VERSION[0]) >= 5;
|
||||
}
|
63
node_modules/@sveltejs/kit/src/core/sync/write_ambient.js
generated
vendored
Normal file
63
node_modules/@sveltejs/kit/src/core/sync/write_ambient.js
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { get_env } from '../../exports/vite/utils.js';
|
||||
import { GENERATED_COMMENT } from '../../constants.js';
|
||||
import { create_dynamic_types, create_static_types } from '../env.js';
|
||||
import { write_if_changed } from './utils.js';
|
||||
|
||||
// TODO these types should be described in a neutral place, rather than
|
||||
// inside either `packages/kit` or `kit.svelte.dev`
|
||||
const descriptions_dir = fileURLToPath(new URL('../../../src/types/synthetic', import.meta.url));
|
||||
|
||||
/** @param {string} filename */
|
||||
function read_description(filename) {
|
||||
const content = fs.readFileSync(`${descriptions_dir}/${filename}`, 'utf8');
|
||||
return `/**\n${content
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((line) => ` * ${line}`)
|
||||
.join('\n')}\n */`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').Env} env
|
||||
* @param {{
|
||||
* public_prefix: string;
|
||||
* private_prefix: string;
|
||||
* }} prefixes
|
||||
*/
|
||||
const template = (env, prefixes) => `
|
||||
${GENERATED_COMMENT}
|
||||
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
${read_description('$env+static+private.md')}
|
||||
${create_static_types('private', env)}
|
||||
|
||||
${read_description('$env+static+public.md')}
|
||||
${create_static_types('public', env)}
|
||||
|
||||
${read_description('$env+dynamic+private.md')}
|
||||
${create_dynamic_types('private', env, prefixes)}
|
||||
|
||||
${read_description('$env+dynamic+public.md')}
|
||||
${create_dynamic_types('public', env, prefixes)}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Writes ambient declarations including types reference to @sveltejs/kit,
|
||||
* and the existing environment variables in process.env to
|
||||
* $env/static/private and $env/static/public
|
||||
* @param {import('types').ValidatedKitConfig} config
|
||||
* @param {string} mode The Vite mode
|
||||
*/
|
||||
export function write_ambient(config, mode) {
|
||||
const env = get_env(config.env, mode);
|
||||
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env;
|
||||
|
||||
write_if_changed(
|
||||
path.join(config.outDir, 'ambient.d.ts'),
|
||||
template(env, { public_prefix, private_prefix })
|
||||
);
|
||||
}
|
167
node_modules/@sveltejs/kit/src/core/sync/write_client_manifest.js
generated
vendored
Normal file
167
node_modules/@sveltejs/kit/src/core/sync/write_client_manifest.js
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
import path from 'node:path';
|
||||
import { relative_path, resolve_entry } from '../../utils/filesystem.js';
|
||||
import { s } from '../../utils/misc.js';
|
||||
import { dedent, isSvelte5Plus, write_if_changed } from './utils.js';
|
||||
import colors from 'kleur';
|
||||
|
||||
/**
|
||||
* Writes the client manifest to disk. The manifest is used to power the router. It contains the
|
||||
* list of routes and corresponding Svelte components (i.e. pages and layouts).
|
||||
* @param {import('types').ValidatedKitConfig} kit
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
* @param {string} output
|
||||
* @param {Array<{ has_server_load: boolean }>} [metadata]
|
||||
*/
|
||||
export function write_client_manifest(kit, manifest_data, output, metadata) {
|
||||
/**
|
||||
* Creates a module that exports a `CSRPageNode`
|
||||
* @param {import('types').PageNode} node
|
||||
*/
|
||||
function generate_node(node) {
|
||||
const declarations = [];
|
||||
|
||||
if (node.universal) {
|
||||
declarations.push(
|
||||
`import * as universal from ${s(relative_path(`${output}/nodes`, node.universal))};`,
|
||||
'export { universal };'
|
||||
);
|
||||
}
|
||||
|
||||
if (node.component) {
|
||||
declarations.push(
|
||||
`export { default as component } from ${s(
|
||||
relative_path(`${output}/nodes`, node.component)
|
||||
)};`
|
||||
);
|
||||
}
|
||||
|
||||
return declarations.join('\n');
|
||||
}
|
||||
|
||||
/** @type {Map<import('types').PageNode, number>} */
|
||||
const indices = new Map();
|
||||
const nodes = manifest_data.nodes
|
||||
.map((node, i) => {
|
||||
indices.set(node, i);
|
||||
|
||||
write_if_changed(`${output}/nodes/${i}.js`, generate_node(node));
|
||||
return `() => import('./nodes/${i}')`;
|
||||
})
|
||||
.join(',\n');
|
||||
|
||||
const layouts_with_server_load = new Set();
|
||||
|
||||
const dictionary = dedent`
|
||||
{
|
||||
${manifest_data.routes
|
||||
.map((route) => {
|
||||
if (route.page) {
|
||||
const errors = route.page.errors.slice(1).map((n) => n ?? '');
|
||||
const layouts = route.page.layouts.slice(1).map((n) => n ?? '');
|
||||
|
||||
while (layouts.at(-1) === '') layouts.pop();
|
||||
while (errors.at(-1) === '') errors.pop();
|
||||
|
||||
let leaf_has_server_load = false;
|
||||
if (route.leaf) {
|
||||
if (metadata) {
|
||||
const i = /** @type {number} */ (indices.get(route.leaf));
|
||||
|
||||
leaf_has_server_load = metadata[i].has_server_load;
|
||||
} else if (route.leaf.server) {
|
||||
leaf_has_server_load = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode whether or not the route uses server data
|
||||
// using the ones' complement, to save space
|
||||
const array = [`${leaf_has_server_load ? '~' : ''}${route.page.leaf}`];
|
||||
|
||||
// Encode whether or not the layout uses server data.
|
||||
// It's a different method compared to pages because layouts
|
||||
// are reused across pages, so we save space by doing it this way.
|
||||
route.page.layouts.forEach((layout) => {
|
||||
if (layout == undefined) return;
|
||||
|
||||
let layout_has_server_load = false;
|
||||
|
||||
if (metadata) {
|
||||
layout_has_server_load = metadata[layout].has_server_load;
|
||||
} else if (manifest_data.nodes[layout].server) {
|
||||
layout_has_server_load = true;
|
||||
}
|
||||
|
||||
if (layout_has_server_load) {
|
||||
layouts_with_server_load.add(layout);
|
||||
}
|
||||
});
|
||||
|
||||
// only include non-root layout/error nodes if they exist
|
||||
if (layouts.length > 0 || errors.length > 0) array.push(`[${layouts.join(',')}]`);
|
||||
if (errors.length > 0) array.push(`[${errors.join(',')}]`);
|
||||
|
||||
return `${s(route.id)}: [${array.join(',')}]`;
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(',\n')}
|
||||
}
|
||||
`;
|
||||
|
||||
const hooks_file = resolve_entry(kit.files.hooks.client);
|
||||
|
||||
const typo = resolve_entry('src/+hooks.client');
|
||||
if (typo) {
|
||||
console.log(
|
||||
colors
|
||||
.bold()
|
||||
.yellow(
|
||||
`Unexpected + prefix. Did you mean ${typo.split('/').at(-1)?.slice(1)}?` +
|
||||
` at ${path.resolve(typo)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
write_if_changed(
|
||||
`${output}/app.js`,
|
||||
dedent`
|
||||
${hooks_file ? `import * as client_hooks from '${relative_path(output, hooks_file)}';` : ''}
|
||||
|
||||
export { matchers } from './matchers.js';
|
||||
|
||||
export const nodes = [
|
||||
${nodes}
|
||||
];
|
||||
|
||||
export const server_loads = [${[...layouts_with_server_load].join(',')}];
|
||||
|
||||
export const dictionary = ${dictionary};
|
||||
|
||||
export const hooks = {
|
||||
handleError: ${
|
||||
hooks_file ? 'client_hooks.handleError || ' : ''
|
||||
}(({ error }) => { console.error(error) }),
|
||||
};
|
||||
|
||||
export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
|
||||
`
|
||||
);
|
||||
|
||||
// write matchers to a separate module so that we don't
|
||||
// need to worry about name conflicts
|
||||
const imports = [];
|
||||
const matchers = [];
|
||||
|
||||
for (const key in manifest_data.matchers) {
|
||||
const src = manifest_data.matchers[key];
|
||||
|
||||
imports.push(`import { match as ${key} } from ${s(relative_path(output, src))};`);
|
||||
matchers.push(key);
|
||||
}
|
||||
|
||||
const module = imports.length
|
||||
? `${imports.join('\n')}\n\nexport const matchers = { ${matchers.join(', ')} };`
|
||||
: 'export const matchers = {};';
|
||||
|
||||
write_if_changed(`${output}/matchers.js`, module);
|
||||
}
|
103
node_modules/@sveltejs/kit/src/core/sync/write_root.js
generated
vendored
Normal file
103
node_modules/@sveltejs/kit/src/core/sync/write_root.js
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
import { dedent, isSvelte5Plus, write_if_changed } from './utils.js';
|
||||
|
||||
/**
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
* @param {string} output
|
||||
*/
|
||||
export function write_root(manifest_data, output) {
|
||||
// TODO remove default layout altogether
|
||||
|
||||
const max_depth = Math.max(
|
||||
...manifest_data.routes.map((route) =>
|
||||
route.page ? route.page.layouts.filter(Boolean).length + 1 : 0
|
||||
),
|
||||
1
|
||||
);
|
||||
|
||||
const levels = [];
|
||||
for (let i = 0; i <= max_depth; i += 1) {
|
||||
levels.push(i);
|
||||
}
|
||||
|
||||
let l = max_depth;
|
||||
|
||||
let pyramid = `<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />`;
|
||||
|
||||
while (l--) {
|
||||
pyramid = dedent`
|
||||
{#if constructors[${l + 1}]}
|
||||
<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}}>
|
||||
${pyramid}
|
||||
</svelte:component>
|
||||
{:else}
|
||||
<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />
|
||||
{/if}
|
||||
`;
|
||||
}
|
||||
|
||||
write_if_changed(
|
||||
`${output}/root.svelte`,
|
||||
dedent`
|
||||
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
|
||||
<script>
|
||||
import { setContext, afterUpdate, onMount, tick } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// stores
|
||||
export let stores;
|
||||
export let page;
|
||||
|
||||
export let constructors;
|
||||
export let components = [];
|
||||
export let form;
|
||||
${levels.map((l) => `export let data_${l} = null;`).join('\n')}
|
||||
|
||||
if (!browser) {
|
||||
setContext('__svelte__', stores);
|
||||
}
|
||||
|
||||
$: stores.page.set(page);
|
||||
afterUpdate(stores.page.notify);
|
||||
|
||||
let mounted = false;
|
||||
let navigated = false;
|
||||
let title = null;
|
||||
|
||||
onMount(() => {
|
||||
const unsubscribe = stores.page.subscribe(() => {
|
||||
if (mounted) {
|
||||
navigated = true;
|
||||
tick().then(() => {
|
||||
title = document.title || 'untitled page';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
mounted = true;
|
||||
return unsubscribe;
|
||||
});
|
||||
</script>
|
||||
|
||||
${pyramid}
|
||||
|
||||
{#if mounted}
|
||||
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
|
||||
{#if navigated}
|
||||
{title}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
`
|
||||
);
|
||||
|
||||
if (isSvelte5Plus()) {
|
||||
write_if_changed(
|
||||
`${output}/root.js`,
|
||||
dedent`
|
||||
import { asClassComponent } from 'svelte/legacy';
|
||||
import Root from './root.svelte';
|
||||
export default asClassComponent(Root);
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
110
node_modules/@sveltejs/kit/src/core/sync/write_server.js
generated
vendored
Normal file
110
node_modules/@sveltejs/kit/src/core/sync/write_server.js
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
import path from 'node:path';
|
||||
import { hash } from '../../runtime/hash.js';
|
||||
import { posixify, resolve_entry } from '../../utils/filesystem.js';
|
||||
import { s } from '../../utils/misc.js';
|
||||
import { load_error_page, load_template } from '../config/index.js';
|
||||
import { runtime_directory } from '../utils.js';
|
||||
import { isSvelte5Plus, write_if_changed } from './utils.js';
|
||||
import colors from 'kleur';
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* hooks: string | null;
|
||||
* config: import('types').ValidatedConfig;
|
||||
* has_service_worker: boolean;
|
||||
* runtime_directory: string;
|
||||
* template: string;
|
||||
* error_page: string;
|
||||
* }} opts
|
||||
*/
|
||||
const server_template = ({
|
||||
config,
|
||||
hooks,
|
||||
has_service_worker,
|
||||
runtime_directory,
|
||||
template,
|
||||
error_page
|
||||
}) => `
|
||||
import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
|
||||
import { set_building } from '__sveltekit/environment';
|
||||
import { set_assets } from '__sveltekit/paths';
|
||||
import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js';
|
||||
|
||||
export const options = {
|
||||
app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
|
||||
csp: ${s(config.kit.csp)},
|
||||
csrf_check_origin: ${s(config.kit.csrf.checkOrigin)},
|
||||
track_server_fetches: ${s(config.kit.dangerZone.trackServerFetches)},
|
||||
embedded: ${config.kit.embedded},
|
||||
env_public_prefix: '${config.kit.env.publicPrefix}',
|
||||
env_private_prefix: '${config.kit.env.privatePrefix}',
|
||||
hooks: null, // added lazily, via \`get_hooks\`
|
||||
preload_strategy: ${s(config.kit.output.preloadStrategy)},
|
||||
root,
|
||||
service_worker: ${has_service_worker},
|
||||
templates: {
|
||||
app: ({ head, body, assets, nonce, env }) => ${s(template)
|
||||
.replace('%sveltekit.head%', '" + head + "')
|
||||
.replace('%sveltekit.body%', '" + body + "')
|
||||
.replace(/%sveltekit\.assets%/g, '" + assets + "')
|
||||
.replace(/%sveltekit\.nonce%/g, '" + nonce + "')
|
||||
.replace(
|
||||
/%sveltekit\.env\.([^%]+)%/g,
|
||||
(_match, capture) => `" + (env[${s(capture)}] ?? "") + "`
|
||||
)},
|
||||
error: ({ status, message }) => ${s(error_page)
|
||||
.replace(/%sveltekit\.status%/g, '" + status + "')
|
||||
.replace(/%sveltekit\.error\.message%/g, '" + message + "')}
|
||||
},
|
||||
version_hash: ${s(hash(config.kit.version.name))}
|
||||
};
|
||||
|
||||
export function get_hooks() {
|
||||
return ${hooks ? `import(${s(hooks)})` : '{}'};
|
||||
}
|
||||
|
||||
export { set_assets, set_building, set_private_env, set_public_env };
|
||||
`;
|
||||
|
||||
// TODO need to re-run this whenever src/app.html or src/error.html are
|
||||
// created or changed, or src/service-worker.js is created or deleted.
|
||||
// Also, need to check that updating hooks.server.js works
|
||||
|
||||
/**
|
||||
* Write server configuration to disk
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {string} output
|
||||
*/
|
||||
export function write_server(config, output) {
|
||||
const hooks_file = resolve_entry(config.kit.files.hooks.server);
|
||||
|
||||
const typo = resolve_entry('src/+hooks.server');
|
||||
if (typo) {
|
||||
console.log(
|
||||
colors
|
||||
.bold()
|
||||
.yellow(
|
||||
`Unexpected + prefix. Did you mean ${typo.split('/').at(-1)?.slice(1)}?` +
|
||||
` at ${path.resolve(typo)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {string} file */
|
||||
function relative(file) {
|
||||
return posixify(path.relative(`${output}/server`, file));
|
||||
}
|
||||
|
||||
write_if_changed(
|
||||
`${output}/server/internal.js`,
|
||||
server_template({
|
||||
config,
|
||||
hooks: hooks_file ? relative(hooks_file) : null,
|
||||
has_service_worker:
|
||||
config.kit.serviceWorker.register && !!resolve_entry(config.kit.files.serviceWorker),
|
||||
runtime_directory: relative(runtime_directory),
|
||||
template: load_template(process.cwd(), config),
|
||||
error_page: load_error_page(config)
|
||||
})
|
||||
);
|
||||
}
|
276
node_modules/@sveltejs/kit/src/core/sync/write_tsconfig.js
generated
vendored
Normal file
276
node_modules/@sveltejs/kit/src/core/sync/write_tsconfig.js
generated
vendored
Normal file
@ -0,0 +1,276 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import colors from 'kleur';
|
||||
import { posixify } from '../../utils/filesystem.js';
|
||||
import { write_if_changed } from './utils.js';
|
||||
import { ts } from './ts.js';
|
||||
|
||||
/**
|
||||
* @param {string} cwd
|
||||
* @param {string} file
|
||||
*/
|
||||
function maybe_file(cwd, file) {
|
||||
const resolved = path.resolve(cwd, file);
|
||||
if (fs.existsSync(resolved)) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
*/
|
||||
function project_relative(file) {
|
||||
return posixify(path.relative('.', file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
*/
|
||||
function remove_trailing_slashstar(file) {
|
||||
if (file.endsWith('/*')) {
|
||||
return file.slice(0, -2);
|
||||
} else {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the tsconfig that the user's tsconfig inherits from.
|
||||
* @param {import('types').ValidatedKitConfig} kit
|
||||
*/
|
||||
export function write_tsconfig(kit, cwd = process.cwd()) {
|
||||
const out = path.join(kit.outDir, 'tsconfig.json');
|
||||
|
||||
const user_config = load_user_tsconfig(cwd);
|
||||
if (user_config) validate_user_config(kit, cwd, out, user_config);
|
||||
|
||||
// only specify baseUrl if a) the user doesn't specify their own baseUrl
|
||||
// and b) they have non-relative paths. this causes problems with auto-imports,
|
||||
// so we print a suggestion that they use relative paths instead
|
||||
// TODO(v2): never include base URL, and skip the check below
|
||||
let include_base_url = false;
|
||||
|
||||
if (user_config && !user_config.options.compilerOptions?.baseUrl) {
|
||||
const non_relative_paths = new Set();
|
||||
for (const paths of Object.values(user_config?.options.compilerOptions?.paths || {})) {
|
||||
for (const path of paths) {
|
||||
if (!path.startsWith('.')) non_relative_paths.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (non_relative_paths.size) {
|
||||
include_base_url = true;
|
||||
|
||||
console.log(colors.bold().yellow('Please replace non-relative compilerOptions.paths:\n'));
|
||||
|
||||
for (const path of non_relative_paths) {
|
||||
console.log(` - "${path}" -> "./${path}"`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
'\nDoing so allows us to omit "baseUrl" — which causes problems with imports — from the generated tsconfig.json. See https://github.com/sveltejs/kit/pull/8437 for more information.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
write_if_changed(out, JSON.stringify(get_tsconfig(kit, include_base_url), null, '\t'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the tsconfig that the user's tsconfig inherits from.
|
||||
* @param {import('types').ValidatedKitConfig} kit
|
||||
* @param {boolean} include_base_url
|
||||
*/
|
||||
export function get_tsconfig(kit, include_base_url) {
|
||||
/** @param {string} file */
|
||||
const config_relative = (file) => posixify(path.relative(kit.outDir, file));
|
||||
|
||||
const include = new Set([
|
||||
'ambient.d.ts',
|
||||
'./types/**/$types.d.ts',
|
||||
config_relative('vite.config.js'),
|
||||
config_relative('vite.config.ts')
|
||||
]);
|
||||
// TODO(v2): find a better way to include all src files. We can't just use routes/lib only because
|
||||
// people might have other folders/files in src that they want included.
|
||||
const src_includes = [kit.files.routes, kit.files.lib, path.resolve('src')].filter((dir) => {
|
||||
const relative = path.relative(path.resolve('src'), dir);
|
||||
return !relative || relative.startsWith('..');
|
||||
});
|
||||
for (const dir of src_includes) {
|
||||
include.add(config_relative(`${dir}/**/*.js`));
|
||||
include.add(config_relative(`${dir}/**/*.ts`));
|
||||
include.add(config_relative(`${dir}/**/*.svelte`));
|
||||
}
|
||||
|
||||
// Test folder is a special case - we advocate putting tests in a top-level test folder
|
||||
// and it's not configurable (should we make it?)
|
||||
const test_folder = project_relative('tests');
|
||||
include.add(config_relative(`${test_folder}/**/*.js`));
|
||||
include.add(config_relative(`${test_folder}/**/*.ts`));
|
||||
include.add(config_relative(`${test_folder}/**/*.svelte`));
|
||||
|
||||
const exclude = [config_relative('node_modules/**'), './[!ambient.d.ts]**'];
|
||||
if (path.extname(kit.files.serviceWorker)) {
|
||||
exclude.push(config_relative(kit.files.serviceWorker));
|
||||
} else {
|
||||
exclude.push(config_relative(`${kit.files.serviceWorker}.js`));
|
||||
exclude.push(config_relative(`${kit.files.serviceWorker}.ts`));
|
||||
exclude.push(config_relative(`${kit.files.serviceWorker}.d.ts`));
|
||||
}
|
||||
|
||||
const config = {
|
||||
compilerOptions: {
|
||||
// generated options
|
||||
baseUrl: include_base_url ? config_relative('.') : undefined,
|
||||
paths: get_tsconfig_paths(kit, include_base_url),
|
||||
rootDirs: [config_relative('.'), './types'],
|
||||
|
||||
// essential options
|
||||
// svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||
// to enforce using \`import type\` instead of \`import\` for Types.
|
||||
importsNotUsedAsValues: 'error',
|
||||
// Vite compiles modules one at a time
|
||||
isolatedModules: true,
|
||||
// TypeScript doesn't know about import usages in the template because it only sees the
|
||||
// script of a Svelte file. Therefore preserve all value imports. Requires TS 4.5 or higher.
|
||||
preserveValueImports: true,
|
||||
|
||||
// This is required for svelte-package to work as expected
|
||||
// Can be overwritten
|
||||
lib: ['esnext', 'DOM', 'DOM.Iterable'],
|
||||
moduleResolution: 'node',
|
||||
module: 'esnext',
|
||||
target: 'esnext',
|
||||
|
||||
// TODO(v2): use the new flag verbatimModuleSyntax instead (requires support by Vite/Esbuild)
|
||||
ignoreDeprecations: ts && Number(ts.version.split('.')[0]) >= 5 ? '5.0' : undefined
|
||||
},
|
||||
include: [...include],
|
||||
exclude
|
||||
};
|
||||
|
||||
return kit.typescript.config(config) ?? config;
|
||||
}
|
||||
|
||||
/** @param {string} cwd */
|
||||
function load_user_tsconfig(cwd) {
|
||||
const file = maybe_file(cwd, 'tsconfig.json') || maybe_file(cwd, 'jsconfig.json');
|
||||
|
||||
if (!file) return;
|
||||
|
||||
// we have to eval the file, since it's not parseable as JSON (contains comments)
|
||||
const json = fs.readFileSync(file, 'utf-8');
|
||||
|
||||
return {
|
||||
kind: path.basename(file),
|
||||
options: (0, eval)(`(${json})`)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ValidatedKitConfig} kit
|
||||
* @param {string} cwd
|
||||
* @param {string} out
|
||||
* @param {{ kind: string, options: any }} config
|
||||
*/
|
||||
function validate_user_config(kit, cwd, out, config) {
|
||||
// we need to check that the user's tsconfig extends the framework config
|
||||
const extend = config.options.extends;
|
||||
const extends_framework_config =
|
||||
typeof extend === 'string'
|
||||
? path.resolve(cwd, extend) === out
|
||||
: Array.isArray(extend)
|
||||
? extend.some((e) => path.resolve(cwd, e) === out)
|
||||
: false;
|
||||
|
||||
const options = config.options.compilerOptions || {};
|
||||
|
||||
if (extends_framework_config) {
|
||||
const { paths: user_paths } = options;
|
||||
|
||||
if (user_paths && fs.existsSync(kit.files.lib)) {
|
||||
/** @type {string[]} */
|
||||
const lib = user_paths['$lib'] || [];
|
||||
/** @type {string[]} */
|
||||
const lib_ = user_paths['$lib/*'] || [];
|
||||
|
||||
// TODO(v2): check needs to be adjusted when we remove the base path
|
||||
const missing_lib_paths =
|
||||
!lib.some((relative) => path.resolve(cwd, relative) === kit.files.lib) ||
|
||||
!lib_.some((relative) => path.resolve(cwd, relative) === path.join(kit.files.lib, '/*'));
|
||||
|
||||
if (missing_lib_paths) {
|
||||
console.warn(
|
||||
colors
|
||||
.bold()
|
||||
.yellow(`Your compilerOptions.paths in ${config.kind} should include the following:`)
|
||||
);
|
||||
let relative = posixify(path.relative('.', kit.files.lib));
|
||||
if (!relative.startsWith('.')) relative = `./${relative}`;
|
||||
console.warn(`{\n "$lib":["${relative}"],\n "$lib/*":["${relative}/*"]\n}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let relative = posixify(path.relative('.', out));
|
||||
if (!relative.startsWith('./')) relative = './' + relative;
|
||||
|
||||
console.warn(
|
||||
colors
|
||||
.bold()
|
||||
.yellow(`Your ${config.kind} should extend the configuration generated by SvelteKit:`)
|
||||
);
|
||||
console.warn(`{\n "extends": "${relative}"\n}`);
|
||||
}
|
||||
}
|
||||
|
||||
// <something><optional /*>
|
||||
const alias_regex = /^(.+?)(\/\*)?$/;
|
||||
// <path><optional /* or .fileending>
|
||||
const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/;
|
||||
|
||||
/**
|
||||
* Generates tsconfig path aliases from kit's aliases.
|
||||
* Related to vite alias creation.
|
||||
*
|
||||
* @param {import('types').ValidatedKitConfig} config
|
||||
* @param {boolean} include_base_url
|
||||
*/
|
||||
function get_tsconfig_paths(config, include_base_url) {
|
||||
/** @param {string} file */
|
||||
const config_relative = (file) => posixify(path.relative(config.outDir, file));
|
||||
|
||||
const alias = { ...config.alias };
|
||||
if (fs.existsSync(project_relative(config.files.lib))) {
|
||||
alias['$lib'] = project_relative(config.files.lib);
|
||||
}
|
||||
|
||||
/** @type {Record<string, string[]>} */
|
||||
const paths = {};
|
||||
|
||||
for (const [key, value] of Object.entries(alias)) {
|
||||
const key_match = alias_regex.exec(key);
|
||||
if (!key_match) throw new Error(`Invalid alias key: ${key}`);
|
||||
|
||||
const value_match = value_regex.exec(value);
|
||||
if (!value_match) throw new Error(`Invalid alias value: ${value}`);
|
||||
|
||||
const rel_path = (include_base_url ? project_relative : config_relative)(
|
||||
remove_trailing_slashstar(value)
|
||||
);
|
||||
const slashstar = key_match[2];
|
||||
|
||||
if (slashstar) {
|
||||
paths[key] = [rel_path + '/*'];
|
||||
} else {
|
||||
paths[key] = [rel_path];
|
||||
const fileending = value_match[4];
|
||||
|
||||
if (!fileending && !(key + '/*' in alias)) {
|
||||
paths[key + '/*'] = [rel_path + '/*'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
853
node_modules/@sveltejs/kit/src/core/sync/write_types/index.js
generated
vendored
Normal file
853
node_modules/@sveltejs/kit/src/core/sync/write_types/index.js
generated
vendored
Normal file
@ -0,0 +1,853 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import MagicString from 'magic-string';
|
||||
import { posixify, rimraf, walk } from '../../../utils/filesystem.js';
|
||||
import { compact } from '../../../utils/array.js';
|
||||
import { ts } from '../ts.js';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* file_name: string;
|
||||
* modified: boolean;
|
||||
* code: string;
|
||||
* exports: any[];
|
||||
* } | null} Proxy
|
||||
*
|
||||
* @typedef {{
|
||||
* server: Proxy,
|
||||
* universal: Proxy
|
||||
* }} Proxies
|
||||
*
|
||||
* @typedef {Map<import('types').PageNode, {route: import('types').RouteData, proxies: Proxies}>} RoutesMap
|
||||
*/
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
/**
|
||||
* Creates types for the whole manifest
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
*/
|
||||
export async 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 related to the given file. This should only be called
|
||||
* if the file in question was edited, not if it was created/deleted/moved.
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
* @param {string} file
|
||||
*/
|
||||
export async function write_types(config, manifest_data, file) {
|
||||
if (!ts) return;
|
||||
|
||||
if (!path.basename(file).startsWith('+')) {
|
||||
// Not a route file
|
||||
return;
|
||||
}
|
||||
|
||||
const id = '/' + posixify(path.relative(config.kit.files.routes, path.dirname(file)));
|
||||
|
||||
const route = manifest_data.routes.find((route) => route.id === id);
|
||||
if (!route) return;
|
||||
if (!route.leaf && !route.layout && !route.endpoint) return; // nothing to do
|
||||
|
||||
update_types(config, create_routes_map(manifest_data), route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all leafs into a leaf -> route map
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
*/
|
||||
function create_routes_map(manifest_data) {
|
||||
/** @type {RoutesMap} */
|
||||
const map = new Map();
|
||||
for (const route of manifest_data.routes) {
|
||||
if (route.leaf) {
|
||||
map.set(route.leaf, { route, proxies: { server: null, universal: null } });
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update types for a specific route
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
* @param {RoutesMap} routes
|
||||
* @param {import('types').RouteData} route
|
||||
* @param {Set<string>} [to_delete]
|
||||
*/
|
||||
function update_types(config, routes, route, to_delete = new Set()) {
|
||||
const routes_dir = posixify(path.relative('.', config.kit.files.routes)).replace(/\.\.\//g, '');
|
||||
const outdir = path.join(config.kit.outDir, 'types', routes_dir, route.id);
|
||||
|
||||
// now generate new types
|
||||
const imports = ["import type * as Kit from '@sveltejs/kit';"];
|
||||
|
||||
/** @type {string[]} */
|
||||
const declarations = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
const exports = [];
|
||||
|
||||
// add 'Expand' helper
|
||||
// Makes sure a type is "repackaged" and therefore more readable
|
||||
declarations.push('type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;');
|
||||
|
||||
// returns the predicate of a matcher's type guard - or string if there is no type guard
|
||||
declarations.push(
|
||||
// TS complains on infer U, which seems weird, therefore ts-ignore it
|
||||
[
|
||||
'// @ts-ignore',
|
||||
'type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;'
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
declarations.push(
|
||||
'type RouteParams = ' + generate_params_type(route.params, outdir, config) + ';'
|
||||
);
|
||||
|
||||
if (route.params.length > 0) {
|
||||
exports.push(
|
||||
'export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;'
|
||||
);
|
||||
}
|
||||
|
||||
declarations.push(`type RouteId = '${route.id}';`);
|
||||
|
||||
// These could also be placed in our public types, but it would bloat them unnecessarily and we may want to change these in the future
|
||||
if (route.layout || route.leaf) {
|
||||
declarations.push(
|
||||
// If T extends the empty object, void is also allowed as a return type
|
||||
'type MaybeWithVoid<T> = {} extends T ? T | void : T;',
|
||||
|
||||
// Returns the key of the object whose values are required.
|
||||
'export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];',
|
||||
|
||||
// Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required.
|
||||
// If none, void is also allowed as a return type.
|
||||
'type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>',
|
||||
|
||||
// null & {} == null, we need to prevent that in some situations
|
||||
'type EnsureDefined<T> = T extends null | undefined ? {} : T;',
|
||||
|
||||
// Takes a union type and returns a union type where each type also has all properties
|
||||
// of all possible types (typed as undefined), making accessing them more ergonomic
|
||||
'type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;',
|
||||
|
||||
// Re-export `Snapshot` from @sveltejs/kit — in future we could use this to infer <T> from the return type of `snapshot.capture`
|
||||
'export type Snapshot<T = any> = Kit.Snapshot<T>;'
|
||||
);
|
||||
}
|
||||
|
||||
if (route.leaf) {
|
||||
let route_info = routes.get(route.leaf);
|
||||
if (!route_info) {
|
||||
// This should be defined, but belts and braces
|
||||
route_info = { route, proxies: { server: null, universal: null } };
|
||||
routes.set(route.leaf, route_info);
|
||||
}
|
||||
|
||||
const {
|
||||
declarations: d,
|
||||
exports: e,
|
||||
proxies
|
||||
} = process_node(route.leaf, outdir, true, route_info.proxies);
|
||||
|
||||
exports.push(...e);
|
||||
declarations.push(...d);
|
||||
|
||||
if (proxies.server) {
|
||||
route_info.proxies.server = proxies.server;
|
||||
if (proxies.server?.modified) to_delete.delete(proxies.server.file_name);
|
||||
}
|
||||
if (proxies.universal) {
|
||||
route_info.proxies.universal = proxies.universal;
|
||||
if (proxies.universal?.modified) to_delete.delete(proxies.universal.file_name);
|
||||
}
|
||||
|
||||
if (route.leaf.server) {
|
||||
exports.push(
|
||||
'export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>'
|
||||
);
|
||||
exports.push(
|
||||
'export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (route.layout) {
|
||||
let all_pages_have_load = true;
|
||||
/** @type {import('types').RouteParam[]} */
|
||||
const layout_params = [];
|
||||
const ids = ['RouteId'];
|
||||
|
||||
route.layout.child_pages?.forEach((page) => {
|
||||
const leaf = routes.get(page);
|
||||
if (leaf) {
|
||||
if (leaf.route.page) ids.push(`"${leaf.route.id}"`);
|
||||
|
||||
for (const param of leaf.route.params) {
|
||||
// skip if already added
|
||||
if (layout_params.some((p) => p.name === param.name)) continue;
|
||||
layout_params.push({ ...param, optional: true });
|
||||
}
|
||||
|
||||
ensureProxies(page, leaf.proxies);
|
||||
|
||||
if (
|
||||
// Be defensive - if a proxy doesn't exist (because it couldn't be created), assume a load function exists.
|
||||
// If we didn't and it's a false negative, the user could wrongfully get a type error on layouts.
|
||||
(leaf.proxies.server && !leaf.proxies.server.exports.includes('load')) ||
|
||||
(leaf.proxies.universal && !leaf.proxies.universal.exports.includes('load'))
|
||||
) {
|
||||
all_pages_have_load = false;
|
||||
}
|
||||
}
|
||||
if (!page.server && !page.universal) {
|
||||
all_pages_have_load = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (route.id === '/') {
|
||||
// root layout is used for fallback error page, where ID can be null
|
||||
ids.push('null');
|
||||
}
|
||||
|
||||
declarations.push(`type LayoutRouteId = ${ids.join(' | ')}`);
|
||||
|
||||
declarations.push(
|
||||
'type LayoutParams = RouteParams & ' + generate_params_type(layout_params, outdir, config)
|
||||
);
|
||||
|
||||
const {
|
||||
exports: e,
|
||||
declarations: d,
|
||||
proxies
|
||||
} = process_node(
|
||||
route.layout,
|
||||
outdir,
|
||||
false,
|
||||
{ server: null, universal: null },
|
||||
all_pages_have_load
|
||||
);
|
||||
|
||||
exports.push(...e);
|
||||
declarations.push(...d);
|
||||
|
||||
if (proxies.server?.modified) to_delete.delete(proxies.server.file_name);
|
||||
if (proxies.universal?.modified) to_delete.delete(proxies.universal.file_name);
|
||||
}
|
||||
|
||||
if (route.endpoint) {
|
||||
exports.push('export type RequestHandler = Kit.RequestHandler<RouteParams, RouteId>;');
|
||||
}
|
||||
|
||||
if (route.leaf?.server || route.layout?.server || route.endpoint) {
|
||||
exports.push('export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;');
|
||||
}
|
||||
|
||||
const output = [imports.join('\n'), declarations.join('\n'), exports.join('\n')]
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
|
||||
fs.writeFileSync(`${outdir}/$types.d.ts`, output);
|
||||
to_delete.delete('$types.d.ts');
|
||||
|
||||
for (const file of to_delete) {
|
||||
fs.unlinkSync(path.join(outdir, file));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').PageNode} node
|
||||
* @param {string} outdir
|
||||
* @param {boolean} is_page
|
||||
* @param {Proxies} proxies
|
||||
* @param {boolean} [all_pages_have_load]
|
||||
*/
|
||||
function process_node(node, outdir, is_page, proxies, all_pages_have_load = true) {
|
||||
const params = `${is_page ? 'Route' : 'Layout'}Params`;
|
||||
const prefix = is_page ? 'Page' : 'Layout';
|
||||
|
||||
const route_id = is_page ? 'RouteId' : 'LayoutRouteId';
|
||||
|
||||
/** @type {string[]} */
|
||||
const declarations = [];
|
||||
/** @type {string[]} */
|
||||
const exports = [];
|
||||
|
||||
/** @type {string} */
|
||||
let server_data;
|
||||
/** @type {string} */
|
||||
let data;
|
||||
|
||||
ensureProxies(node, proxies);
|
||||
|
||||
if (node.server) {
|
||||
const basename = path.basename(node.server);
|
||||
const proxy = proxies.server;
|
||||
if (proxy?.modified) {
|
||||
fs.writeFileSync(`${outdir}/proxy${basename}`, proxy.code);
|
||||
}
|
||||
|
||||
server_data = get_data_type(node.server, 'null', proxy, true);
|
||||
|
||||
const parent_type = `${prefix}ServerParentData`;
|
||||
|
||||
declarations.push(`type ${parent_type} = ${get_parent_type(node, 'LayoutServerData')};`);
|
||||
|
||||
// +page.js load present -> server can return all-optional data
|
||||
const output_data_shape =
|
||||
node.universal || (!is_page && all_pages_have_load)
|
||||
? 'Partial<App.PageData> & Record<string, any> | void'
|
||||
: `OutputDataShape<${parent_type}>`;
|
||||
exports.push(
|
||||
`export type ${prefix}ServerLoad<OutputData extends ${output_data_shape} = ${output_data_shape}> = Kit.ServerLoad<${params}, ${parent_type}, OutputData, ${route_id}>;`
|
||||
);
|
||||
|
||||
exports.push(`export type ${prefix}ServerLoadEvent = Parameters<${prefix}ServerLoad>[0];`);
|
||||
|
||||
if (is_page) {
|
||||
let type = 'unknown';
|
||||
if (proxy && proxy.exports.includes('actions')) {
|
||||
// If the file wasn't tweaked, we can use the return type of the original file.
|
||||
// The advantage is that type updates are reflected without saving.
|
||||
const from = proxy.modified
|
||||
? `./proxy${replace_ext_with_js(basename)}`
|
||||
: path_to_original(outdir, node.server);
|
||||
|
||||
exports.push(
|
||||
'type ExcludeActionFailure<T> = T extends Kit.ActionFailure<any> ? never : T extends void ? never : T;',
|
||||
'type ActionsSuccess<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: ExcludeActionFailure<Awaited<ReturnType<T[Key]>>>; }[keyof T];',
|
||||
'type ExtractActionFailure<T> = T extends Kit.ActionFailure<infer X> ? X extends void ? never : X : never;',
|
||||
'type ActionsFailure<T extends Record<string, (...args: any) => any>> = { [Key in keyof T]: Exclude<ExtractActionFailure<Awaited<ReturnType<T[Key]>>>, void>; }[keyof T];',
|
||||
`type ActionsExport = typeof import('${from}').actions`,
|
||||
'export type SubmitFunction = Kit.SubmitFunction<Expand<ActionsSuccess<ActionsExport>>, Expand<ActionsFailure<ActionsExport>>>'
|
||||
);
|
||||
|
||||
type = 'Expand<Kit.AwaitedActions<ActionsExport>> | null';
|
||||
}
|
||||
exports.push(`export type ActionData = ${type};`);
|
||||
}
|
||||
} else {
|
||||
server_data = 'null';
|
||||
}
|
||||
exports.push(`export type ${prefix}ServerData = ${server_data};`);
|
||||
|
||||
const parent_type = `${prefix}ParentData`;
|
||||
declarations.push(`type ${parent_type} = ${get_parent_type(node, 'LayoutData')};`);
|
||||
|
||||
if (node.universal) {
|
||||
const proxy = proxies.universal;
|
||||
if (proxy?.modified) {
|
||||
fs.writeFileSync(`${outdir}/proxy${path.basename(node.universal)}`, proxy.code);
|
||||
}
|
||||
|
||||
const type = get_data_type(
|
||||
node.universal,
|
||||
`${parent_type} & EnsureDefined<${prefix}ServerData>`,
|
||||
proxy
|
||||
);
|
||||
|
||||
data = `Expand<Omit<${parent_type}, keyof ${type}> & OptionalUnion<EnsureDefined<${type}>>>`;
|
||||
|
||||
const output_data_shape =
|
||||
!is_page && all_pages_have_load
|
||||
? 'Partial<App.PageData> & Record<string, any> | void'
|
||||
: `OutputDataShape<${parent_type}>`;
|
||||
exports.push(
|
||||
`export type ${prefix}Load<OutputData extends ${output_data_shape} = ${output_data_shape}> = Kit.Load<${params}, ${prefix}ServerData, ${parent_type}, OutputData, ${route_id}>;`
|
||||
);
|
||||
|
||||
exports.push(`export type ${prefix}LoadEvent = Parameters<${prefix}Load>[0];`);
|
||||
} else if (server_data === 'null') {
|
||||
data = `Expand<${parent_type}>`;
|
||||
} else {
|
||||
data = `Expand<Omit<${parent_type}, keyof ${prefix}ServerData> & EnsureDefined<${prefix}ServerData>>`;
|
||||
}
|
||||
|
||||
exports.push(`export type ${prefix}Data = ${data};`);
|
||||
|
||||
return { declarations, exports, proxies };
|
||||
|
||||
/**
|
||||
* @param {string} file_path
|
||||
* @param {string} fallback
|
||||
* @param {Proxy} proxy
|
||||
* @param {boolean} expand
|
||||
*/
|
||||
function get_data_type(file_path, fallback, proxy, expand = false) {
|
||||
if (proxy) {
|
||||
if (proxy.exports.includes('load')) {
|
||||
// If the file wasn't tweaked, we can use the return type of the original file.
|
||||
// The advantage is that type updates are reflected without saving.
|
||||
const from = proxy.modified
|
||||
? `./proxy${replace_ext_with_js(path.basename(file_path))}`
|
||||
: path_to_original(outdir, file_path);
|
||||
const type = `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').load>>>`;
|
||||
return expand ? `Expand<OptionalUnion<EnsureDefined<${type}>>>` : type;
|
||||
} else {
|
||||
return fallback;
|
||||
}
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function populates the proxies object, if necessary and not already done.
|
||||
* Proxies are used to tweak the code of a file before it's typechecked.
|
||||
* They are needed in two places - when generating the types for a page or layout.
|
||||
* To not do the same work twice, we generate the proxies once and pass them around.
|
||||
*
|
||||
* @param {import('types').PageNode} node
|
||||
* @param {Proxies} proxies
|
||||
*/
|
||||
function ensureProxies(node, proxies) {
|
||||
if (node.server && !proxies.server) {
|
||||
proxies.server = createProxy(node.server, true);
|
||||
}
|
||||
|
||||
if (node.universal && !proxies.universal) {
|
||||
proxies.universal = createProxy(node.universal, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file_path
|
||||
* @param {boolean} is_server
|
||||
* @returns {Proxy}
|
||||
*/
|
||||
function createProxy(file_path, is_server) {
|
||||
const proxy = tweak_types(fs.readFileSync(file_path, 'utf8'), is_server);
|
||||
if (proxy) {
|
||||
return {
|
||||
...proxy,
|
||||
file_name: `proxy${path.basename(file_path)}`
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent type string by recursively looking up the parent layout and accumulate them to one type.
|
||||
* @param {import('types').PageNode} node
|
||||
* @param {string} type
|
||||
*/
|
||||
function get_parent_type(node, type) {
|
||||
const parent_imports = [];
|
||||
|
||||
let parent = node.parent;
|
||||
|
||||
while (parent) {
|
||||
const d = node.depth - parent.depth;
|
||||
// unshift because we need it the other way round for the import string
|
||||
parent_imports.unshift(
|
||||
`${d === 0 ? '' : `import('${'../'.repeat(d)}${'$types.js'}').`}${type}`
|
||||
);
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
let parent_str = `EnsureDefined<${parent_imports[0] || '{}'}>`;
|
||||
for (let i = 1; i < parent_imports.length; i++) {
|
||||
// Omit is necessary because a parent could have a property with the same key which would
|
||||
// cause a type conflict. At runtime the child overwrites the parent property in this case,
|
||||
// so reflect that in the type definition.
|
||||
// EnsureDefined is necessary because {something: string} & null becomes null.
|
||||
// Output types of server loads can be null but when passed in through the `parent` parameter they are the empty object instead.
|
||||
parent_str = `Omit<${parent_str}, keyof ${parent_imports[i]}> & EnsureDefined<${parent_imports[i]}>`;
|
||||
}
|
||||
return parent_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} outdir
|
||||
* @param {string} file_path
|
||||
*/
|
||||
function path_to_original(outdir, file_path) {
|
||||
return posixify(path.relative(outdir, path.join(cwd, replace_ext_with_js(file_path))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file_path
|
||||
*/
|
||||
function replace_ext_with_js(file_path) {
|
||||
// Another extension than `.js` (or nothing, but that fails with node16 moduleResolution)
|
||||
// will result in TS failing to lookup the file
|
||||
const ext = path.extname(file_path);
|
||||
return file_path.slice(0, -ext.length) + '.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').RouteParam[]} params
|
||||
* @param {string} outdir
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
*/
|
||||
function generate_params_type(params, outdir, config) {
|
||||
/** @param {string} matcher */
|
||||
const path_to_matcher = (matcher) =>
|
||||
posixify(path.relative(outdir, path.join(config.kit.files.params, matcher)));
|
||||
|
||||
return `{ ${params
|
||||
.map(
|
||||
(param) =>
|
||||
`${param.name}${param.optional ? '?' : ''}: ${
|
||||
param.matcher
|
||||
? `MatcherParam<typeof import('${path_to_matcher(param.matcher)}').match>`
|
||||
: 'string'
|
||||
}`
|
||||
)
|
||||
.join('; ')} }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
* @param {boolean} is_server
|
||||
* @returns {Omit<NonNullable<Proxy>, 'file_name'> | null}
|
||||
*/
|
||||
export function tweak_types(content, is_server) {
|
||||
const names = new Set(is_server ? ['load', 'actions'] : ['load']);
|
||||
|
||||
try {
|
||||
let modified = false;
|
||||
|
||||
const ast = ts.createSourceFile(
|
||||
'filename.ts',
|
||||
content,
|
||||
ts.ScriptTarget.Latest,
|
||||
false,
|
||||
ts.ScriptKind.TS
|
||||
);
|
||||
|
||||
const code = new MagicString(content);
|
||||
|
||||
const exports = new Map();
|
||||
|
||||
ast.forEachChild((node) => {
|
||||
if (
|
||||
ts.isExportDeclaration(node) &&
|
||||
node.exportClause &&
|
||||
ts.isNamedExports(node.exportClause)
|
||||
) {
|
||||
node.exportClause.elements.forEach((element) => {
|
||||
const exported = element.name;
|
||||
if (names.has(element.name.text)) {
|
||||
const local = element.propertyName || element.name;
|
||||
exports.set(exported.text, local.text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
ts.canHaveModifiers(node) &&
|
||||
ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
||||
) {
|
||||
if (ts.isFunctionDeclaration(node) && node.name?.text && names.has(node.name?.text)) {
|
||||
exports.set(node.name.text, node.name.text);
|
||||
}
|
||||
|
||||
if (ts.isVariableStatement(node)) {
|
||||
node.declarationList.declarations.forEach((declaration) => {
|
||||
if (ts.isIdentifier(declaration.name) && names.has(declaration.name.text)) {
|
||||
exports.set(declaration.name.text, declaration.name.text);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {import('typescript').Node} node
|
||||
* @param {import('typescript').Node} value
|
||||
*/
|
||||
function replace_jsdoc_type_tags(node, value) {
|
||||
let _modified = false;
|
||||
// @ts-ignore
|
||||
if (node.jsDoc) {
|
||||
// @ts-ignore
|
||||
for (const comment of node.jsDoc) {
|
||||
for (const tag of comment.tags ?? []) {
|
||||
if (ts.isJSDocTypeTag(tag)) {
|
||||
const is_fn =
|
||||
ts.isFunctionDeclaration(value) ||
|
||||
ts.isFunctionExpression(value) ||
|
||||
ts.isArrowFunction(value);
|
||||
|
||||
if (is_fn && value.parameters?.length > 0) {
|
||||
const name = ts.isIdentifier(value.parameters[0].name)
|
||||
? value.parameters[0].name.text
|
||||
: 'event';
|
||||
code.overwrite(tag.tagName.pos, tag.tagName.end, 'param');
|
||||
code.prependRight(tag.typeExpression.pos + 1, 'Parameters<');
|
||||
code.appendLeft(tag.typeExpression.end - 1, '>[0]');
|
||||
code.appendLeft(tag.typeExpression.end, ` ${name}`);
|
||||
} else {
|
||||
code.overwrite(tag.pos, tag.end, '');
|
||||
}
|
||||
_modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
modified = modified || _modified;
|
||||
return _modified;
|
||||
}
|
||||
|
||||
ast.forEachChild((node) => {
|
||||
if (ts.isFunctionDeclaration(node) && node.name?.text && node.name?.text === 'load') {
|
||||
// remove JSDoc comment above `export function load ...`
|
||||
replace_jsdoc_type_tags(node, node);
|
||||
}
|
||||
|
||||
if (ts.isVariableStatement(node)) {
|
||||
// remove JSDoc comment above `export const load = ...`
|
||||
if (
|
||||
ts.isIdentifier(node.declarationList.declarations[0].name) &&
|
||||
names.has(node.declarationList.declarations[0].name.text) &&
|
||||
node.declarationList.declarations[0].initializer
|
||||
) {
|
||||
replace_jsdoc_type_tags(node, node.declarationList.declarations[0].initializer);
|
||||
}
|
||||
|
||||
for (const declaration of node.declarationList.declarations) {
|
||||
if (
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.name.text === 'load' &&
|
||||
declaration.initializer
|
||||
) {
|
||||
// edge case — remove JSDoc comment above individual export
|
||||
replace_jsdoc_type_tags(declaration, declaration.initializer);
|
||||
|
||||
// remove type from `export const load: Load ...`
|
||||
if (declaration.type) {
|
||||
let a = declaration.type.pos;
|
||||
const b = declaration.type.end;
|
||||
while (/\s/.test(content[a])) a += 1;
|
||||
|
||||
const type = content.slice(a, b);
|
||||
code.remove(declaration.name.end, declaration.type.end);
|
||||
|
||||
const rhs = declaration.initializer;
|
||||
|
||||
if (
|
||||
rhs &&
|
||||
(ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) &&
|
||||
rhs.parameters.length
|
||||
) {
|
||||
const arg = rhs.parameters[0];
|
||||
const add_parens = content[arg.pos - 1] !== '(';
|
||||
|
||||
if (add_parens) code.prependRight(arg.pos, '(');
|
||||
|
||||
if (arg && !arg.type) {
|
||||
code.appendLeft(
|
||||
arg.name.end,
|
||||
`: Parameters<${type}>[0]` + (add_parens ? ')' : '')
|
||||
);
|
||||
} else {
|
||||
// prevent "type X is imported but not used" (isn't silenced by @ts-nocheck) when svelte-check runs
|
||||
code.append(`;null as any as ${type};`);
|
||||
}
|
||||
} else {
|
||||
// prevent "type X is imported but not used" (isn't silenced by @ts-nocheck) when svelte-check runs
|
||||
code.append(`;null as any as ${type};`);
|
||||
}
|
||||
|
||||
modified = true;
|
||||
}
|
||||
} else if (
|
||||
is_server &&
|
||||
ts.isIdentifier(declaration.name) &&
|
||||
declaration.name?.text === 'actions' &&
|
||||
declaration.initializer
|
||||
) {
|
||||
// remove JSDoc comment from `export const actions = ..`
|
||||
const removed = replace_jsdoc_type_tags(node, declaration.initializer);
|
||||
// ... and move type to each individual action
|
||||
if (removed) {
|
||||
const rhs = declaration.initializer;
|
||||
if (ts.isObjectLiteralExpression(rhs)) {
|
||||
for (const prop of rhs.properties) {
|
||||
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
||||
const rhs = prop.initializer;
|
||||
const replaced = replace_jsdoc_type_tags(prop, rhs);
|
||||
if (
|
||||
!replaced &&
|
||||
rhs &&
|
||||
(ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) &&
|
||||
rhs.parameters?.[0]
|
||||
) {
|
||||
const name = ts.isIdentifier(rhs.parameters[0].name)
|
||||
? rhs.parameters[0].name.text
|
||||
: 'event';
|
||||
code.prependRight(
|
||||
rhs.pos,
|
||||
`/** @param {import('./$types').RequestEvent} ${name} */ `
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove type from `export const actions: Actions ...`
|
||||
if (declaration.type) {
|
||||
let a = declaration.type.pos;
|
||||
const b = declaration.type.end;
|
||||
while (/\s/.test(content[a])) a += 1;
|
||||
|
||||
const type = content.slice(a, b);
|
||||
code.remove(declaration.name.end, declaration.type.end);
|
||||
code.append(`;null as any as ${type};`);
|
||||
modified = true;
|
||||
|
||||
// ... and move type to each individual action
|
||||
const rhs = declaration.initializer;
|
||||
if (ts.isObjectLiteralExpression(rhs)) {
|
||||
for (const prop of rhs.properties) {
|
||||
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
||||
const rhs = prop.initializer;
|
||||
|
||||
if (
|
||||
rhs &&
|
||||
(ts.isArrowFunction(rhs) || ts.isFunctionExpression(rhs)) &&
|
||||
rhs.parameters.length
|
||||
) {
|
||||
const arg = rhs.parameters[0];
|
||||
const add_parens = content[arg.pos - 1] !== '(';
|
||||
|
||||
if (add_parens) code.prependRight(arg.pos, '(');
|
||||
|
||||
if (arg && !arg.type) {
|
||||
code.appendLeft(
|
||||
arg.name.end,
|
||||
": import('./$types').RequestEvent" + (add_parens ? ')' : '')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (modified) {
|
||||
// Ignore all type errors so they don't show up twice when svelte-check runs
|
||||
// Account for possible @ts-check which would overwrite @ts-nocheck
|
||||
if (code.original.startsWith('// @ts-check')) {
|
||||
code.prependLeft('// @ts-check'.length, '\n// @ts-nocheck\n');
|
||||
} else {
|
||||
code.prepend('// @ts-nocheck\n');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
modified,
|
||||
code: code.toString(),
|
||||
exports: Array.from(exports.keys())
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
86
node_modules/@sveltejs/kit/src/core/utils.js
generated
vendored
Normal file
86
node_modules/@sveltejs/kit/src/core/utils.js
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import colors from 'kleur';
|
||||
import { posixify, to_fs } from '../utils/filesystem.js';
|
||||
|
||||
/**
|
||||
* Resolved path of the `runtime` directory
|
||||
*
|
||||
* TODO Windows issue:
|
||||
* Vite or sth else somehow sets the driver letter inconsistently to lower or upper case depending on the run environment.
|
||||
* In playwright debug mode run through VS Code this a root-to-lowercase conversion is needed in order for the tests to run.
|
||||
* If we do this conversion in other cases it has the opposite effect though and fails.
|
||||
*/
|
||||
export const runtime_directory = posixify(fileURLToPath(new URL('../runtime', import.meta.url)));
|
||||
|
||||
/**
|
||||
* This allows us to import SvelteKit internals that aren't exposed via `pkg.exports` in a
|
||||
* way that works whether `@sveltejs/kit` is installed inside the project's `node_modules`
|
||||
* or in a workspace root
|
||||
*/
|
||||
export const runtime_base = runtime_directory.startsWith(process.cwd())
|
||||
? `/${path.relative('.', runtime_directory)}`
|
||||
: to_fs(runtime_directory);
|
||||
|
||||
function noop() {}
|
||||
|
||||
/** @param {{ verbose: boolean }} opts */
|
||||
export function logger({ verbose }) {
|
||||
/** @type {import('types').Logger} */
|
||||
const log = (msg) => console.log(msg.replace(/^/gm, ' '));
|
||||
|
||||
/** @param {string} msg */
|
||||
const err = (msg) => console.error(msg.replace(/^/gm, ' '));
|
||||
|
||||
log.success = (msg) => log(colors.green(`✔ ${msg}`));
|
||||
log.error = (msg) => err(colors.bold().red(msg));
|
||||
log.warn = (msg) => log(colors.bold().yellow(msg));
|
||||
|
||||
log.minor = verbose ? (msg) => log(colors.grey(msg)) : noop;
|
||||
log.info = verbose ? log : noop;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
/** @param {import('types').ManifestData} manifest_data */
|
||||
export function get_mime_lookup(manifest_data) {
|
||||
/** @type {Record<string, string>} */
|
||||
const mime = {};
|
||||
|
||||
manifest_data.assets.forEach((asset) => {
|
||||
if (asset.type) {
|
||||
const ext = path.extname(asset.file);
|
||||
mime[ext] = asset.type;
|
||||
}
|
||||
});
|
||||
|
||||
return mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @param {(file: string) => boolean} [filter]
|
||||
*/
|
||||
export function list_files(dir, filter) {
|
||||
/** @type {string[]} */
|
||||
const files = [];
|
||||
|
||||
/** @param {string} current */
|
||||
function walk(current) {
|
||||
for (const file of fs.readdirSync(path.resolve(dir, current))) {
|
||||
const child = path.posix.join(current, file);
|
||||
if (fs.statSync(path.resolve(dir, child)).isDirectory()) {
|
||||
walk(child);
|
||||
} else {
|
||||
if (!filter || filter(child)) {
|
||||
files.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(dir)) walk('');
|
||||
|
||||
return files;
|
||||
}
|
1
node_modules/@sveltejs/kit/src/exports/hooks/index.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/exports/hooks/index.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { sequence } from './sequence.js';
|
120
node_modules/@sveltejs/kit/src/exports/hooks/sequence.js
generated
vendored
Normal file
120
node_modules/@sveltejs/kit/src/exports/hooks/sequence.js
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* A helper function for sequencing multiple `handle` calls in a middleware-like manner.
|
||||
* The behavior for the `handle` options is as follows:
|
||||
* - `transformPageChunk` is applied in reverse order and merged
|
||||
* - `preload` is applied in forward order, the first option "wins" and no `preload` options after it are called
|
||||
* - `filterSerializedResponseHeaders` behaves the same as `preload`
|
||||
*
|
||||
* ```js
|
||||
* /// file: src/hooks.server.js
|
||||
* import { sequence } from '@sveltejs/kit/hooks';
|
||||
*
|
||||
* /// type: import('@sveltejs/kit').Handle
|
||||
* async function first({ event, resolve }) {
|
||||
* console.log('first pre-processing');
|
||||
* const result = await resolve(event, {
|
||||
* transformPageChunk: ({ html }) => {
|
||||
* // transforms are applied in reverse order
|
||||
* console.log('first transform');
|
||||
* return html;
|
||||
* },
|
||||
* preload: () => {
|
||||
* // this one wins as it's the first defined in the chain
|
||||
* console.log('first preload');
|
||||
* }
|
||||
* });
|
||||
* console.log('first post-processing');
|
||||
* return result;
|
||||
* }
|
||||
*
|
||||
* /// type: import('@sveltejs/kit').Handle
|
||||
* async function second({ event, resolve }) {
|
||||
* console.log('second pre-processing');
|
||||
* const result = await resolve(event, {
|
||||
* transformPageChunk: ({ html }) => {
|
||||
* console.log('second transform');
|
||||
* return html;
|
||||
* },
|
||||
* preload: () => {
|
||||
* console.log('second preload');
|
||||
* },
|
||||
* filterSerializedResponseHeaders: () => {
|
||||
* // this one wins as it's the first defined in the chain
|
||||
* console.log('second filterSerializedResponseHeaders');
|
||||
* }
|
||||
* });
|
||||
* console.log('second post-processing');
|
||||
* return result;
|
||||
* }
|
||||
*
|
||||
* export const handle = sequence(first, second);
|
||||
* ```
|
||||
*
|
||||
* The example above would print:
|
||||
*
|
||||
* ```
|
||||
* first pre-processing
|
||||
* first preload
|
||||
* second pre-processing
|
||||
* second filterSerializedResponseHeaders
|
||||
* second transform
|
||||
* first transform
|
||||
* second post-processing
|
||||
* first post-processing
|
||||
* ```
|
||||
*
|
||||
* @param {...import('@sveltejs/kit').Handle} handlers The chain of `handle` functions
|
||||
* @returns {import('@sveltejs/kit').Handle}
|
||||
*/
|
||||
export function sequence(...handlers) {
|
||||
const length = handlers.length;
|
||||
if (!length) return ({ event, resolve }) => resolve(event);
|
||||
|
||||
return ({ event, resolve }) => {
|
||||
return apply_handle(0, event, {});
|
||||
|
||||
/**
|
||||
* @param {number} i
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('@sveltejs/kit').ResolveOptions | undefined} parent_options
|
||||
* @returns {import('types').MaybePromise<Response>}
|
||||
*/
|
||||
function apply_handle(i, event, parent_options) {
|
||||
const handle = handlers[i];
|
||||
|
||||
return handle({
|
||||
event,
|
||||
resolve: (event, options) => {
|
||||
/** @type {import('@sveltejs/kit').ResolveOptions['transformPageChunk']} */
|
||||
const transformPageChunk = async ({ html, done }) => {
|
||||
if (options?.transformPageChunk) {
|
||||
html = (await options.transformPageChunk({ html, done })) ?? '';
|
||||
}
|
||||
|
||||
if (parent_options?.transformPageChunk) {
|
||||
html = (await parent_options.transformPageChunk({ html, done })) ?? '';
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
/** @type {import('@sveltejs/kit').ResolveOptions['filterSerializedResponseHeaders']} */
|
||||
const filterSerializedResponseHeaders =
|
||||
parent_options?.filterSerializedResponseHeaders ??
|
||||
options?.filterSerializedResponseHeaders;
|
||||
|
||||
/** @type {import('@sveltejs/kit').ResolveOptions['preload']} */
|
||||
const preload = parent_options?.preload ?? options?.preload;
|
||||
|
||||
return i < length - 1
|
||||
? apply_handle(i + 1, event, {
|
||||
transformPageChunk,
|
||||
filterSerializedResponseHeaders,
|
||||
preload
|
||||
})
|
||||
: resolve(event, { transformPageChunk, filterSerializedResponseHeaders, preload });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
158
node_modules/@sveltejs/kit/src/exports/index.js
generated
vendored
Normal file
158
node_modules/@sveltejs/kit/src/exports/index.js
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
import { HttpError, Redirect, ActionFailure } from '../runtime/control.js';
|
||||
import { BROWSER, DEV } from 'esm-env';
|
||||
import { get_route_segments } from '../utils/routing.js';
|
||||
|
||||
export { VERSION } from '../version.js';
|
||||
|
||||
/**
|
||||
* @overload
|
||||
* @param {number} status
|
||||
* @param {App.Error} body
|
||||
* @return {HttpError}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @overload
|
||||
* @param {number} status
|
||||
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} [body]
|
||||
* @return {HttpError}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an `HttpError` object with an HTTP status code and an optional message.
|
||||
* This object, if thrown during request handling, will cause SvelteKit to
|
||||
* return an error response without invoking `handleError`.
|
||||
* Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.
|
||||
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
|
||||
* @param {{ message: string } extends App.Error ? App.Error | string | undefined : never} body An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
|
||||
*/
|
||||
export function error(status, body) {
|
||||
if ((!BROWSER || DEV) && (isNaN(status) || status < 400 || status > 599)) {
|
||||
throw new Error(`HTTP error status codes must be between 400 and 599 — ${status} is invalid`);
|
||||
}
|
||||
|
||||
return new HttpError(status, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Redirect` object. If thrown during request handling, SvelteKit will return a redirect response.
|
||||
* Make sure you're not catching the thrown redirect, which would prevent SvelteKit from handling it.
|
||||
* @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages). Must be in the range 300-308.
|
||||
* @param {string | URL} location The location to redirect to.
|
||||
*/
|
||||
export function redirect(status, location) {
|
||||
if ((!BROWSER || DEV) && (isNaN(status) || status < 300 || status > 308)) {
|
||||
throw new Error('Invalid status code');
|
||||
}
|
||||
|
||||
return new Redirect(status, location.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSON `Response` object from the supplied data.
|
||||
* @param {any} data The value that will be serialized as JSON.
|
||||
* @param {ResponseInit} [init] Options such as `status` and `headers` that will be added to the response. `Content-Type: application/json` and `Content-Length` headers will be added automatically.
|
||||
*/
|
||||
export function json(data, init) {
|
||||
// TODO deprecate this in favour of `Response.json` when it's
|
||||
// more widely supported
|
||||
const body = JSON.stringify(data);
|
||||
|
||||
// we can't just do `text(JSON.stringify(data), init)` because
|
||||
// it will set a default `content-type` header. duplicated code
|
||||
// means less duplicated work
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!headers.has('content-length')) {
|
||||
headers.set('content-length', encoder.encode(body).byteLength.toString());
|
||||
}
|
||||
|
||||
if (!headers.has('content-type')) {
|
||||
headers.set('content-type', 'application/json');
|
||||
}
|
||||
|
||||
return new Response(body, {
|
||||
...init,
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* Create a `Response` object from the supplied body.
|
||||
* @param {string} body The value that will be used as-is.
|
||||
* @param {ResponseInit} [init] Options such as `status` and `headers` that will be added to the response. A `Content-Length` header will be added automatically.
|
||||
*/
|
||||
export function text(body, init) {
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!headers.has('content-length')) {
|
||||
const encoded = encoder.encode(body);
|
||||
headers.set('content-length', encoded.byteLength.toString());
|
||||
return new Response(encoded, {
|
||||
...init,
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(body, {
|
||||
...init,
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an `ActionFailure` object.
|
||||
* @template {Record<string, unknown> | undefined} [T=undefined]
|
||||
* @param {number} status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
|
||||
* @param {T} [data] Data associated with the failure (e.g. validation errors)
|
||||
* @returns {ActionFailure<T>}
|
||||
*/
|
||||
export function fail(status, data) {
|
||||
return new ActionFailure(status, data);
|
||||
}
|
||||
|
||||
const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
|
||||
|
||||
/**
|
||||
* Populate a route ID with params to resolve a pathname.
|
||||
* @example
|
||||
* ```js
|
||||
* resolvePath(
|
||||
* `/blog/[slug]/[...somethingElse]`,
|
||||
* {
|
||||
* slug: 'hello-world',
|
||||
* somethingElse: 'something/else'
|
||||
* }
|
||||
* ); // `/blog/hello-world/something/else`
|
||||
* ```
|
||||
* @param {string} id
|
||||
* @param {Record<string, string | undefined>} params
|
||||
* @returns {string}
|
||||
*/
|
||||
export function resolvePath(id, params) {
|
||||
const segments = get_route_segments(id);
|
||||
return (
|
||||
'/' +
|
||||
segments
|
||||
.map((segment) =>
|
||||
segment.replace(basic_param_pattern, (_, optional, rest, name) => {
|
||||
const param_value = params[name];
|
||||
|
||||
// This is nested so TS correctly narrows the type
|
||||
if (!param_value) {
|
||||
if (optional) return '';
|
||||
if (rest && param_value !== undefined) return '';
|
||||
throw new Error(`Missing parameter '${name}' in route ${id}`);
|
||||
}
|
||||
|
||||
if (param_value.startsWith('/') || param_value.endsWith('/'))
|
||||
throw new Error(
|
||||
`Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
|
||||
);
|
||||
return param_value;
|
||||
})
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
);
|
||||
}
|
190
node_modules/@sveltejs/kit/src/exports/node/index.js
generated
vendored
Normal file
190
node_modules/@sveltejs/kit/src/exports/node/index.js
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
import * as set_cookie_parser from 'set-cookie-parser';
|
||||
import { error } from '../index.js';
|
||||
|
||||
/**
|
||||
* @param {import('http').IncomingMessage} req
|
||||
* @param {number} [body_size_limit]
|
||||
*/
|
||||
function get_raw_body(req, body_size_limit) {
|
||||
const h = req.headers;
|
||||
|
||||
if (!h['content-type']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content_length = Number(h['content-length']);
|
||||
|
||||
// check if no request body
|
||||
if (
|
||||
(req.httpVersionMajor === 1 && isNaN(content_length) && h['transfer-encoding'] == null) ||
|
||||
content_length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let length = content_length;
|
||||
|
||||
if (body_size_limit) {
|
||||
if (!length) {
|
||||
length = body_size_limit;
|
||||
} else if (length > body_size_limit) {
|
||||
throw error(
|
||||
413,
|
||||
`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (req.destroyed) {
|
||||
const readable = new ReadableStream();
|
||||
readable.cancel();
|
||||
return readable;
|
||||
}
|
||||
|
||||
let size = 0;
|
||||
let cancelled = false;
|
||||
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
req.on('error', (error) => {
|
||||
cancelled = true;
|
||||
controller.error(error);
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
if (cancelled) return;
|
||||
controller.close();
|
||||
});
|
||||
|
||||
req.on('data', (chunk) => {
|
||||
if (cancelled) return;
|
||||
|
||||
size += chunk.length;
|
||||
if (size > length) {
|
||||
cancelled = true;
|
||||
controller.error(
|
||||
error(
|
||||
413,
|
||||
`request body size exceeded ${
|
||||
content_length ? "'content-length'" : 'BODY_SIZE_LIMIT'
|
||||
} of ${length}`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
controller.enqueue(chunk);
|
||||
|
||||
if (controller.desiredSize === null || controller.desiredSize <= 0) {
|
||||
req.pause();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
pull() {
|
||||
req.resume();
|
||||
},
|
||||
|
||||
cancel(reason) {
|
||||
cancelled = true;
|
||||
req.destroy(reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* request: import('http').IncomingMessage;
|
||||
* base: string;
|
||||
* bodySizeLimit?: number;
|
||||
* }} options
|
||||
* @returns {Promise<Request>}
|
||||
*/
|
||||
export async function getRequest({ request, base, bodySizeLimit }) {
|
||||
return new Request(base + request.url, {
|
||||
// @ts-expect-error
|
||||
duplex: 'half',
|
||||
method: request.method,
|
||||
headers: /** @type {Record<string, string>} */ (request.headers),
|
||||
body: get_raw_body(request, bodySizeLimit)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('http').ServerResponse} res
|
||||
* @param {Response} response
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function setResponse(res, response) {
|
||||
for (const [key, value] of response.headers) {
|
||||
try {
|
||||
res.setHeader(
|
||||
key,
|
||||
key === 'set-cookie'
|
||||
? set_cookie_parser.splitCookiesString(
|
||||
// This is absurd but necessary, TODO: investigate why
|
||||
/** @type {string}*/ (response.headers.get(key))
|
||||
)
|
||||
: value
|
||||
);
|
||||
} catch (error) {
|
||||
res.getHeaderNames().forEach((name) => res.removeHeader(name));
|
||||
res.writeHead(500).end(String(error));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead(response.status);
|
||||
|
||||
if (!response.body) {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.body.locked) {
|
||||
res.end(
|
||||
'Fatal error: Response body is locked. ' +
|
||||
"This can happen when the response was already read (for example through 'response.json()' or 'response.text()')."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
|
||||
if (res.destroyed) {
|
||||
reader.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
const cancel = (/** @type {Error|undefined} */ error) => {
|
||||
res.off('close', cancel);
|
||||
res.off('error', cancel);
|
||||
|
||||
// If the reader has already been interrupted with an error earlier,
|
||||
// then it will appear here, it is useless, but it needs to be catch.
|
||||
reader.cancel(error).catch(() => {});
|
||||
if (error) res.destroy(error);
|
||||
};
|
||||
|
||||
res.on('close', cancel);
|
||||
res.on('error', cancel);
|
||||
|
||||
next();
|
||||
async function next() {
|
||||
try {
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) break;
|
||||
|
||||
if (!res.write(value)) {
|
||||
res.once('drain', next);
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
} catch (error) {
|
||||
cancel(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
}
|
||||
}
|
59
node_modules/@sveltejs/kit/src/exports/node/polyfills.js
generated
vendored
Normal file
59
node_modules/@sveltejs/kit/src/exports/node/polyfills.js
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
import { ReadableStream, TransformStream, WritableStream } from 'node:stream/web';
|
||||
import buffer from 'node:buffer';
|
||||
import { webcrypto as crypto } from 'node:crypto';
|
||||
import { fetch, Response, Request, Headers, FormData, File as UndiciFile } from 'undici';
|
||||
|
||||
// `buffer.File` was added in Node 18.13.0 while the `File` global was added in Node 20.0.0
|
||||
const File = /** @type {import('node:buffer') & { File?: File}} */ (buffer).File ?? UndiciFile;
|
||||
|
||||
/** @type {Record<string, any>} */
|
||||
const globals_post_node_18_11 = {
|
||||
crypto,
|
||||
File
|
||||
};
|
||||
|
||||
/** @type {Record<string, any>} */
|
||||
// TODO: remove this once we only support Node 18.11+ (the version multipart/form-data was added)
|
||||
const globals_pre_node_18_11 = {
|
||||
crypto,
|
||||
fetch,
|
||||
Response,
|
||||
Request,
|
||||
Headers,
|
||||
ReadableStream,
|
||||
TransformStream,
|
||||
WritableStream,
|
||||
FormData,
|
||||
File
|
||||
};
|
||||
|
||||
// exported for dev/preview and node environments
|
||||
/**
|
||||
* Make various web APIs available as globals:
|
||||
* - `crypto`
|
||||
* - `fetch` (only in node < 18.11)
|
||||
* - `Headers` (only in node < 18.11)
|
||||
* - `Request` (only in node < 18.11)
|
||||
* - `Response` (only in node < 18.11)
|
||||
*/
|
||||
export function installPolyfills() {
|
||||
// Be defensive (we don't know in which environments this is called) and always apply if something goes wrong
|
||||
let globals = globals_pre_node_18_11;
|
||||
try {
|
||||
const version = process.versions.node.split('.').map((n) => parseInt(n, 10));
|
||||
if ((version[0] === 18 && version[1] >= 11) || version[0] > 18) {
|
||||
globals = globals_post_node_18_11;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
for (const name in globals) {
|
||||
Object.defineProperty(globalThis, name, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: globals[name]
|
||||
});
|
||||
}
|
||||
}
|
1299
node_modules/@sveltejs/kit/src/exports/public.d.ts
generated
vendored
Normal file
1299
node_modules/@sveltejs/kit/src/exports/public.d.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
111
node_modules/@sveltejs/kit/src/exports/vite/build/build_server.js
generated
vendored
Normal file
111
node_modules/@sveltejs/kit/src/exports/vite/build/build_server.js
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
import fs from 'node:fs';
|
||||
import { mkdirp } from '../../../utils/filesystem.js';
|
||||
import { find_deps, resolve_symlinks } from './utils.js';
|
||||
import { s } from '../../../utils/misc.js';
|
||||
import { normalizePath } from 'vite';
|
||||
|
||||
/**
|
||||
* @param {string} out
|
||||
* @param {import('types').ValidatedKitConfig} kit
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
* @param {import('vite').Manifest} server_manifest
|
||||
* @param {import('vite').Manifest | null} client_manifest
|
||||
* @param {import('vite').Rollup.OutputAsset[] | null} css
|
||||
*/
|
||||
export function build_server_nodes(out, kit, manifest_data, server_manifest, client_manifest, css) {
|
||||
mkdirp(`${out}/server/nodes`);
|
||||
mkdirp(`${out}/server/stylesheets`);
|
||||
|
||||
const stylesheet_lookup = new Map();
|
||||
|
||||
if (css) {
|
||||
css.forEach((asset) => {
|
||||
if (asset.source.length < kit.inlineStyleThreshold) {
|
||||
const index = stylesheet_lookup.size;
|
||||
const file = `${out}/server/stylesheets/${index}.js`;
|
||||
|
||||
fs.writeFileSync(file, `// ${asset.fileName}\nexport default ${s(asset.source)};`);
|
||||
stylesheet_lookup.set(asset.fileName, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
manifest_data.nodes.forEach((node, i) => {
|
||||
/** @type {string[]} */
|
||||
const imports = [];
|
||||
|
||||
// String representation of
|
||||
/** @type {import('types').SSRNode} */
|
||||
/** @type {string[]} */
|
||||
const exports = [`export const index = ${i};`];
|
||||
|
||||
/** @type {string[]} */
|
||||
const imported = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
const stylesheets = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
const fonts = [];
|
||||
|
||||
if (node.component && client_manifest) {
|
||||
exports.push(
|
||||
'let component_cache;',
|
||||
`export const component = async () => component_cache ??= (await import('../${
|
||||
resolve_symlinks(server_manifest, node.component).chunk.file
|
||||
}')).default;`
|
||||
);
|
||||
}
|
||||
|
||||
if (node.universal) {
|
||||
imports.push(`import * as universal from '../${server_manifest[node.universal].file}';`);
|
||||
exports.push('export { universal };');
|
||||
exports.push(`export const universal_id = ${s(node.universal)};`);
|
||||
}
|
||||
|
||||
if (node.server) {
|
||||
imports.push(`import * as server from '../${server_manifest[node.server].file}';`);
|
||||
exports.push('export { server };');
|
||||
exports.push(`export const server_id = ${s(node.server)};`);
|
||||
}
|
||||
|
||||
if (client_manifest && (node.universal || node.component)) {
|
||||
const entry = find_deps(
|
||||
client_manifest,
|
||||
`${normalizePath(kit.outDir)}/generated/client-optimized/nodes/${i}.js`,
|
||||
true
|
||||
);
|
||||
|
||||
imported.push(...entry.imports);
|
||||
stylesheets.push(...entry.stylesheets);
|
||||
fonts.push(...entry.fonts);
|
||||
}
|
||||
|
||||
exports.push(
|
||||
`export const imports = ${s(imported)};`,
|
||||
`export const stylesheets = ${s(stylesheets)};`,
|
||||
`export const fonts = ${s(fonts)};`
|
||||
);
|
||||
|
||||
/** @type {string[]} */
|
||||
const styles = [];
|
||||
|
||||
stylesheets.forEach((file) => {
|
||||
if (stylesheet_lookup.has(file)) {
|
||||
const index = stylesheet_lookup.get(file);
|
||||
const name = `stylesheet_${index}`;
|
||||
imports.push(`import ${name} from '../stylesheets/${index}.js';`);
|
||||
styles.push(`\t${s(file)}: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (styles.length > 0) {
|
||||
exports.push(`export const inline_styles = () => ({\n${styles.join(',\n')}\n});`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
`${out}/server/nodes/${i}.js`,
|
||||
`${imports.join('\n')}\n\n${exports.join('\n')}\n`
|
||||
);
|
||||
});
|
||||
}
|
89
node_modules/@sveltejs/kit/src/exports/vite/build/build_service_worker.js
generated
vendored
Normal file
89
node_modules/@sveltejs/kit/src/exports/vite/build/build_service_worker.js
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
import fs from 'node:fs';
|
||||
import * as vite from 'vite';
|
||||
import { dedent } from '../../../core/sync/utils.js';
|
||||
import { s } from '../../../utils/misc.js';
|
||||
import { get_config_aliases } from '../utils.js';
|
||||
import { assets_base } from './utils.js';
|
||||
|
||||
/**
|
||||
* @param {string} out
|
||||
* @param {import('types').ValidatedKitConfig} kit
|
||||
* @param {import('vite').ResolvedConfig} vite_config
|
||||
* @param {import('types').ManifestData} manifest_data
|
||||
* @param {string} service_worker_entry_file
|
||||
* @param {import('types').Prerendered} prerendered
|
||||
* @param {import('vite').Manifest} client_manifest
|
||||
*/
|
||||
export async function build_service_worker(
|
||||
out,
|
||||
kit,
|
||||
vite_config,
|
||||
manifest_data,
|
||||
service_worker_entry_file,
|
||||
prerendered,
|
||||
client_manifest
|
||||
) {
|
||||
const build = new Set();
|
||||
for (const key in client_manifest) {
|
||||
const { file, css = [], assets = [] } = client_manifest[key];
|
||||
build.add(file);
|
||||
css.forEach((file) => build.add(file));
|
||||
assets.forEach((file) => build.add(file));
|
||||
}
|
||||
|
||||
const service_worker = `${kit.outDir}/generated/service-worker.js`;
|
||||
|
||||
// in a service worker, `location` is the location of the service worker itself,
|
||||
// which is guaranteed to be `<base>/service-worker.js`
|
||||
const base = "location.pathname.split('/').slice(0, -1).join('/')";
|
||||
|
||||
fs.writeFileSync(
|
||||
service_worker,
|
||||
dedent`
|
||||
export const base = /*@__PURE__*/ ${base};
|
||||
|
||||
export const build = [
|
||||
${Array.from(build)
|
||||
.map((file) => `base + ${s(`/${file}`)}`)
|
||||
.join(',\n')}
|
||||
];
|
||||
|
||||
export const files = [
|
||||
${manifest_data.assets
|
||||
.filter((asset) => kit.serviceWorker.files(asset.file))
|
||||
.map((asset) => `base + ${s(`/${asset.file}`)}`)
|
||||
.join(',\n')}
|
||||
];
|
||||
|
||||
export const prerendered = [
|
||||
${prerendered.paths.map((path) => `base + ${s(path.replace(kit.paths.base, ''))}`).join(',\n')}
|
||||
];
|
||||
|
||||
export const version = ${s(kit.version.name)};
|
||||
`
|
||||
);
|
||||
|
||||
await vite.build({
|
||||
base: assets_base(kit),
|
||||
build: {
|
||||
lib: {
|
||||
entry: /** @type {string} */ (service_worker_entry_file),
|
||||
name: 'app',
|
||||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: 'service-worker.js'
|
||||
}
|
||||
},
|
||||
outDir: `${out}/client`,
|
||||
emptyOutDir: false
|
||||
},
|
||||
configFile: false,
|
||||
define: vite_config.define,
|
||||
publicDir: false,
|
||||
resolve: {
|
||||
alias: [...get_config_aliases(kit), { find: '$service-worker', replacement: service_worker }]
|
||||
}
|
||||
});
|
||||
}
|
92
node_modules/@sveltejs/kit/src/exports/vite/build/utils.js
generated
vendored
Normal file
92
node_modules/@sveltejs/kit/src/exports/vite/build/utils.js
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { normalizePath } from 'vite';
|
||||
|
||||
/**
|
||||
* Adds transitive JS and CSS dependencies to the js and css inputs.
|
||||
* @param {import('vite').Manifest} manifest
|
||||
* @param {string} entry
|
||||
* @param {boolean} add_dynamic_css
|
||||
* @returns {import('types').AssetDependencies}
|
||||
*/
|
||||
export function find_deps(manifest, entry, add_dynamic_css) {
|
||||
/** @type {Set<string>} */
|
||||
const seen = new Set();
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const imports = new Set();
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const stylesheets = new Set();
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const fonts = new Set();
|
||||
|
||||
/**
|
||||
* @param {string} current
|
||||
* @param {boolean} add_js
|
||||
*/
|
||||
function traverse(current, add_js) {
|
||||
if (seen.has(current)) return;
|
||||
seen.add(current);
|
||||
|
||||
const { chunk } = resolve_symlinks(manifest, current);
|
||||
|
||||
if (add_js) imports.add(chunk.file);
|
||||
|
||||
if (chunk.assets) {
|
||||
for (const asset of chunk.assets) {
|
||||
if (/\.(woff2?|ttf|otf)$/.test(asset)) {
|
||||
fonts.add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.css) {
|
||||
chunk.css.forEach((file) => stylesheets.add(file));
|
||||
}
|
||||
|
||||
if (chunk.imports) {
|
||||
chunk.imports.forEach((file) => traverse(file, add_js));
|
||||
}
|
||||
|
||||
if (add_dynamic_css && chunk.dynamicImports) {
|
||||
chunk.dynamicImports.forEach((file) => traverse(file, false));
|
||||
}
|
||||
}
|
||||
|
||||
const { chunk, file } = resolve_symlinks(manifest, entry);
|
||||
|
||||
traverse(file, true);
|
||||
|
||||
return {
|
||||
file: chunk.file,
|
||||
imports: Array.from(imports),
|
||||
stylesheets: Array.from(stylesheets),
|
||||
fonts: Array.from(fonts)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('vite').Manifest} manifest
|
||||
* @param {string} file
|
||||
*/
|
||||
export function resolve_symlinks(manifest, file) {
|
||||
while (!manifest[file]) {
|
||||
const next = normalizePath(path.relative('.', fs.realpathSync(file)));
|
||||
if (next === file) throw new Error(`Could not find file "${file}" in Vite manifest`);
|
||||
file = next;
|
||||
}
|
||||
|
||||
const chunk = manifest[file];
|
||||
|
||||
return { chunk, file };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ValidatedKitConfig} config
|
||||
* @returns {string}
|
||||
*/
|
||||
export function assets_base(config) {
|
||||
return (config.paths.assets || config.paths.base || '.') + '/';
|
||||
}
|
611
node_modules/@sveltejs/kit/src/exports/vite/dev/index.js
generated
vendored
Normal file
611
node_modules/@sveltejs/kit/src/exports/vite/dev/index.js
generated
vendored
Normal file
@ -0,0 +1,611 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { URL } from 'node:url';
|
||||
import colors from 'kleur';
|
||||
import sirv from 'sirv';
|
||||
import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite';
|
||||
import { getRequest, setResponse } from '../../../exports/node/index.js';
|
||||
import { installPolyfills } from '../../../exports/node/polyfills.js';
|
||||
import { coalesce_to_error } from '../../../utils/error.js';
|
||||
import { posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js';
|
||||
import { should_polyfill } from '../../../utils/platform.js';
|
||||
import { load_error_page } from '../../../core/config/index.js';
|
||||
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
||||
import * as sync from '../../../core/sync/sync.js';
|
||||
import { get_mime_lookup, runtime_base } from '../../../core/utils.js';
|
||||
import { compact } from '../../../utils/array.js';
|
||||
import { not_found } from '../utils.js';
|
||||
import { SCHEME } from '../../../utils/url.js';
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
/**
|
||||
* @param {import('vite').ViteDevServer} vite
|
||||
* @param {import('vite').ResolvedConfig} vite_config
|
||||
* @param {import('types').ValidatedConfig} svelte_config
|
||||
* @return {Promise<Promise<() => void>>}
|
||||
*/
|
||||
export async function dev(vite, vite_config, svelte_config) {
|
||||
if (should_polyfill) {
|
||||
installPolyfills();
|
||||
}
|
||||
|
||||
const fetch = globalThis.fetch;
|
||||
globalThis.fetch = (info, init) => {
|
||||
if (typeof info === 'string' && !SCHEME.test(info)) {
|
||||
throw new Error(
|
||||
`Cannot use relative URL (${info}) with global fetch — use \`event.fetch\` instead: https://kit.svelte.dev/docs/web-standards#fetch-apis`
|
||||
);
|
||||
}
|
||||
|
||||
return fetch(info, init);
|
||||
};
|
||||
|
||||
sync.init(svelte_config, vite_config.mode);
|
||||
|
||||
/** @type {import('types').ManifestData} */
|
||||
let manifest_data;
|
||||
/** @type {import('@sveltejs/kit').SSRManifest} */
|
||||
let manifest;
|
||||
|
||||
/** @type {Error | null} */
|
||||
let manifest_error = null;
|
||||
|
||||
/** @param {string} url */
|
||||
async function loud_ssr_load_module(url) {
|
||||
try {
|
||||
return await vite.ssrLoadModule(url, { fixStacktrace: true });
|
||||
} catch (/** @type {any} */ err) {
|
||||
const msg = buildErrorMessage(err, [colors.red(`Internal server error: ${err.message}`)]);
|
||||
|
||||
if (!vite.config.logger.hasErrorLogged(err)) {
|
||||
vite.config.logger.error(msg, { error: err });
|
||||
}
|
||||
|
||||
vite.ws.send({
|
||||
type: 'error',
|
||||
err: {
|
||||
...err,
|
||||
// these properties are non-enumerable and will
|
||||
// not be serialized unless we explicitly include them
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
}
|
||||
});
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} id */
|
||||
async function resolve(id) {
|
||||
const url = id.startsWith('..') ? `/@fs${path.posix.resolve(id)}` : `/${id}`;
|
||||
|
||||
const module = await loud_ssr_load_module(url);
|
||||
|
||||
const module_node = await vite.moduleGraph.getModuleByUrl(url);
|
||||
if (!module_node) throw new Error(`Could not find node for ${url}`);
|
||||
|
||||
return { module, module_node, url };
|
||||
}
|
||||
|
||||
async function update_manifest() {
|
||||
try {
|
||||
({ manifest_data } = await sync.create(svelte_config));
|
||||
|
||||
if (manifest_error) {
|
||||
manifest_error = null;
|
||||
vite.ws.send({ type: 'full-reload' });
|
||||
}
|
||||
} catch (error) {
|
||||
manifest_error = /** @type {Error} */ (error);
|
||||
|
||||
console.error(colors.bold().red(manifest_error.message));
|
||||
vite.ws.send({
|
||||
type: 'error',
|
||||
err: {
|
||||
message: manifest_error.message ?? 'Invalid routes',
|
||||
stack: ''
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
manifest = {
|
||||
appDir: svelte_config.kit.appDir,
|
||||
appPath: svelte_config.kit.appDir,
|
||||
assets: new Set(manifest_data.assets.map((asset) => asset.file)),
|
||||
mimeTypes: get_mime_lookup(manifest_data),
|
||||
_: {
|
||||
client: {
|
||||
start: `${runtime_base}/client/start.js`,
|
||||
app: `${to_fs(svelte_config.kit.outDir)}/generated/client/app.js`,
|
||||
imports: [],
|
||||
stylesheets: [],
|
||||
fonts: []
|
||||
},
|
||||
nodes: manifest_data.nodes.map((node, index) => {
|
||||
return async () => {
|
||||
/** @type {import('types').SSRNode} */
|
||||
const result = {};
|
||||
|
||||
/** @type {import('vite').ModuleNode[]} */
|
||||
const module_nodes = [];
|
||||
|
||||
result.index = index;
|
||||
|
||||
// these are unused in dev, it's easier to include them
|
||||
result.imports = [];
|
||||
result.stylesheets = [];
|
||||
result.fonts = [];
|
||||
|
||||
if (node.component) {
|
||||
result.component = async () => {
|
||||
const { module_node, module } = await resolve(
|
||||
/** @type {string} */ (node.component)
|
||||
);
|
||||
|
||||
module_nodes.push(module_node);
|
||||
|
||||
return module.default;
|
||||
};
|
||||
}
|
||||
|
||||
if (node.universal) {
|
||||
const { module, module_node } = await resolve(node.universal);
|
||||
|
||||
module_nodes.push(module_node);
|
||||
|
||||
result.universal = module;
|
||||
result.universal_id = node.universal;
|
||||
}
|
||||
|
||||
if (node.server) {
|
||||
const { module } = await resolve(node.server);
|
||||
result.server = module;
|
||||
result.server_id = node.server;
|
||||
}
|
||||
|
||||
// in dev we inline all styles to avoid FOUC. this gets populated lazily so that
|
||||
// components/stylesheets loaded via import() during `load` are included
|
||||
result.inline_styles = async () => {
|
||||
const deps = new Set();
|
||||
|
||||
for (const module_node of module_nodes) {
|
||||
await find_deps(vite, module_node, deps);
|
||||
}
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const styles = {};
|
||||
|
||||
for (const dep of deps) {
|
||||
const url = new URL(dep.url, 'dummy:/');
|
||||
const query = url.searchParams;
|
||||
|
||||
if (
|
||||
(isCSSRequest(dep.file) ||
|
||||
(query.has('svelte') && query.get('type') === 'style')) &&
|
||||
!(query.has('raw') || query.has('url') || query.has('inline'))
|
||||
) {
|
||||
try {
|
||||
query.set('inline', '');
|
||||
const mod = await vite.ssrLoadModule(
|
||||
`${decodeURI(url.pathname)}${url.search}${url.hash}`
|
||||
);
|
||||
styles[dep.url] = mod.default;
|
||||
} catch {
|
||||
// this can happen with dynamically imported modules, I think
|
||||
// because the Vite module graph doesn't distinguish between
|
||||
// static and dynamic imports? TODO investigate, submit fix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return styles;
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
}),
|
||||
routes: compact(
|
||||
manifest_data.routes.map((route) => {
|
||||
if (!route.page && !route.endpoint) return null;
|
||||
|
||||
const endpoint = route.endpoint;
|
||||
|
||||
return {
|
||||
id: route.id,
|
||||
pattern: route.pattern,
|
||||
params: route.params,
|
||||
page: route.page,
|
||||
endpoint: endpoint
|
||||
? async () => {
|
||||
const url = path.resolve(cwd, endpoint.file);
|
||||
return await loud_ssr_load_module(url);
|
||||
}
|
||||
: null,
|
||||
endpoint_id: endpoint?.file
|
||||
};
|
||||
})
|
||||
),
|
||||
matchers: async () => {
|
||||
/** @type {Record<string, import('@sveltejs/kit').ParamMatcher>} */
|
||||
const matchers = {};
|
||||
|
||||
for (const key in manifest_data.matchers) {
|
||||
const file = manifest_data.matchers[key];
|
||||
const url = path.resolve(cwd, file);
|
||||
const module = await vite.ssrLoadModule(url, { fixStacktrace: true });
|
||||
|
||||
if (module.match) {
|
||||
matchers[key] = module.match;
|
||||
} else {
|
||||
throw new Error(`${file} does not export a \`match\` function`);
|
||||
}
|
||||
}
|
||||
|
||||
return matchers;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {Error} error */
|
||||
function fix_stack_trace(error) {
|
||||
vite.ssrFixStacktrace(error);
|
||||
return error.stack;
|
||||
}
|
||||
|
||||
await update_manifest();
|
||||
|
||||
/**
|
||||
* @param {string} event
|
||||
* @param {(file: string) => void} cb
|
||||
*/
|
||||
const watch = (event, cb) => {
|
||||
vite.watcher.on(event, (file) => {
|
||||
if (
|
||||
file.startsWith(svelte_config.kit.files.routes + path.sep) ||
|
||||
file.startsWith(svelte_config.kit.files.params + path.sep) ||
|
||||
// in contrast to server hooks, client hooks are written to the client manifest
|
||||
// and therefore need rebuilding when they are added/removed
|
||||
file.startsWith(svelte_config.kit.files.hooks.client)
|
||||
) {
|
||||
cb(file);
|
||||
}
|
||||
});
|
||||
};
|
||||
/** @type {NodeJS.Timeout | null } */
|
||||
let timeout = null;
|
||||
/** @param {() => void} to_run */
|
||||
const debounce = (to_run) => {
|
||||
timeout && clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
to_run();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// flag to skip watchers if server is already restarting
|
||||
let restarting = false;
|
||||
|
||||
// Debounce add/unlink events because in case of folder deletion or moves
|
||||
// they fire in rapid succession, causing needless invocations.
|
||||
watch('add', () => debounce(update_manifest));
|
||||
watch('unlink', () => debounce(update_manifest));
|
||||
watch('change', (file) => {
|
||||
// Don't run for a single file if the whole manifest is about to get updated
|
||||
if (timeout || restarting) return;
|
||||
|
||||
sync.update(svelte_config, manifest_data, file);
|
||||
});
|
||||
|
||||
const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files;
|
||||
|
||||
// vite client only executes a full reload if the triggering html file path is index.html
|
||||
// kit defaults to src/app.html, so unless user changed that to index.html
|
||||
// send the vite client a full-reload event without path being set
|
||||
if (appTemplate !== 'index.html') {
|
||||
vite.watcher.on('change', (file) => {
|
||||
if (file === appTemplate && !restarting) {
|
||||
vite.ws.send({ type: 'full-reload' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
vite.watcher.on('all', (_, file) => {
|
||||
if (
|
||||
file === appTemplate ||
|
||||
file === errorTemplate ||
|
||||
file.startsWith(serviceWorker) ||
|
||||
file.startsWith(hooks.server)
|
||||
) {
|
||||
sync.server(svelte_config);
|
||||
}
|
||||
});
|
||||
|
||||
// changing the svelte config requires restarting the dev server
|
||||
// the config is only read on start and passed on to vite-plugin-svelte
|
||||
// which needs up-to-date values to operate correctly
|
||||
vite.watcher.on('change', (file) => {
|
||||
if (path.basename(file) === 'svelte.config.js') {
|
||||
console.log(`svelte config changed, restarting vite dev-server. changed file: ${file}`);
|
||||
restarting = true;
|
||||
vite.restart();
|
||||
}
|
||||
});
|
||||
|
||||
const assets = svelte_config.kit.paths.assets ? SVELTE_KIT_ASSETS : svelte_config.kit.paths.base;
|
||||
const asset_server = sirv(svelte_config.kit.files.assets, {
|
||||
dev: true,
|
||||
etag: true,
|
||||
maxAge: 0,
|
||||
extensions: [],
|
||||
setHeaders: (res) => {
|
||||
res.setHeader('access-control-allow-origin', '*');
|
||||
}
|
||||
});
|
||||
|
||||
async function align_exports() {
|
||||
// This shameful hack allows us to load runtime server code via Vite
|
||||
// while apps load `HttpError` and `Redirect` in Node, without
|
||||
// causing `instanceof` checks to fail
|
||||
const control_module_node = await import('../../../runtime/control.js');
|
||||
const control_module_vite = await vite.ssrLoadModule(`${runtime_base}/control.js`);
|
||||
|
||||
control_module_node.replace_implementations({
|
||||
ActionFailure: control_module_vite.ActionFailure,
|
||||
HttpError: control_module_vite.HttpError,
|
||||
Redirect: control_module_vite.Redirect
|
||||
});
|
||||
}
|
||||
align_exports();
|
||||
const ws_send = vite.ws.send;
|
||||
/** @param {any} args */
|
||||
vite.ws.send = function (...args) {
|
||||
// We need to reapply the patch after Vite did dependency optimizations
|
||||
// because that clears the module resolutions
|
||||
if (args[0]?.type === 'full-reload' && args[0].path === '*') {
|
||||
align_exports();
|
||||
}
|
||||
return ws_send.apply(vite.ws, args);
|
||||
};
|
||||
|
||||
vite.middlewares.use(async (req, res, next) => {
|
||||
try {
|
||||
const base = `${vite.config.server.https ? 'https' : 'http'}://${
|
||||
req.headers[':authority'] || req.headers.host
|
||||
}`;
|
||||
|
||||
const decoded = decodeURI(new URL(base + req.url).pathname);
|
||||
|
||||
if (decoded.startsWith(assets)) {
|
||||
const pathname = decoded.slice(assets.length);
|
||||
const file = svelte_config.kit.files.assets + pathname;
|
||||
|
||||
if (fs.existsSync(file) && !fs.statSync(file).isDirectory()) {
|
||||
if (has_correct_case(file, svelte_config.kit.files.assets)) {
|
||||
req.url = encodeURI(pathname); // don't need query/hash
|
||||
asset_server(req, res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (e) {
|
||||
const error = coalesce_to_error(e);
|
||||
res.statusCode = 500;
|
||||
res.end(fix_stack_trace(error));
|
||||
}
|
||||
});
|
||||
|
||||
const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, '');
|
||||
|
||||
return () => {
|
||||
const serve_static_middleware = vite.middlewares.stack.find(
|
||||
(middleware) =>
|
||||
/** @type {function} */ (middleware.handle).name === 'viteServeStaticMiddleware'
|
||||
);
|
||||
|
||||
// Vite will give a 403 on URLs like /test, /static, and /package.json preventing us from
|
||||
// serving routes with those names. See https://github.com/vitejs/vite/issues/7363
|
||||
remove_static_middlewares(vite.middlewares);
|
||||
|
||||
vite.middlewares.use(async (req, res) => {
|
||||
// Vite's base middleware strips out the base path. Restore it
|
||||
const original_url = req.url;
|
||||
req.url = req.originalUrl;
|
||||
try {
|
||||
const base = `${vite.config.server.https ? 'https' : 'http'}://${
|
||||
req.headers[':authority'] || req.headers.host
|
||||
}`;
|
||||
|
||||
const decoded = decodeURI(new URL(base + req.url).pathname);
|
||||
const file = posixify(path.resolve(decoded.slice(svelte_config.kit.paths.base.length + 1)));
|
||||
const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory();
|
||||
const allowed =
|
||||
!vite_config.server.fs.strict ||
|
||||
vite_config.server.fs.allow.some((dir) => file.startsWith(dir));
|
||||
|
||||
if (is_file && allowed) {
|
||||
req.url = original_url;
|
||||
// @ts-expect-error
|
||||
serve_static_middleware.handle(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decoded.startsWith(svelte_config.kit.paths.base)) {
|
||||
return not_found(req, res, svelte_config.kit.paths.base);
|
||||
}
|
||||
|
||||
if (decoded === svelte_config.kit.paths.base + '/service-worker.js') {
|
||||
const resolved = resolve_entry(svelte_config.kit.files.serviceWorker);
|
||||
|
||||
if (resolved) {
|
||||
res.writeHead(200, {
|
||||
'content-type': 'application/javascript'
|
||||
});
|
||||
res.end(`import '${to_fs(resolved)}';`);
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('not found');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// we have to import `Server` before calling `set_assets`
|
||||
const { Server } = /** @type {import('types').ServerModule} */ (
|
||||
await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true })
|
||||
);
|
||||
|
||||
const { set_fix_stack_trace } = await vite.ssrLoadModule(
|
||||
`${runtime_base}/shared-server.js`
|
||||
);
|
||||
set_fix_stack_trace(fix_stack_trace);
|
||||
|
||||
const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths');
|
||||
set_assets(assets);
|
||||
|
||||
const server = new Server(manifest);
|
||||
|
||||
await server.init({ env });
|
||||
|
||||
let request;
|
||||
|
||||
try {
|
||||
request = await getRequest({
|
||||
base,
|
||||
request: req
|
||||
});
|
||||
} catch (/** @type {any} */ err) {
|
||||
res.statusCode = err.status || 400;
|
||||
return res.end('Invalid request body');
|
||||
}
|
||||
|
||||
if (manifest_error) {
|
||||
console.error(colors.bold().red(manifest_error.message));
|
||||
|
||||
const error_page = load_error_page(svelte_config);
|
||||
|
||||
/** @param {{ status: number; message: string }} opts */
|
||||
const error_template = ({ status, message }) => {
|
||||
return error_page
|
||||
.replace(/%sveltekit\.status%/g, String(status))
|
||||
.replace(/%sveltekit\.error\.message%/g, message);
|
||||
};
|
||||
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/html; charset=utf-8'
|
||||
});
|
||||
res.end(
|
||||
error_template({ status: 500, message: manifest_error.message ?? 'Invalid routes' })
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const rendered = await server.respond(request, {
|
||||
getClientAddress: () => {
|
||||
const { remoteAddress } = req.socket;
|
||||
if (remoteAddress) return remoteAddress;
|
||||
throw new Error('Could not determine clientAddress');
|
||||
},
|
||||
read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file))
|
||||
});
|
||||
|
||||
if (rendered.status === 404) {
|
||||
// @ts-expect-error
|
||||
serve_static_middleware.handle(req, res, () => {
|
||||
setResponse(res, rendered);
|
||||
});
|
||||
} else {
|
||||
setResponse(res, rendered);
|
||||
}
|
||||
} catch (e) {
|
||||
const error = coalesce_to_error(e);
|
||||
res.statusCode = 500;
|
||||
res.end(fix_stack_trace(error));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('connect').Server} server
|
||||
*/
|
||||
function remove_static_middlewares(server) {
|
||||
const static_middlewares = ['viteServeStaticMiddleware'];
|
||||
for (let i = server.stack.length - 1; i > 0; i--) {
|
||||
// @ts-expect-error using internals
|
||||
if (static_middlewares.includes(server.stack[i].handle.name)) {
|
||||
server.stack.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('vite').ViteDevServer} vite
|
||||
* @param {import('vite').ModuleNode} node
|
||||
* @param {Set<import('vite').ModuleNode>} deps
|
||||
*/
|
||||
async function find_deps(vite, node, deps) {
|
||||
// since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous.
|
||||
// instead of using `await`, we resolve all branches in parallel.
|
||||
/** @type {Promise<void>[]} */
|
||||
const branches = [];
|
||||
|
||||
/** @param {import('vite').ModuleNode} node */
|
||||
async function add(node) {
|
||||
if (!deps.has(node)) {
|
||||
deps.add(node);
|
||||
await find_deps(vite, node, deps);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} url */
|
||||
async function add_by_url(url) {
|
||||
const node = await vite.moduleGraph.getModuleByUrl(url);
|
||||
|
||||
if (node) {
|
||||
await add(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.ssrTransformResult) {
|
||||
if (node.ssrTransformResult.deps) {
|
||||
node.ssrTransformResult.deps.forEach((url) => branches.push(add_by_url(url)));
|
||||
}
|
||||
|
||||
if (node.ssrTransformResult.dynamicDeps) {
|
||||
node.ssrTransformResult.dynamicDeps.forEach((url) => branches.push(add_by_url(url)));
|
||||
}
|
||||
} else {
|
||||
node.importedModules.forEach((node) => branches.push(add(node)));
|
||||
}
|
||||
|
||||
await Promise.all(branches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a file is being requested with the correct case,
|
||||
* to ensure consistent behaviour between dev and prod and across
|
||||
* operating systems. Note that we can't use realpath here,
|
||||
* because we don't want to follow symlinks
|
||||
* @param {string} file
|
||||
* @param {string} assets
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function has_correct_case(file, assets) {
|
||||
if (file === assets) return true;
|
||||
|
||||
const parent = path.dirname(file);
|
||||
|
||||
if (fs.readdirSync(parent).includes(path.basename(file))) {
|
||||
return has_correct_case(parent, assets);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
107
node_modules/@sveltejs/kit/src/exports/vite/graph_analysis/index.js
generated
vendored
Normal file
107
node_modules/@sveltejs/kit/src/exports/vite/graph_analysis/index.js
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
import path from 'node:path';
|
||||
import { posixify } from '../../../utils/filesystem.js';
|
||||
import { strip_virtual_prefix } from '../utils.js';
|
||||
|
||||
const ILLEGAL_IMPORTS = new Set([
|
||||
'\0virtual:$env/dynamic/private',
|
||||
'\0virtual:$env/static/private'
|
||||
]);
|
||||
const ILLEGAL_MODULE_NAME_PATTERN = /.*\.server\..+/;
|
||||
|
||||
/**
|
||||
* Checks if given id imports a module that is not allowed to be imported into client-side code.
|
||||
* @param {string} id
|
||||
* @param {{
|
||||
* cwd: string;
|
||||
* node_modules: string;
|
||||
* server: string;
|
||||
* }} dirs
|
||||
*/
|
||||
export function is_illegal(id, dirs) {
|
||||
if (ILLEGAL_IMPORTS.has(id)) return true;
|
||||
if (!id.startsWith(dirs.cwd) || id.startsWith(dirs.node_modules)) return false;
|
||||
return ILLEGAL_MODULE_NAME_PATTERN.test(path.basename(id)) || id.startsWith(dirs.server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a guard that checks that no id imports a module that is not allowed to be imported into client-side code.
|
||||
* @param {import('vite').Rollup.PluginContext} context
|
||||
* @param {{ cwd: string; lib: string }} paths
|
||||
*/
|
||||
export function module_guard(context, { cwd, lib }) {
|
||||
/** @type {Set<string>} */
|
||||
const seen = new Set();
|
||||
|
||||
const dirs = {
|
||||
// ids will be posixified, so we need to posixify these, too
|
||||
cwd: posixify(cwd),
|
||||
node_modules: posixify(path.join(cwd, 'node_modules')),
|
||||
server: posixify(path.join(lib, 'server'))
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {Array<{ id: string; dynamic: boolean }>} chain
|
||||
*/
|
||||
function follow(id, chain) {
|
||||
if (seen.has(id)) return;
|
||||
seen.add(id);
|
||||
|
||||
if (is_illegal(id, dirs)) {
|
||||
chain.shift(); // discard the entry point
|
||||
id = normalize_id(id, lib, cwd);
|
||||
|
||||
const pyramid =
|
||||
chain.map(({ id, dynamic }, i) => {
|
||||
id = normalize_id(id, lib, cwd);
|
||||
|
||||
return `${' '.repeat(i * 2)}- ${strip_virtual_prefix(id)} ${
|
||||
dynamic ? 'dynamically imports' : 'imports'
|
||||
}\n`;
|
||||
}) + `${' '.repeat(chain.length)}- ${strip_virtual_prefix(id)}`;
|
||||
|
||||
const message = `Cannot import ${strip_virtual_prefix(
|
||||
id
|
||||
)} into client-side code:\n${pyramid}`;
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const module = context.getModuleInfo(id);
|
||||
|
||||
if (module) {
|
||||
for (const child of module.importedIds) {
|
||||
follow(child, [...chain, { id, dynamic: false }]);
|
||||
}
|
||||
|
||||
for (const child of module.dynamicallyImportedIds) {
|
||||
follow(child, [...chain, { id, dynamic: true }]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/** @param {string} id should be posixified */
|
||||
check: (id) => {
|
||||
follow(id, []);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes cwd/lib path from the start of the id
|
||||
* @param {string} id
|
||||
* @param {string} lib
|
||||
* @param {string} cwd
|
||||
*/
|
||||
export function normalize_id(id, lib, cwd) {
|
||||
if (id.startsWith(lib)) {
|
||||
id = id.replace(lib, '$lib');
|
||||
}
|
||||
|
||||
if (id.startsWith(cwd)) {
|
||||
id = path.relative(cwd, id);
|
||||
}
|
||||
|
||||
return posixify(id);
|
||||
}
|
5
node_modules/@sveltejs/kit/src/exports/vite/graph_analysis/types.d.ts
generated
vendored
Normal file
5
node_modules/@sveltejs/kit/src/exports/vite/graph_analysis/types.d.ts
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export interface ImportGraph {
|
||||
readonly id: string;
|
||||
readonly dynamic: boolean;
|
||||
readonly children: Generator<ImportGraph>;
|
||||
}
|
6
node_modules/@sveltejs/kit/src/exports/vite/graph_analysis/utils.js
generated
vendored
Normal file
6
node_modules/@sveltejs/kit/src/exports/vite/graph_analysis/utils.js
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
const query_pattern = /\?.*$/s;
|
||||
|
||||
/** @param {string} path */
|
||||
export function remove_query_from_id(path) {
|
||||
return path.replace(query_pattern, '');
|
||||
}
|
908
node_modules/@sveltejs/kit/src/exports/vite/index.js
generated
vendored
Normal file
908
node_modules/@sveltejs/kit/src/exports/vite/index.js
generated
vendored
Normal file
@ -0,0 +1,908 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import colors from 'kleur';
|
||||
import * as vite from 'vite';
|
||||
|
||||
import { copy, mkdirp, posixify, read, resolve_entry, rimraf } from '../../utils/filesystem.js';
|
||||
import { create_static_module, create_dynamic_module } from '../../core/env.js';
|
||||
import * as sync from '../../core/sync/sync.js';
|
||||
import { create_assets } from '../../core/sync/create_manifest_data/index.js';
|
||||
import { runtime_directory, logger } from '../../core/utils.js';
|
||||
import { load_config } from '../../core/config/index.js';
|
||||
import { generate_manifest } from '../../core/generate_manifest/index.js';
|
||||
import { build_server_nodes } from './build/build_server.js';
|
||||
import { build_service_worker } from './build/build_service_worker.js';
|
||||
import { assets_base, find_deps } from './build/utils.js';
|
||||
import { dev } from './dev/index.js';
|
||||
import { is_illegal, module_guard, normalize_id } from './graph_analysis/index.js';
|
||||
import { preview } from './preview/index.js';
|
||||
import { get_config_aliases, get_env, strip_virtual_prefix } from './utils.js';
|
||||
import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
|
||||
import prerender from '../../core/postbuild/prerender.js';
|
||||
import analyse from '../../core/postbuild/analyse.js';
|
||||
import { s } from '../../utils/misc.js';
|
||||
import { hash } from '../../runtime/hash.js';
|
||||
import { dedent, isSvelte5Plus } from '../../core/sync/utils.js';
|
||||
|
||||
export { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
/** @type {import('./types.js').EnforcedConfig} */
|
||||
const enforced_config = {
|
||||
appType: true,
|
||||
base: true,
|
||||
build: {
|
||||
cssCodeSplit: true,
|
||||
emptyOutDir: true,
|
||||
lib: {
|
||||
entry: true,
|
||||
name: true,
|
||||
formats: true
|
||||
},
|
||||
manifest: true,
|
||||
outDir: true,
|
||||
rollupOptions: {
|
||||
input: true,
|
||||
output: {
|
||||
format: true,
|
||||
entryFileNames: true,
|
||||
chunkFileNames: true,
|
||||
assetFileNames: true
|
||||
},
|
||||
preserveEntrySignatures: true
|
||||
},
|
||||
ssr: true
|
||||
},
|
||||
publicDir: true,
|
||||
resolve: {
|
||||
alias: {
|
||||
$app: true,
|
||||
$lib: true,
|
||||
'$service-worker': true
|
||||
}
|
||||
},
|
||||
root: true
|
||||
};
|
||||
|
||||
const options_regex = /(export\s+const\s+(prerender|csr|ssr|trailingSlash))\s*=/s;
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const warned = new Set();
|
||||
|
||||
/** @type {import('@sveltejs/vite-plugin-svelte').PreprocessorGroup} */
|
||||
const warning_preprocessor = {
|
||||
script: ({ content, filename }) => {
|
||||
if (!filename) return;
|
||||
|
||||
const basename = path.basename(filename);
|
||||
if (basename.startsWith('+page.') || basename.startsWith('+layout.')) {
|
||||
const match = content.match(options_regex);
|
||||
if (match) {
|
||||
const fixed = basename.replace('.svelte', '(.server).js/ts');
|
||||
|
||||
const message =
|
||||
`\n${colors.bold().red(path.relative('.', filename))}\n` +
|
||||
`\`${match[1]}\` will be ignored — move it to ${fixed} instead. See https://kit.svelte.dev/docs/page-options for more information.`;
|
||||
|
||||
if (!warned.has(message)) {
|
||||
console.log(message);
|
||||
warned.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
markup: ({ content, filename }) => {
|
||||
if (!filename) return;
|
||||
|
||||
const basename = path.basename(filename);
|
||||
if (basename.startsWith('+layout.') && !content.includes('<slot')) {
|
||||
const message =
|
||||
`\n${colors.bold().red(path.relative('.', filename))}\n` +
|
||||
'`<slot />` missing — inner content will not be rendered';
|
||||
|
||||
if (!warned.has(message)) {
|
||||
console.log(message);
|
||||
warned.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the SvelteKit Vite plugins.
|
||||
* @returns {Promise<import('vite').Plugin[]>}
|
||||
*/
|
||||
export async function sveltekit() {
|
||||
const svelte_config = await load_config();
|
||||
|
||||
/** @type {import('@sveltejs/vite-plugin-svelte').Options['preprocess']} */
|
||||
let preprocess = svelte_config.preprocess;
|
||||
if (Array.isArray(preprocess)) {
|
||||
preprocess = [...preprocess, warning_preprocessor];
|
||||
} else if (preprocess) {
|
||||
preprocess = [preprocess, warning_preprocessor];
|
||||
} else {
|
||||
preprocess = warning_preprocessor;
|
||||
}
|
||||
|
||||
/** @type {import('@sveltejs/vite-plugin-svelte').Options} */
|
||||
const vite_plugin_svelte_options = {
|
||||
configFile: false,
|
||||
extensions: svelte_config.extensions,
|
||||
preprocess,
|
||||
onwarn: svelte_config.onwarn,
|
||||
compilerOptions: {
|
||||
// @ts-expect-error SvelteKit requires hydratable true by default
|
||||
hydratable: isSvelte5Plus() ? undefined : true,
|
||||
...svelte_config.compilerOptions
|
||||
},
|
||||
...svelte_config.vitePlugin
|
||||
};
|
||||
|
||||
return [...svelte(vite_plugin_svelte_options), ...kit({ svelte_config })];
|
||||
}
|
||||
|
||||
// These variables live outside the `kit()` function because it is re-invoked by each Vite build
|
||||
|
||||
let secondary_build_started = false;
|
||||
|
||||
/** @type {import('types').ManifestData} */
|
||||
let manifest_data;
|
||||
|
||||
/**
|
||||
* Returns the SvelteKit Vite plugin. Vite executes Rollup hooks as well as some of its own.
|
||||
* Background reading is available at:
|
||||
* - https://vitejs.dev/guide/api-plugin.html
|
||||
* - https://rollupjs.org/guide/en/#plugin-development
|
||||
*
|
||||
* You can get an idea of the lifecycle by looking at the flow charts here:
|
||||
* - https://rollupjs.org/guide/en/#build-hooks
|
||||
* - https://rollupjs.org/guide/en/#output-generation-hooks
|
||||
*
|
||||
* @param {{ svelte_config: import('types').ValidatedConfig }} options
|
||||
* @return {import('vite').Plugin[]}
|
||||
*/
|
||||
function kit({ svelte_config }) {
|
||||
const { kit } = svelte_config;
|
||||
const out = `${kit.outDir}/output`;
|
||||
|
||||
const version_hash = hash(kit.version.name);
|
||||
|
||||
/** @type {import('vite').ResolvedConfig} */
|
||||
let vite_config;
|
||||
|
||||
/** @type {import('vite').ConfigEnv} */
|
||||
let vite_config_env;
|
||||
|
||||
/** @type {boolean} */
|
||||
let is_build;
|
||||
|
||||
/** @type {{ public: Record<string, string>; private: Record<string, string> }} */
|
||||
let env;
|
||||
|
||||
/** @type {() => Promise<void>} */
|
||||
let finalise;
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
let initial_config;
|
||||
|
||||
const service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
|
||||
|
||||
const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) =>
|
||||
relative_path.includes('node_modules') || relative_path.includes(kit.outDir);
|
||||
|
||||
/** @type {import('vite').Plugin} */
|
||||
const plugin_setup = {
|
||||
name: 'vite-plugin-sveltekit-setup',
|
||||
|
||||
/**
|
||||
* Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
|
||||
* @see https://vitejs.dev/guide/api-plugin.html#config
|
||||
*/
|
||||
async config(config, config_env) {
|
||||
initial_config = config;
|
||||
vite_config_env = config_env;
|
||||
is_build = config_env.command === 'build';
|
||||
|
||||
env = get_env(kit.env, vite_config_env.mode);
|
||||
|
||||
const allow = new Set([
|
||||
kit.files.lib,
|
||||
kit.files.routes,
|
||||
kit.outDir,
|
||||
path.resolve('src'), // TODO this isn't correct if user changed all his files to sth else than src (like in test/options)
|
||||
path.resolve('node_modules'),
|
||||
path.resolve(vite.searchForWorkspaceRoot(cwd), 'node_modules')
|
||||
]);
|
||||
|
||||
// We can only add directories to the allow list, so we find out
|
||||
// if there's a client hooks file and pass its directory
|
||||
const client_hooks = resolve_entry(kit.files.hooks.client);
|
||||
if (client_hooks) allow.add(path.dirname(client_hooks));
|
||||
|
||||
const generated = path.posix.join(kit.outDir, 'generated');
|
||||
|
||||
// dev and preview config can be shared
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const new_config = {
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: '__SERVER__', replacement: `${generated}/server` },
|
||||
{ find: '$app', replacement: `${runtime_directory}/app` },
|
||||
...get_config_aliases(kit)
|
||||
]
|
||||
},
|
||||
root: cwd,
|
||||
server: {
|
||||
cors: { preflightContinue: true },
|
||||
fs: {
|
||||
allow: [...allow]
|
||||
},
|
||||
sourcemapIgnoreList,
|
||||
watch: {
|
||||
ignored: [
|
||||
// Ignore all siblings of config.kit.outDir/generated
|
||||
`${posixify(kit.outDir)}/!(generated)`
|
||||
]
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
cors: { preflightContinue: true }
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
'@sveltejs/kit',
|
||||
// exclude kit features so that libraries using them work even when they are prebundled
|
||||
// this does not affect app code, just handling of imported libraries that use $app or $env
|
||||
'$app',
|
||||
'$env'
|
||||
]
|
||||
},
|
||||
ssr: {
|
||||
noExternal: [
|
||||
// This ensures that esm-env is inlined into the server output with the
|
||||
// export conditions resolved correctly through Vite. This prevents adapters
|
||||
// that bundle later on from resolving the export conditions incorrectly
|
||||
// and for example include browser-only code in the server output
|
||||
// because they for example use esbuild.build with `platform: 'browser'`
|
||||
'esm-env',
|
||||
// We need this for two reasons:
|
||||
// 1. Without this, `@sveltejs/kit` imports are kept as-is in the server output,
|
||||
// and that causes modules and therefore classes like `Redirect` to be imported twice
|
||||
// under different IDs, which breaks a bunch of stuff because of failing instanceof checks.
|
||||
// 2. Vitest bypasses Vite when loading external modules, so we bundle
|
||||
// when it is detected to keep our virtual modules working.
|
||||
// See https://github.com/sveltejs/kit/pull/9172
|
||||
// and https://vitest.dev/config/#deps-registernodeloader
|
||||
'@sveltejs/kit'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (is_build) {
|
||||
if (!new_config.build) new_config.build = {};
|
||||
new_config.build.ssr = !secondary_build_started;
|
||||
|
||||
new_config.define = {
|
||||
__SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name),
|
||||
__SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`),
|
||||
__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval),
|
||||
__SVELTEKIT_DEV__: 'false',
|
||||
__SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
|
||||
};
|
||||
|
||||
if (!secondary_build_started) {
|
||||
manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
|
||||
}
|
||||
} else {
|
||||
new_config.define = {
|
||||
__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
|
||||
__SVELTEKIT_DEV__: 'true',
|
||||
__SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
|
||||
};
|
||||
|
||||
// These Kit dependencies are packaged as CommonJS, which means they must always be externalized.
|
||||
// Without this, the tests will still pass but `pnpm dev` will fail in projects that link `@sveltejs/kit`.
|
||||
/** @type {NonNullable<import('vite').UserConfig['ssr']>} */ (new_config.ssr).external = [
|
||||
'cookie',
|
||||
'set-cookie-parser'
|
||||
];
|
||||
}
|
||||
|
||||
warn_overridden_config(config, new_config);
|
||||
|
||||
return new_config;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores the final config.
|
||||
*/
|
||||
configResolved(config) {
|
||||
vite_config = config;
|
||||
|
||||
// This is a hack to prevent Vite from nuking useful logs,
|
||||
// pending https://github.com/vitejs/vite/issues/9378
|
||||
config.logger.warn('');
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {import('vite').Plugin} */
|
||||
const plugin_virtual_modules = {
|
||||
name: 'vite-plugin-sveltekit-virtual-modules',
|
||||
|
||||
async resolveId(id) {
|
||||
// treat $env/static/[public|private] as virtual
|
||||
if (id.startsWith('$env/') || id.startsWith('__sveltekit/') || id === '$service-worker') {
|
||||
return `\0virtual:${id}`;
|
||||
}
|
||||
},
|
||||
|
||||
async load(id, options) {
|
||||
const browser = !options?.ssr;
|
||||
|
||||
const global = is_build
|
||||
? `globalThis.__sveltekit_${version_hash}`
|
||||
: 'globalThis.__sveltekit_dev';
|
||||
|
||||
if (options?.ssr === false && process.env.TEST !== 'true') {
|
||||
const normalized_cwd = vite.normalizePath(cwd);
|
||||
const normalized_lib = vite.normalizePath(kit.files.lib);
|
||||
if (
|
||||
is_illegal(id, {
|
||||
cwd: normalized_cwd,
|
||||
node_modules: vite.normalizePath(path.resolve('node_modules')),
|
||||
server: vite.normalizePath(path.join(normalized_lib, 'server'))
|
||||
})
|
||||
) {
|
||||
const relative = normalize_id(id, normalized_lib, normalized_cwd);
|
||||
throw new Error(`Cannot import ${strip_virtual_prefix(relative)} into client-side code`);
|
||||
}
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case '\0virtual:$env/static/private':
|
||||
return create_static_module('$env/static/private', env.private);
|
||||
|
||||
case '\0virtual:$env/static/public':
|
||||
return create_static_module('$env/static/public', env.public);
|
||||
|
||||
case '\0virtual:$env/dynamic/private':
|
||||
return create_dynamic_module(
|
||||
'private',
|
||||
vite_config_env.command === 'serve' ? env.private : undefined
|
||||
);
|
||||
|
||||
case '\0virtual:$env/dynamic/public':
|
||||
// populate `$env/dynamic/public` from `window`
|
||||
if (browser) {
|
||||
return `export const env = ${global}.env;`;
|
||||
}
|
||||
|
||||
return create_dynamic_module(
|
||||
'public',
|
||||
vite_config_env.command === 'serve' ? env.public : undefined
|
||||
);
|
||||
|
||||
case '\0virtual:$service-worker':
|
||||
return create_service_worker_module(svelte_config);
|
||||
|
||||
// for internal use only. it's published as $app/paths externally
|
||||
// we use this alias so that we won't collide with user aliases
|
||||
case '\0virtual:__sveltekit/paths': {
|
||||
const { assets, base } = svelte_config.kit.paths;
|
||||
|
||||
// use the values defined in `global`, but fall back to hard-coded values
|
||||
// for the sake of things like Vitest which may import this module
|
||||
// outside the context of a page
|
||||
if (browser) {
|
||||
return dedent`
|
||||
export const base = ${global}?.base ?? ${s(base)};
|
||||
export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};
|
||||
`;
|
||||
}
|
||||
|
||||
return dedent`
|
||||
export let base = ${s(base)};
|
||||
export let assets = ${assets ? s(assets) : 'base'};
|
||||
|
||||
export const relative = ${svelte_config.kit.paths.relative};
|
||||
|
||||
const initial = { base, assets };
|
||||
|
||||
export function override(paths) {
|
||||
base = paths.base;
|
||||
assets = paths.assets;
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
base = initial.base;
|
||||
assets = initial.assets;
|
||||
}
|
||||
|
||||
/** @param {string} path */
|
||||
export function set_assets(path) {
|
||||
assets = initial.assets = path;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
case '\0virtual:__sveltekit/environment': {
|
||||
const { version } = svelte_config.kit;
|
||||
|
||||
return dedent`
|
||||
export const version = ${s(version.name)};
|
||||
export let building = false;
|
||||
|
||||
export function set_building() {
|
||||
building = true;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures that client-side code can't accidentally import server-side code,
|
||||
* whether in `*.server.js` files, `$lib/server`, or `$env/[static|dynamic]/private`
|
||||
* @type {import('vite').Plugin}
|
||||
*/
|
||||
const plugin_guard = {
|
||||
name: 'vite-plugin-sveltekit-guard',
|
||||
|
||||
writeBundle: {
|
||||
sequential: true,
|
||||
async handler(_options) {
|
||||
if (vite_config.build.ssr) return;
|
||||
|
||||
const guard = module_guard(this, {
|
||||
cwd: vite.normalizePath(process.cwd()),
|
||||
lib: vite.normalizePath(kit.files.lib)
|
||||
});
|
||||
|
||||
manifest_data.nodes.forEach((_node, i) => {
|
||||
const id = vite.normalizePath(
|
||||
path.resolve(kit.outDir, `generated/client-optimized/nodes/${i}.js`)
|
||||
);
|
||||
|
||||
guard.check(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {import('vite').Plugin} */
|
||||
const plugin_compile = {
|
||||
name: 'vite-plugin-sveltekit-compile',
|
||||
|
||||
/**
|
||||
* Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
|
||||
* @see https://vitejs.dev/guide/api-plugin.html#config
|
||||
*/
|
||||
async config(config) {
|
||||
/** @type {import('vite').UserConfig} */
|
||||
let new_config;
|
||||
|
||||
if (is_build) {
|
||||
const ssr = /** @type {boolean} */ (config.build?.ssr);
|
||||
const prefix = `${kit.appDir}/immutable`;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const input = {};
|
||||
|
||||
if (ssr) {
|
||||
input.index = `${runtime_directory}/server/index.js`;
|
||||
input.internal = `${kit.outDir}/generated/server/internal.js`;
|
||||
|
||||
// add entry points for every endpoint...
|
||||
manifest_data.routes.forEach((route) => {
|
||||
if (route.endpoint) {
|
||||
const resolved = path.resolve(route.endpoint.file);
|
||||
const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
|
||||
const name = posixify(path.join('entries/endpoints', relative.replace(/\.js$/, '')));
|
||||
input[name] = resolved;
|
||||
}
|
||||
});
|
||||
|
||||
// ...and every component used by pages...
|
||||
manifest_data.nodes.forEach((node) => {
|
||||
for (const file of [node.component, node.universal, node.server]) {
|
||||
if (file) {
|
||||
const resolved = path.resolve(file);
|
||||
const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
|
||||
|
||||
const name = relative.startsWith('..')
|
||||
? posixify(path.join('entries/fallbacks', path.basename(file)))
|
||||
: posixify(path.join('entries/pages', relative.replace(/\.js$/, '')));
|
||||
input[name] = resolved;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ...and every matcher
|
||||
Object.entries(manifest_data.matchers).forEach(([key, file]) => {
|
||||
const name = posixify(path.join('entries/matchers', key));
|
||||
input[name] = path.resolve(file);
|
||||
});
|
||||
} else {
|
||||
input['entry/start'] = `${runtime_directory}/client/start.js`;
|
||||
input['entry/app'] = `${kit.outDir}/generated/client-optimized/app.js`;
|
||||
|
||||
manifest_data.nodes.forEach((node, i) => {
|
||||
if (node.component || node.universal) {
|
||||
input[`nodes/${i}`] = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// see the kit.output.preloadStrategy option for details on why we have multiple options here
|
||||
const ext = kit.output.preloadStrategy === 'preload-mjs' ? 'mjs' : 'js';
|
||||
|
||||
// We could always use a relative asset base path here, but it's better for performance not to.
|
||||
// E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'.
|
||||
// That's larger and takes longer to run and also causes an HTML diff between SSR and client
|
||||
// causing us to do a more expensive hydration check.
|
||||
const client_base =
|
||||
kit.paths.relative !== false || kit.paths.assets ? './' : kit.paths.base || '/';
|
||||
|
||||
new_config = {
|
||||
base: ssr ? assets_base(kit) : client_base,
|
||||
build: {
|
||||
copyPublicDir: !ssr,
|
||||
cssCodeSplit: true,
|
||||
cssMinify: initial_config.build?.minify == null ? true : !!initial_config.build.minify,
|
||||
// don't use the default name to avoid collisions with 'static/manifest.json'
|
||||
manifest: '.vite/manifest.json', // TODO: remove this after bumping peer dep to vite 5
|
||||
outDir: `${out}/${ssr ? 'server' : 'client'}`,
|
||||
rollupOptions: {
|
||||
input,
|
||||
output: {
|
||||
format: 'esm',
|
||||
entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`,
|
||||
chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].${ext}`,
|
||||
assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
|
||||
hoistTransitiveImports: false,
|
||||
sourcemapIgnoreList
|
||||
},
|
||||
preserveEntrySignatures: 'strict'
|
||||
},
|
||||
ssrEmitAssets: true,
|
||||
target: ssr ? 'node16.14' : undefined
|
||||
},
|
||||
publicDir: kit.files.assets,
|
||||
worker: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `${prefix}/workers/[name]-[hash].js`,
|
||||
chunkFileNames: `${prefix}/workers/chunks/[name]-[hash].js`,
|
||||
assetFileNames: `${prefix}/workers/assets/[name]-[hash][extname]`,
|
||||
hoistTransitiveImports: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
new_config = {
|
||||
appType: 'custom',
|
||||
base: kit.paths.base,
|
||||
build: {
|
||||
rollupOptions: {
|
||||
// Vite dependency crawler needs an explicit JS entry point
|
||||
// eventhough server otherwise works without it
|
||||
input: `${runtime_directory}/client/start.js`
|
||||
}
|
||||
},
|
||||
publicDir: kit.files.assets
|
||||
};
|
||||
}
|
||||
|
||||
warn_overridden_config(config, new_config);
|
||||
|
||||
return new_config;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the SvelteKit middleware to do SSR in dev mode.
|
||||
* @see https://vitejs.dev/guide/api-plugin.html#configureserver
|
||||
*/
|
||||
async configureServer(vite) {
|
||||
return await dev(vite, vite_config, svelte_config);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the SvelteKit middleware to do SSR in preview mode.
|
||||
* @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver
|
||||
*/
|
||||
configurePreviewServer(vite) {
|
||||
return preview(vite, vite_config, svelte_config);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the output directories.
|
||||
*/
|
||||
buildStart() {
|
||||
if (secondary_build_started) return;
|
||||
|
||||
if (is_build) {
|
||||
if (!vite_config.build.watch) {
|
||||
rimraf(out);
|
||||
}
|
||||
mkdirp(out);
|
||||
}
|
||||
},
|
||||
|
||||
generateBundle() {
|
||||
if (vite_config.build.ssr) return;
|
||||
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: `${kit.appDir}/version.json`,
|
||||
source: s({ version: kit.version.name })
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Vite builds a single bundle. We need three bundles: client, server, and service worker.
|
||||
* The user's package.json scripts will invoke the Vite CLI to execute the server build. We
|
||||
* then use this hook to kick off builds for the client and service worker.
|
||||
*/
|
||||
writeBundle: {
|
||||
sequential: true,
|
||||
async handler(_options) {
|
||||
if (secondary_build_started) return; // only run this once
|
||||
|
||||
const verbose = vite_config.logLevel === 'info';
|
||||
const log = logger({ verbose });
|
||||
|
||||
/** @type {import('vite').Manifest} */
|
||||
const server_manifest = JSON.parse(read(`${out}/server/${vite_config.build.manifest}`));
|
||||
|
||||
/** @type {import('types').BuildData} */
|
||||
const build_data = {
|
||||
app_dir: kit.appDir,
|
||||
app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`,
|
||||
manifest_data,
|
||||
service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
|
||||
client: null,
|
||||
server_manifest
|
||||
};
|
||||
|
||||
const manifest_path = `${out}/server/manifest-full.js`;
|
||||
fs.writeFileSync(
|
||||
manifest_path,
|
||||
`export const manifest = ${generate_manifest({
|
||||
build_data,
|
||||
relative_path: '.',
|
||||
routes: manifest_data.routes
|
||||
})};\n`
|
||||
);
|
||||
|
||||
// first, build server nodes without the client manifest so we can analyse it
|
||||
log.info('Analysing routes');
|
||||
|
||||
build_server_nodes(out, kit, manifest_data, server_manifest, null, null);
|
||||
|
||||
const metadata = await analyse({
|
||||
manifest_path,
|
||||
env: { ...env.private, ...env.public }
|
||||
});
|
||||
|
||||
log.info('Building app');
|
||||
|
||||
// create client build
|
||||
write_client_manifest(
|
||||
kit,
|
||||
manifest_data,
|
||||
`${kit.outDir}/generated/client-optimized`,
|
||||
metadata.nodes
|
||||
);
|
||||
|
||||
secondary_build_started = true;
|
||||
|
||||
const { output } = /** @type {import('vite').Rollup.RollupOutput} */ (
|
||||
await vite.build({
|
||||
configFile: vite_config.configFile,
|
||||
// CLI args
|
||||
mode: vite_config_env.mode,
|
||||
logLevel: vite_config.logLevel,
|
||||
clearScreen: vite_config.clearScreen,
|
||||
build: {
|
||||
minify: initial_config.build?.minify,
|
||||
assetsInlineLimit: vite_config.build.assetsInlineLimit,
|
||||
sourcemap: vite_config.build.sourcemap
|
||||
},
|
||||
optimizeDeps: {
|
||||
force: vite_config.optimizeDeps.force
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
copy(
|
||||
`${out}/server/${kit.appDir}/immutable/assets`,
|
||||
`${out}/client/${kit.appDir}/immutable/assets`
|
||||
);
|
||||
|
||||
/** @type {import('vite').Manifest} */
|
||||
const client_manifest = JSON.parse(read(`${out}/client/${vite_config.build.manifest}`));
|
||||
|
||||
const deps_of = /** @param {string} f */ (f) =>
|
||||
find_deps(client_manifest, posixify(path.relative('.', f)), false);
|
||||
const start = deps_of(`${runtime_directory}/client/start.js`);
|
||||
const app = deps_of(`${kit.outDir}/generated/client-optimized/app.js`);
|
||||
|
||||
build_data.client = {
|
||||
start: start.file,
|
||||
app: app.file,
|
||||
imports: [...start.imports, ...app.imports],
|
||||
stylesheets: [...start.stylesheets, ...app.stylesheets],
|
||||
fonts: [...start.fonts, ...app.fonts]
|
||||
};
|
||||
|
||||
const css = output.filter(
|
||||
/** @type {(value: any) => value is import('vite').Rollup.OutputAsset} */
|
||||
(value) => value.type === 'asset' && value.fileName.endsWith('.css')
|
||||
);
|
||||
|
||||
// regenerate manifest now that we have client entry...
|
||||
fs.writeFileSync(
|
||||
manifest_path,
|
||||
`export const manifest = ${generate_manifest({
|
||||
build_data,
|
||||
relative_path: '.',
|
||||
routes: manifest_data.routes
|
||||
})};\n`
|
||||
);
|
||||
|
||||
// regenerate nodes with the client manifest...
|
||||
build_server_nodes(out, kit, manifest_data, server_manifest, client_manifest, css);
|
||||
|
||||
// ...and prerender
|
||||
const { prerendered, prerender_map } = await prerender({
|
||||
out,
|
||||
manifest_path,
|
||||
metadata,
|
||||
verbose,
|
||||
env: { ...env.private, ...env.public }
|
||||
});
|
||||
|
||||
// generate a new manifest that doesn't include prerendered pages
|
||||
fs.writeFileSync(
|
||||
`${out}/server/manifest.js`,
|
||||
`export const manifest = ${generate_manifest({
|
||||
build_data,
|
||||
relative_path: '.',
|
||||
routes: manifest_data.routes.filter((route) => prerender_map.get(route.id) !== true)
|
||||
})};\n`
|
||||
);
|
||||
|
||||
if (service_worker_entry_file) {
|
||||
if (kit.paths.assets) {
|
||||
throw new Error('Cannot use service worker alongside config.kit.paths.assets');
|
||||
}
|
||||
|
||||
log.info('Building service worker');
|
||||
|
||||
await build_service_worker(
|
||||
out,
|
||||
kit,
|
||||
vite_config,
|
||||
manifest_data,
|
||||
service_worker_entry_file,
|
||||
prerendered,
|
||||
client_manifest
|
||||
);
|
||||
}
|
||||
|
||||
// we need to defer this to closeBundle, so that adapters copy files
|
||||
// created by other Vite plugins
|
||||
finalise = async () => {
|
||||
console.log(
|
||||
`\nRun ${colors
|
||||
.bold()
|
||||
.cyan('npm run preview')} to preview your production build locally.`
|
||||
);
|
||||
|
||||
if (kit.adapter) {
|
||||
const { adapt } = await import('../../core/adapt/index.js');
|
||||
await adapt(
|
||||
svelte_config,
|
||||
build_data,
|
||||
metadata,
|
||||
prerendered,
|
||||
prerender_map,
|
||||
log,
|
||||
vite_config
|
||||
);
|
||||
} else {
|
||||
console.log(colors.bold().yellow('\nNo adapter specified'));
|
||||
|
||||
const link = colors.bold().cyan('https://kit.svelte.dev/docs/adapters');
|
||||
console.log(
|
||||
`See ${link} to learn how to configure your app to run on the platform of your choosing`
|
||||
);
|
||||
}
|
||||
|
||||
secondary_build_started = false;
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs the adapter.
|
||||
*/
|
||||
closeBundle: {
|
||||
sequential: true,
|
||||
async handler() {
|
||||
if (!vite_config.build.ssr) return;
|
||||
await finalise?.();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return [plugin_setup, plugin_virtual_modules, plugin_guard, plugin_compile];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, any>} config
|
||||
* @param {Record<string, any>} resolved_config
|
||||
*/
|
||||
function warn_overridden_config(config, resolved_config) {
|
||||
const overridden = find_overridden_config(config, resolved_config, enforced_config, '', []);
|
||||
|
||||
if (overridden.length > 0) {
|
||||
console.error(
|
||||
colors.bold().red('The following Vite config options will be overridden by SvelteKit:') +
|
||||
overridden.map((key) => `\n - ${key}`).join('')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, any>} config
|
||||
* @param {Record<string, any>} resolved_config
|
||||
* @param {import('./types.js').EnforcedConfig} enforced_config
|
||||
* @param {string} path
|
||||
* @param {string[]} out used locally to compute the return value
|
||||
*/
|
||||
function find_overridden_config(config, resolved_config, enforced_config, path, out) {
|
||||
if (config == null || resolved_config == null) {
|
||||
return out;
|
||||
}
|
||||
|
||||
for (const key in enforced_config) {
|
||||
if (typeof config === 'object' && key in config && key in resolved_config) {
|
||||
const enforced = enforced_config[key];
|
||||
|
||||
if (enforced === true) {
|
||||
if (config[key] !== resolved_config[key]) {
|
||||
out.push(path + key);
|
||||
}
|
||||
} else {
|
||||
find_overridden_config(config[key], resolved_config[key], enforced, path + key + '.', out);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ValidatedConfig} config
|
||||
*/
|
||||
const create_service_worker_module = (config) => dedent`
|
||||
if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
|
||||
throw new Error('This module can only be imported inside a service worker');
|
||||
}
|
||||
|
||||
export const build = [];
|
||||
export const files = [
|
||||
${create_assets(config)
|
||||
.filter((asset) => config.kit.serviceWorker.files(asset.file))
|
||||
.map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
|
||||
.join(',\n')}
|
||||
];
|
||||
export const prerendered = [];
|
||||
export const version = ${s(config.kit.version.name)};
|
||||
`;
|
221
node_modules/@sveltejs/kit/src/exports/vite/preview/index.js
generated
vendored
Normal file
221
node_modules/@sveltejs/kit/src/exports/vite/preview/index.js
generated
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
import fs from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { lookup } from 'mrmime';
|
||||
import sirv from 'sirv';
|
||||
import { loadEnv, normalizePath } from 'vite';
|
||||
import { getRequest, setResponse } from '../../../exports/node/index.js';
|
||||
import { installPolyfills } from '../../../exports/node/polyfills.js';
|
||||
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
||||
import { should_polyfill } from '../../../utils/platform.js';
|
||||
import { not_found } from '../utils.js';
|
||||
|
||||
/** @typedef {import('http').IncomingMessage} Req */
|
||||
/** @typedef {import('http').ServerResponse} Res */
|
||||
/** @typedef {(req: Req, res: Res, next: () => void) => void} Handler */
|
||||
|
||||
/**
|
||||
* @param {{ middlewares: import('connect').Server }} vite
|
||||
* @param {import('vite').ResolvedConfig} vite_config
|
||||
* @param {import('types').ValidatedConfig} svelte_config
|
||||
*/
|
||||
export async function preview(vite, vite_config, svelte_config) {
|
||||
if (should_polyfill) {
|
||||
installPolyfills();
|
||||
}
|
||||
|
||||
const { paths } = svelte_config.kit;
|
||||
const base = paths.base;
|
||||
const assets = paths.assets ? SVELTE_KIT_ASSETS : paths.base;
|
||||
|
||||
const protocol = vite_config.preview.https ? 'https' : 'http';
|
||||
|
||||
const etag = `"${Date.now()}"`;
|
||||
|
||||
const dir = join(svelte_config.kit.outDir, 'output/server');
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
throw new Error(`Server files not found at ${dir}, did you run \`build\` first?`);
|
||||
}
|
||||
|
||||
/** @type {import('types').ServerInternalModule} */
|
||||
const { set_assets } = await import(pathToFileURL(join(dir, 'internal.js')).href);
|
||||
|
||||
/** @type {import('types').ServerModule} */
|
||||
const { Server } = await import(pathToFileURL(join(dir, 'index.js')).href);
|
||||
|
||||
const { manifest } = await import(pathToFileURL(join(dir, 'manifest.js')).href);
|
||||
|
||||
set_assets(assets);
|
||||
|
||||
const server = new Server(manifest);
|
||||
await server.init({
|
||||
env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, '')
|
||||
});
|
||||
|
||||
return () => {
|
||||
// generated client assets and the contents of `static`
|
||||
vite.middlewares.use(
|
||||
scoped(
|
||||
assets,
|
||||
sirv(join(svelte_config.kit.outDir, 'output/client'), {
|
||||
setHeaders: (res, pathname) => {
|
||||
// only apply to immutable directory, not e.g. version.json
|
||||
if (pathname.startsWith(`/${svelte_config.kit.appDir}/immutable`)) {
|
||||
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
vite.middlewares.use((req, res, next) => {
|
||||
const original_url = /** @type {string} */ (req.url);
|
||||
const { pathname } = new URL(original_url, 'http://dummy');
|
||||
|
||||
if (pathname.startsWith(base)) {
|
||||
next();
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
not_found(req, res, base);
|
||||
}
|
||||
});
|
||||
|
||||
// prerendered dependencies
|
||||
vite.middlewares.use(
|
||||
scoped(base, mutable(join(svelte_config.kit.outDir, 'output/prerendered/dependencies')))
|
||||
);
|
||||
|
||||
// prerendered pages (we can't just use sirv because we need to
|
||||
// preserve the correct trailingSlash behaviour)
|
||||
vite.middlewares.use(
|
||||
scoped(base, (req, res, next) => {
|
||||
let if_none_match_value = req.headers['if-none-match'];
|
||||
|
||||
if (if_none_match_value?.startsWith('W/"')) {
|
||||
if_none_match_value = if_none_match_value.substring(2);
|
||||
}
|
||||
|
||||
if (if_none_match_value === etag) {
|
||||
res.statusCode = 304;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const { pathname } = new URL(/** @type {string} */ (req.url), 'http://dummy');
|
||||
|
||||
let filename = normalizePath(
|
||||
join(svelte_config.kit.outDir, 'output/prerendered/pages' + pathname)
|
||||
);
|
||||
let prerendered = is_file(filename);
|
||||
|
||||
if (!prerendered) {
|
||||
const has_trailing_slash = pathname.endsWith('/');
|
||||
const html_filename = `${filename}${has_trailing_slash ? 'index.html' : '.html'}`;
|
||||
|
||||
let redirect;
|
||||
|
||||
if (is_file(html_filename)) {
|
||||
filename = html_filename;
|
||||
prerendered = true;
|
||||
} else if (has_trailing_slash) {
|
||||
if (is_file(filename.slice(0, -1) + '.html')) {
|
||||
redirect = pathname.slice(0, -1);
|
||||
}
|
||||
} else if (is_file(filename + '/index.html')) {
|
||||
redirect = pathname + '/';
|
||||
}
|
||||
|
||||
if (redirect) {
|
||||
res.writeHead(307, {
|
||||
location: redirect
|
||||
});
|
||||
|
||||
res.end();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (prerendered) {
|
||||
res.writeHead(200, {
|
||||
'content-type': lookup(pathname) || 'text/html',
|
||||
etag
|
||||
});
|
||||
|
||||
fs.createReadStream(filename).pipe(res);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// SSR
|
||||
vite.middlewares.use(async (req, res) => {
|
||||
const host = req.headers['host'];
|
||||
|
||||
let request;
|
||||
|
||||
try {
|
||||
request = await getRequest({
|
||||
base: `${protocol}://${host}`,
|
||||
request: req
|
||||
});
|
||||
} catch (/** @type {any} */ err) {
|
||||
res.statusCode = err.status || 400;
|
||||
return res.end('Invalid request body');
|
||||
}
|
||||
|
||||
setResponse(
|
||||
res,
|
||||
await server.respond(request, {
|
||||
getClientAddress: () => {
|
||||
const { remoteAddress } = req.socket;
|
||||
if (remoteAddress) return remoteAddress;
|
||||
throw new Error('Could not determine clientAddress');
|
||||
},
|
||||
read: (file) => fs.readFileSync(join(svelte_config.kit.files.assets, file))
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Handler}
|
||||
*/
|
||||
const mutable = (dir) =>
|
||||
fs.existsSync(dir)
|
||||
? sirv(dir, {
|
||||
etag: true,
|
||||
maxAge: 0
|
||||
})
|
||||
: (_req, _res, next) => next();
|
||||
|
||||
/**
|
||||
* @param {string} scope
|
||||
* @param {Handler} handler
|
||||
* @returns {Handler}
|
||||
*/
|
||||
function scoped(scope, handler) {
|
||||
if (scope === '') return handler;
|
||||
|
||||
return (req, res, next) => {
|
||||
if (req.url?.startsWith(scope)) {
|
||||
const original_url = req.url;
|
||||
req.url = req.url.slice(scope.length);
|
||||
handler(req, res, () => {
|
||||
req.url = original_url;
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {string} path */
|
||||
function is_file(path) {
|
||||
return fs.existsSync(path) && !fs.statSync(path).isDirectory();
|
||||
}
|
3
node_modules/@sveltejs/kit/src/exports/vite/types.d.ts
generated
vendored
Normal file
3
node_modules/@sveltejs/kit/src/exports/vite/types.d.ts
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export interface EnforcedConfig {
|
||||
[key: string]: EnforcedConfig | true;
|
||||
}
|
101
node_modules/@sveltejs/kit/src/exports/vite/utils.js
generated
vendored
Normal file
101
node_modules/@sveltejs/kit/src/exports/vite/utils.js
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
import path from 'node:path';
|
||||
import { loadEnv } from 'vite';
|
||||
import { posixify } from '../../utils/filesystem.js';
|
||||
import { negotiate } from '../../utils/http.js';
|
||||
import { filter_private_env, filter_public_env } from '../../utils/env.js';
|
||||
|
||||
/**
|
||||
* Transforms kit.alias to a valid vite.resolve.alias array.
|
||||
*
|
||||
* Related to tsconfig path alias creation.
|
||||
*
|
||||
* @param {import('types').ValidatedKitConfig} config
|
||||
* */
|
||||
export function get_config_aliases(config) {
|
||||
/** @type {import('vite').Alias[]} */
|
||||
const alias = [
|
||||
// For now, we handle `$lib` specially here rather than make it a default value for
|
||||
// `config.kit.alias` since it has special meaning for packaging, etc.
|
||||
{ find: '$lib', replacement: config.files.lib }
|
||||
];
|
||||
|
||||
for (let [key, value] of Object.entries(config.alias)) {
|
||||
value = posixify(value);
|
||||
if (value.endsWith('/*')) {
|
||||
value = value.slice(0, -2);
|
||||
}
|
||||
if (key.endsWith('/*')) {
|
||||
// Doing just `{ find: key.slice(0, -2) ,..}` would mean `import .. from "key"` would also be matched, which we don't want
|
||||
alias.push({
|
||||
find: new RegExp(`^${escape_for_regexp(key.slice(0, -2))}\\/(.+)$`),
|
||||
replacement: `${path.resolve(value)}/$1`
|
||||
});
|
||||
} else if (key + '/*' in config.alias) {
|
||||
// key and key/* both exist -> the replacement for key needs to happen _only_ on import .. from "key"
|
||||
alias.push({
|
||||
find: new RegExp(`^${escape_for_regexp(key)}$`),
|
||||
replacement: path.resolve(value)
|
||||
});
|
||||
} else {
|
||||
alias.push({ find: key, replacement: path.resolve(value) });
|
||||
}
|
||||
}
|
||||
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
*/
|
||||
function escape_for_regexp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables from process.env and .env files
|
||||
* @param {import('types').ValidatedKitConfig['env']} env_config
|
||||
* @param {string} mode
|
||||
*/
|
||||
export function get_env(env_config, mode) {
|
||||
const { publicPrefix: public_prefix, privatePrefix: private_prefix } = env_config;
|
||||
const env = loadEnv(mode, env_config.dir, '');
|
||||
|
||||
return {
|
||||
public: filter_public_env(env, { public_prefix, private_prefix }),
|
||||
private: filter_private_env(env, { public_prefix, private_prefix })
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('http').IncomingMessage} req
|
||||
* @param {import('http').ServerResponse} res
|
||||
* @param {string} base
|
||||
*/
|
||||
export function not_found(req, res, base) {
|
||||
const type = negotiate(req.headers.accept ?? '*', ['text/plain', 'text/html']);
|
||||
|
||||
// special case — handle `/` request automatically
|
||||
if (req.url === '/' && type === 'text/html') {
|
||||
res.statusCode = 307;
|
||||
res.setHeader('location', base);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.statusCode = 404;
|
||||
|
||||
const prefixed = base + req.url;
|
||||
|
||||
if (type === 'text/html') {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(
|
||||
`The server is configured with a public base URL of ${base} - did you mean to visit <a href="${prefixed}">${prefixed}</a> instead?`
|
||||
);
|
||||
} else {
|
||||
res.end(
|
||||
`The server is configured with a public base URL of ${base} - did you mean to visit ${prefixed} instead?`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const strip_virtual_prefix = /** @param {string} id */ (id) => id.replace('\0virtual:', '');
|
1
node_modules/@sveltejs/kit/src/runtime/app/env.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/app/env.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
throw new Error('$app/env has been renamed to $app/environment');
|
12
node_modules/@sveltejs/kit/src/runtime/app/environment.js
generated
vendored
Normal file
12
node_modules/@sveltejs/kit/src/runtime/app/environment.js
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { BROWSER, DEV } from 'esm-env';
|
||||
export { building, version } from '__sveltekit/environment';
|
||||
|
||||
/**
|
||||
* `true` if the app is running in the browser.
|
||||
*/
|
||||
export const browser = BROWSER;
|
||||
|
||||
/**
|
||||
* Whether the dev server is running. This is not guaranteed to correspond to `NODE_ENV` or `MODE`.
|
||||
*/
|
||||
export const dev = DEV;
|
254
node_modules/@sveltejs/kit/src/runtime/app/forms.js
generated
vendored
Normal file
254
node_modules/@sveltejs/kit/src/runtime/app/forms.js
generated
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
import * as devalue from 'devalue';
|
||||
import { BROWSER, DEV } from 'esm-env';
|
||||
import { client } from '../client/singletons.js';
|
||||
import { invalidateAll } from './navigation.js';
|
||||
|
||||
/**
|
||||
* This action updates the `form` property of the current page with the given data and updates `$page.status`.
|
||||
* In case of an error, it redirects to the nearest error page.
|
||||
* @template {Record<string, unknown> | undefined} Success
|
||||
* @template {Record<string, unknown> | undefined} Failure
|
||||
* @param {import('@sveltejs/kit').ActionResult<Success, Failure>} result
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function applyAction(result) {
|
||||
if (BROWSER) {
|
||||
return client.apply_action(result);
|
||||
} else {
|
||||
throw new Error('Cannot call applyAction(...) on the server');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to deserialize the response from a form submission.
|
||||
* Usage:
|
||||
*
|
||||
* ```js
|
||||
* import { deserialize } from '$app/forms';
|
||||
*
|
||||
* async function handleSubmit(event) {
|
||||
* const response = await fetch('/form?/action', {
|
||||
* method: 'POST',
|
||||
* body: new FormData(event.target)
|
||||
* });
|
||||
*
|
||||
* const result = deserialize(await response.text());
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* @template {Record<string, unknown> | undefined} Success
|
||||
* @template {Record<string, unknown> | undefined} Failure
|
||||
* @param {string} result
|
||||
* @returns {import('@sveltejs/kit').ActionResult<Success, Failure>}
|
||||
*/
|
||||
export function deserialize(result) {
|
||||
const parsed = JSON.parse(result);
|
||||
if (parsed.data) {
|
||||
parsed.data = devalue.parse(parsed.data);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} old_name
|
||||
* @param {string} new_name
|
||||
* @param {string} call_location
|
||||
* @returns void
|
||||
*/
|
||||
function warn_on_access(old_name, new_name, call_location) {
|
||||
if (!DEV) return;
|
||||
// TODO 2.0: Remove this code
|
||||
console.warn(
|
||||
`\`${old_name}\` has been deprecated in favor of \`${new_name}\`. \`${old_name}\` will be removed in a future version. (Called from ${call_location})`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow clone an element, so that we can access e.g. `form.action` without worrying
|
||||
* that someone has added an `<input name="action">` (https://github.com/sveltejs/kit/issues/7593)
|
||||
* @template {HTMLElement} T
|
||||
* @param {T} element
|
||||
* @returns {T}
|
||||
*/
|
||||
function clone(element) {
|
||||
return /** @type {T} */ (HTMLElement.prototype.cloneNode.call(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* This action enhances a `<form>` element that otherwise would work without JavaScript.
|
||||
*
|
||||
* The `submit` function is called upon submission with the given FormData and the `action` that should be triggered.
|
||||
* If `cancel` is called, the form will not be submitted.
|
||||
* You can use the abort `controller` to cancel the submission in case another one starts.
|
||||
* If a function is returned, that function is called with the response from the server.
|
||||
* If nothing is returned, the fallback will be used.
|
||||
*
|
||||
* If this function or its return value isn't set, it
|
||||
* - falls back to updating the `form` prop with the returned data if the action is one same page as the form
|
||||
* - updates `$page.status`
|
||||
* - resets the `<form>` element and invalidates all data in case of successful submission with no redirect response
|
||||
* - redirects in case of a redirect response
|
||||
* - redirects to the nearest error page in case of an unexpected error
|
||||
*
|
||||
* If you provide a custom function with a callback and want to use the default behavior, invoke `update` in your callback.
|
||||
* @template {Record<string, unknown> | undefined} Success
|
||||
* @template {Record<string, unknown> | undefined} Failure
|
||||
* @param {HTMLFormElement} form_element The form element
|
||||
* @param {import('@sveltejs/kit').SubmitFunction<Success, Failure>} submit Submit callback
|
||||
*/
|
||||
export function enhance(form_element, submit = () => {}) {
|
||||
if (DEV && clone(form_element).method !== 'post') {
|
||||
throw new Error('use:enhance can only be used on <form> fields with method="POST"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* action: URL;
|
||||
* invalidateAll?: boolean;
|
||||
* result: import('@sveltejs/kit').ActionResult;
|
||||
* reset?: boolean
|
||||
* }} opts
|
||||
*/
|
||||
const fallback_callback = async ({
|
||||
action,
|
||||
result,
|
||||
reset = true,
|
||||
invalidateAll: shouldInvalidateAll = true
|
||||
}) => {
|
||||
if (result.type === 'success') {
|
||||
if (reset) {
|
||||
// We call reset from the prototype to avoid DOM clobbering
|
||||
HTMLFormElement.prototype.reset.call(form_element);
|
||||
}
|
||||
if (shouldInvalidateAll) {
|
||||
await invalidateAll();
|
||||
}
|
||||
}
|
||||
|
||||
// For success/failure results, only apply action if it belongs to the
|
||||
// current page, otherwise `form` will be updated erroneously
|
||||
if (
|
||||
location.origin + location.pathname === action.origin + action.pathname ||
|
||||
result.type === 'redirect' ||
|
||||
result.type === 'error'
|
||||
) {
|
||||
applyAction(result);
|
||||
}
|
||||
};
|
||||
|
||||
/** @param {SubmitEvent} event */
|
||||
async function handle_submit(event) {
|
||||
const method = event.submitter?.hasAttribute('formmethod')
|
||||
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formMethod
|
||||
: clone(form_element).method;
|
||||
if (method !== 'post') return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const action = new URL(
|
||||
// We can't do submitter.formAction directly because that property is always set
|
||||
event.submitter?.hasAttribute('formaction')
|
||||
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
|
||||
: clone(form_element).action
|
||||
);
|
||||
|
||||
const form_data = new FormData(form_element);
|
||||
|
||||
if (DEV && clone(form_element).enctype !== 'multipart/form-data') {
|
||||
for (const value of form_data.values()) {
|
||||
if (value instanceof File) {
|
||||
// TODO 2.0: Upgrade to `throw Error`
|
||||
console.warn(
|
||||
'Your form contains <input type="file"> fields, but is missing the `enctype="multipart/form-data"` attribute. This will lead to inconsistent behavior between enhanced and native forms. For more details, see https://github.com/sveltejs/kit/issues/9819. This will be upgraded to an error in v2.0.'
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const submitter_name = event.submitter?.getAttribute('name');
|
||||
if (submitter_name) {
|
||||
form_data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
let cancelled = false;
|
||||
const cancel = () => (cancelled = true);
|
||||
|
||||
// TODO 2.0: Remove `data` and `form`
|
||||
const callback =
|
||||
(await submit({
|
||||
action,
|
||||
cancel,
|
||||
controller,
|
||||
get data() {
|
||||
warn_on_access('data', 'formData', 'use:enhance submit function');
|
||||
return form_data;
|
||||
},
|
||||
formData: form_data,
|
||||
get form() {
|
||||
warn_on_access('form', 'formElement', 'use:enhance submit function');
|
||||
return form_element;
|
||||
},
|
||||
formElement: form_element,
|
||||
submitter: event.submitter
|
||||
})) ?? fallback_callback;
|
||||
if (cancelled) return;
|
||||
|
||||
/** @type {import('@sveltejs/kit').ActionResult} */
|
||||
let result;
|
||||
|
||||
try {
|
||||
const response = await fetch(action, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'x-sveltekit-action': 'true'
|
||||
},
|
||||
cache: 'no-store',
|
||||
body: form_data,
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
result = deserialize(await response.text());
|
||||
if (result.type === 'error') result.status = response.status;
|
||||
} catch (error) {
|
||||
if (/** @type {any} */ (error)?.name === 'AbortError') return;
|
||||
result = { type: 'error', error };
|
||||
}
|
||||
|
||||
callback({
|
||||
action,
|
||||
get data() {
|
||||
warn_on_access('data', 'formData', 'callback returned from use:enhance submit function');
|
||||
return form_data;
|
||||
},
|
||||
formData: form_data,
|
||||
get form() {
|
||||
warn_on_access('form', 'formElement', 'callback returned from use:enhance submit function');
|
||||
return form_element;
|
||||
},
|
||||
formElement: form_element,
|
||||
update: (opts) =>
|
||||
fallback_callback({
|
||||
action,
|
||||
result,
|
||||
reset: opts?.reset,
|
||||
invalidateAll: opts?.invalidateAll
|
||||
}),
|
||||
// @ts-expect-error generic constraints stuff we don't care about
|
||||
result
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
HTMLFormElement.prototype.addEventListener.call(form_element, 'submit', handle_submit);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
// @ts-expect-error
|
||||
HTMLFormElement.prototype.removeEventListener.call(form_element, 'submit', handle_submit);
|
||||
}
|
||||
};
|
||||
}
|
126
node_modules/@sveltejs/kit/src/runtime/app/navigation.js
generated
vendored
Normal file
126
node_modules/@sveltejs/kit/src/runtime/app/navigation.js
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
import { client_method } from '../client/singletons.js';
|
||||
|
||||
/**
|
||||
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
|
||||
* This is generally discouraged, since it breaks user expectations.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const disableScrollHandling = /* @__PURE__ */ client_method('disable_scroll_handling');
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
|
||||
* For external URLs, use `window.location = url` instead of calling `goto(url)`.
|
||||
*
|
||||
* @type {(url: string | URL, opts?: {
|
||||
* replaceState?: boolean;
|
||||
* noScroll?: boolean;
|
||||
* keepFocus?: boolean;
|
||||
* invalidateAll?: boolean;
|
||||
* state?: any
|
||||
* }) => Promise<void>}
|
||||
* @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app.
|
||||
* @param {Object} [opts] Options related to the navigation
|
||||
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
|
||||
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
|
||||
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
|
||||
* @param {boolean} [invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation.
|
||||
* @param {any} [opts.state] The state of the new/updated history entry
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const goto = /* @__PURE__ */ client_method('goto');
|
||||
|
||||
/**
|
||||
* Causes any `load` functions belonging to the currently active page to re-run if they depend on the `url` in question, via `fetch` or `depends`. Returns a `Promise` that resolves when the page is subsequently updated.
|
||||
*
|
||||
* If the argument is given as a `string` or `URL`, it must resolve to the same URL that was passed to `fetch` or `depends` (including query parameters).
|
||||
* To create a custom identifier, use a string beginning with `[a-z]+:` (e.g. `custom:state`) — this is a valid URL.
|
||||
*
|
||||
* The `function` argument can be used define a custom predicate. It receives the full `URL` and causes `load` to rerun if `true` is returned.
|
||||
* This can be useful if you want to invalidate based on a pattern instead of a exact match.
|
||||
*
|
||||
* ```ts
|
||||
* // Example: Match '/path' regardless of the query parameters
|
||||
* import { invalidate } from '$app/navigation';
|
||||
*
|
||||
* invalidate((url) => url.pathname === '/path');
|
||||
* ```
|
||||
* @type {(url: string | URL | ((url: URL) => boolean)) => Promise<void>}
|
||||
* @param {string | URL | ((url: URL) => boolean)} url The invalidated URL
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const invalidate = /* @__PURE__ */ client_method('invalidate');
|
||||
|
||||
/**
|
||||
* Causes all `load` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
|
||||
* @type {() => Promise<void>}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const invalidateAll = /* @__PURE__ */ client_method('invalidate_all');
|
||||
|
||||
/**
|
||||
* Programmatically preloads the given page, which means
|
||||
* 1. ensuring that the code for the page is loaded, and
|
||||
* 2. calling the page's load function with the appropriate options.
|
||||
*
|
||||
* This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `data-sveltekit-preload-data`.
|
||||
* If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous.
|
||||
* Returns a Promise that resolves when the preload is complete.
|
||||
*
|
||||
* @type {(href: string) => Promise<void>}
|
||||
* @param {string} href Page to preload
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const preloadData = /* @__PURE__ */ client_method('preload_data');
|
||||
|
||||
/**
|
||||
* Programmatically imports the code for routes that haven't yet been fetched.
|
||||
* Typically, you might call this to speed up subsequent navigation.
|
||||
*
|
||||
* You can specify routes by any matching pathname such as `/about` (to match `src/routes/about/+page.svelte`) or `/blog/*` (to match `src/routes/blog/[slug]/+page.svelte`).
|
||||
*
|
||||
* Unlike `preloadData`, this won't call `load` functions.
|
||||
* Returns a Promise that resolves when the modules have been imported.
|
||||
*
|
||||
* @type {(...urls: string[]) => Promise<void>}
|
||||
* @param {...string[]} urls
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const preloadCode = /* @__PURE__ */ client_method('preload_code');
|
||||
|
||||
/**
|
||||
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
|
||||
* Calling `cancel()` will prevent the navigation from completing. If the navigation would have directly unloaded the current page, calling `cancel` will trigger the native
|
||||
* browser unload confirmation dialog. In these cases, `navigation.willUnload` is `true`.
|
||||
*
|
||||
* When a navigation isn't client side, `navigation.to.route.id` will be `null`.
|
||||
*
|
||||
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
||||
* @type {(callback: (navigation: import('@sveltejs/kit').BeforeNavigate) => void) => void}
|
||||
* @param {(navigation: import('@sveltejs/kit').BeforeNavigate) => void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
export const beforeNavigate = /* @__PURE__ */ client_method('before_navigate');
|
||||
|
||||
/**
|
||||
* A lifecycle function that runs the supplied `callback` immediately before we navigate to a new URL.
|
||||
*
|
||||
* If you return a `Promise`, SvelteKit will wait for it to resolve before completing the navigation. This allows you to — for example — use `document.startViewTransition`. Avoid promises that are slow to resolve, since navigation will appear stalled to the user.
|
||||
*
|
||||
* If a function (or a `Promise` that resolves to a function) is returned from the callback, it will be called once the DOM has updated.
|
||||
*
|
||||
* `onNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
||||
* @type {(callback: (navigation: import('@sveltejs/kit').OnNavigate) => import('../../types/internal.js').MaybePromise<(() => void) | void>) => void}
|
||||
* @param {(navigation: import('@sveltejs/kit').OnNavigate) => void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
export const onNavigate = /* @__PURE__ */ client_method('on_navigate');
|
||||
|
||||
/**
|
||||
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
|
||||
*
|
||||
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
|
||||
* @type {(callback: (navigation: import('@sveltejs/kit').AfterNavigate) => void) => void}
|
||||
* @param {(navigation: import('@sveltejs/kit').AfterNavigate) => void} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
export const afterNavigate = /* @__PURE__ */ client_method('after_navigate');
|
1
node_modules/@sveltejs/kit/src/runtime/app/paths.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/app/paths.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { base, assets } from '__sveltekit/paths';
|
94
node_modules/@sveltejs/kit/src/runtime/app/stores.js
generated
vendored
Normal file
94
node_modules/@sveltejs/kit/src/runtime/app/stores.js
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
import { getContext } from 'svelte';
|
||||
import { browser } from './environment.js';
|
||||
import { stores as browser_stores } from '../client/singletons.js';
|
||||
|
||||
/**
|
||||
* A function that returns all of the contextual stores. On the server, this must be called during component initialization.
|
||||
* Only use this if you need to defer store subscription until after the component has mounted, for some reason.
|
||||
*/
|
||||
export const getStores = () => {
|
||||
const stores = browser ? browser_stores : getContext('__svelte__');
|
||||
|
||||
return {
|
||||
/** @type {typeof page} */
|
||||
page: {
|
||||
subscribe: stores.page.subscribe
|
||||
},
|
||||
/** @type {typeof navigating} */
|
||||
navigating: {
|
||||
subscribe: stores.navigating.subscribe
|
||||
},
|
||||
/** @type {typeof updated} */
|
||||
updated: stores.updated
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A readable store whose value contains page data.
|
||||
*
|
||||
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
|
||||
*
|
||||
* @type {import('svelte/store').Readable<import('@sveltejs/kit').Page>}
|
||||
*/
|
||||
export const page = {
|
||||
subscribe(fn) {
|
||||
const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
|
||||
return store.subscribe(fn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A readable store.
|
||||
* When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
|
||||
* When navigating finishes, its value reverts to `null`.
|
||||
*
|
||||
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
|
||||
* @type {import('svelte/store').Readable<import('@sveltejs/kit').Navigation | null>}
|
||||
*/
|
||||
export const navigating = {
|
||||
subscribe(fn) {
|
||||
const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
|
||||
return store.subscribe(fn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
|
||||
*
|
||||
* On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
|
||||
* @type {import('svelte/store').Readable<boolean> & { check(): Promise<boolean> }}
|
||||
*/
|
||||
export const updated = {
|
||||
subscribe(fn) {
|
||||
const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;
|
||||
|
||||
if (browser) {
|
||||
updated.check = store.check;
|
||||
}
|
||||
|
||||
return store.subscribe(fn);
|
||||
},
|
||||
check: () => {
|
||||
throw new Error(
|
||||
browser
|
||||
? 'Cannot check updated store before subscribing'
|
||||
: 'Can only check updated store in browser'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @template {keyof ReturnType<typeof getStores>} Name
|
||||
* @param {Name} name
|
||||
* @returns {ReturnType<typeof getStores>[Name]}
|
||||
*/
|
||||
function get_store(name) {
|
||||
try {
|
||||
return getStores()[name];
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.` +
|
||||
'For more information, see https://kit.svelte.dev/docs/state-management#avoid-shared-state-on-the-server'
|
||||
);
|
||||
}
|
||||
}
|
2090
node_modules/@sveltejs/kit/src/runtime/client/client.js
generated
vendored
Normal file
2090
node_modules/@sveltejs/kit/src/runtime/client/client.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
11
node_modules/@sveltejs/kit/src/runtime/client/constants.js
generated
vendored
Normal file
11
node_modules/@sveltejs/kit/src/runtime/client/constants.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
export const SNAPSHOT_KEY = 'sveltekit:snapshot';
|
||||
export const SCROLL_KEY = 'sveltekit:scroll';
|
||||
export const INDEX_KEY = 'sveltekit:index';
|
||||
|
||||
export const PRELOAD_PRIORITIES = /** @type {const} */ ({
|
||||
tap: 1,
|
||||
hover: 2,
|
||||
viewport: 3,
|
||||
eager: 4,
|
||||
off: -1
|
||||
});
|
167
node_modules/@sveltejs/kit/src/runtime/client/fetcher.js
generated
vendored
Normal file
167
node_modules/@sveltejs/kit/src/runtime/client/fetcher.js
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { hash } from '../hash.js';
|
||||
|
||||
let loading = 0;
|
||||
|
||||
export const native_fetch = window.fetch;
|
||||
|
||||
export function lock_fetch() {
|
||||
loading += 1;
|
||||
}
|
||||
|
||||
export function unlock_fetch() {
|
||||
loading -= 1;
|
||||
}
|
||||
|
||||
if (DEV) {
|
||||
let can_inspect_stack_trace = false;
|
||||
|
||||
const check_stack_trace = async () => {
|
||||
const stack = /** @type {string} */ (new Error().stack);
|
||||
can_inspect_stack_trace = stack.includes('check_stack_trace');
|
||||
};
|
||||
|
||||
check_stack_trace();
|
||||
|
||||
/**
|
||||
* @param {RequestInfo | URL} input
|
||||
* @param {RequestInit & Record<string, any> | undefined} init
|
||||
*/
|
||||
window.fetch = (input, init) => {
|
||||
// Check if fetch was called via load_node. the lock method only checks if it was called at the
|
||||
// same time, but not necessarily if it was called from `load`.
|
||||
// We use just the filename as the method name sometimes does not appear on the CI.
|
||||
const url = input instanceof Request ? input.url : input.toString();
|
||||
const stack_array = /** @type {string} */ (new Error().stack).split('\n');
|
||||
// We need to do a cutoff because Safari and Firefox maintain the stack
|
||||
// across events and for example traces a `fetch` call triggered from a button
|
||||
// back to the creation of the event listener and the element creation itself,
|
||||
// where at some point client.js will show up, leading to false positives.
|
||||
const cutoff = stack_array.findIndex((a) => a.includes('load@') || a.includes('at load'));
|
||||
const stack = stack_array.slice(0, cutoff + 2).join('\n');
|
||||
|
||||
const in_load_heuristic = can_inspect_stack_trace
|
||||
? stack.includes('src/runtime/client/client.js')
|
||||
: loading;
|
||||
|
||||
// This flag is set in initial_fetch and subsequent_fetch
|
||||
const used_kit_fetch = init?.__sveltekit_fetch__;
|
||||
|
||||
if (in_load_heuristic && !used_kit_fetch) {
|
||||
console.warn(
|
||||
`Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://kit.svelte.dev/docs/load#making-fetch-requests`
|
||||
);
|
||||
}
|
||||
|
||||
const method = input instanceof Request ? input.method : init?.method || 'GET';
|
||||
|
||||
if (method !== 'GET') {
|
||||
cache.delete(build_selector(input));
|
||||
}
|
||||
|
||||
return native_fetch(input, init);
|
||||
};
|
||||
} else {
|
||||
window.fetch = (input, init) => {
|
||||
const method = input instanceof Request ? input.method : init?.method || 'GET';
|
||||
|
||||
if (method !== 'GET') {
|
||||
cache.delete(build_selector(input));
|
||||
}
|
||||
|
||||
return native_fetch(input, init);
|
||||
};
|
||||
}
|
||||
|
||||
const cache = new Map();
|
||||
|
||||
/**
|
||||
* Should be called on the initial run of load functions that hydrate the page.
|
||||
* Saves any requests with cache-control max-age to the cache.
|
||||
* @param {URL | string} resource
|
||||
* @param {RequestInit} [opts]
|
||||
*/
|
||||
export function initial_fetch(resource, opts) {
|
||||
const selector = build_selector(resource, opts);
|
||||
|
||||
const script = document.querySelector(selector);
|
||||
if (script?.textContent) {
|
||||
const { body, ...init } = JSON.parse(script.textContent);
|
||||
|
||||
const ttl = script.getAttribute('data-ttl');
|
||||
if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
|
||||
|
||||
return Promise.resolve(new Response(body, init));
|
||||
}
|
||||
|
||||
return DEV ? dev_fetch(resource, opts) : window.fetch(resource, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the response from the cache, if max-age allows it, else does a fetch.
|
||||
* @param {URL | string} resource
|
||||
* @param {string} resolved
|
||||
* @param {RequestInit} [opts]
|
||||
*/
|
||||
export function subsequent_fetch(resource, resolved, opts) {
|
||||
if (cache.size > 0) {
|
||||
const selector = build_selector(resource, opts);
|
||||
const cached = cache.get(selector);
|
||||
if (cached) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Request/cache#value
|
||||
if (
|
||||
performance.now() < cached.ttl &&
|
||||
['default', 'force-cache', 'only-if-cached', undefined].includes(opts?.cache)
|
||||
) {
|
||||
return new Response(cached.body, cached.init);
|
||||
}
|
||||
|
||||
cache.delete(selector);
|
||||
}
|
||||
}
|
||||
|
||||
return DEV ? dev_fetch(resolved, opts) : window.fetch(resolved, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RequestInfo | URL} resource
|
||||
* @param {RequestInit & Record<string, any> | undefined} opts
|
||||
*/
|
||||
function dev_fetch(resource, opts) {
|
||||
const patched_opts = { ...opts };
|
||||
// This assigns the __sveltekit_fetch__ flag and makes it non-enumerable
|
||||
Object.defineProperty(patched_opts, '__sveltekit_fetch__', {
|
||||
value: true,
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
return window.fetch(resource, patched_opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the cache key for a given request
|
||||
* @param {URL | RequestInfo} resource
|
||||
* @param {RequestInit} [opts]
|
||||
*/
|
||||
function build_selector(resource, opts) {
|
||||
const url = JSON.stringify(resource instanceof Request ? resource.url : resource);
|
||||
|
||||
let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
|
||||
|
||||
if (opts?.headers || opts?.body) {
|
||||
/** @type {import('types').StrictBody[]} */
|
||||
const values = [];
|
||||
|
||||
if (opts.headers) {
|
||||
values.push([...new Headers(opts.headers)].join(','));
|
||||
}
|
||||
|
||||
if (opts.body && (typeof opts.body === 'string' || ArrayBuffer.isView(opts.body))) {
|
||||
values.push(opts.body);
|
||||
}
|
||||
|
||||
selector += `[data-hash="${hash(...values)}"]`;
|
||||
}
|
||||
|
||||
return selector;
|
||||
}
|
57
node_modules/@sveltejs/kit/src/runtime/client/parse.js
generated
vendored
Normal file
57
node_modules/@sveltejs/kit/src/runtime/client/parse.js
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import { exec, parse_route_id } from '../../utils/routing.js';
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').SvelteKitApp} app
|
||||
* @returns {import('types').CSRRoute[]}
|
||||
*/
|
||||
export function parse({ nodes, server_loads, dictionary, matchers }) {
|
||||
const layouts_with_server_load = new Set(server_loads);
|
||||
|
||||
return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => {
|
||||
const { pattern, params } = parse_route_id(id);
|
||||
|
||||
const route = {
|
||||
id,
|
||||
/** @param {string} path */
|
||||
exec: (path) => {
|
||||
const match = pattern.exec(path);
|
||||
if (match) return exec(match, params, matchers);
|
||||
},
|
||||
errors: [1, ...(errors || [])].map((n) => nodes[n]),
|
||||
layouts: [0, ...(layouts || [])].map(create_layout_loader),
|
||||
leaf: create_leaf_loader(leaf)
|
||||
};
|
||||
|
||||
// bit of a hack, but ensures that layout/error node lists are the same
|
||||
// length, without which the wrong data will be applied if the route
|
||||
// manifest looks like `[[a, b], [c,], d]`
|
||||
route.errors.length = route.layouts.length = Math.max(
|
||||
route.errors.length,
|
||||
route.layouts.length
|
||||
);
|
||||
|
||||
return route;
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {number} id
|
||||
* @returns {[boolean, import('types').CSRPageNodeLoader]}
|
||||
*/
|
||||
function create_leaf_loader(id) {
|
||||
// whether or not the route uses the server data is
|
||||
// encoded using the ones' complement, to save space
|
||||
const uses_server_data = id < 0;
|
||||
if (uses_server_data) id = ~id;
|
||||
return [uses_server_data, nodes[id]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number | undefined} id
|
||||
* @returns {[boolean, import('types').CSRPageNodeLoader] | undefined}
|
||||
*/
|
||||
function create_layout_loader(id) {
|
||||
// whether or not the layout uses the server data is
|
||||
// encoded in the layouts array, to save space
|
||||
return id === undefined ? id : [layouts_with_server_load.has(id), nodes[id]];
|
||||
}
|
||||
}
|
25
node_modules/@sveltejs/kit/src/runtime/client/session-storage.js
generated
vendored
Normal file
25
node_modules/@sveltejs/kit/src/runtime/client/session-storage.js
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Read a value from `sessionStorage`
|
||||
* @param {string} key
|
||||
*/
|
||||
export function get(key) {
|
||||
try {
|
||||
return JSON.parse(sessionStorage[key]);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to `sessionStorage`
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
*/
|
||||
export function set(key, value) {
|
||||
const json = JSON.stringify(value);
|
||||
try {
|
||||
sessionStorage[key] = json;
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
53
node_modules/@sveltejs/kit/src/runtime/client/singletons.js
generated
vendored
Normal file
53
node_modules/@sveltejs/kit/src/runtime/client/singletons.js
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { create_updated_store, notifiable_store } from './utils.js';
|
||||
import { BROWSER } from 'esm-env';
|
||||
|
||||
/** @type {import('./types.js').Client} */
|
||||
export let client;
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* client: import('./types.js').Client;
|
||||
* }} opts
|
||||
*/
|
||||
export function init(opts) {
|
||||
client = opts.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {keyof typeof client} T
|
||||
* @param {T} key
|
||||
* @returns {typeof client[T]}
|
||||
*/
|
||||
export function client_method(key) {
|
||||
if (!BROWSER) {
|
||||
if (key === 'before_navigate' || key === 'after_navigate' || key === 'on_navigate') {
|
||||
// @ts-expect-error doesn't recognize that both keys here return void so expects a async function
|
||||
return () => {};
|
||||
} else {
|
||||
/** @type {Record<string, string>} */
|
||||
const name_lookup = {
|
||||
disable_scroll_handling: 'disableScrollHandling',
|
||||
preload_data: 'preloadData',
|
||||
preload_code: 'preloadCode',
|
||||
invalidate_all: 'invalidateAll'
|
||||
};
|
||||
|
||||
return () => {
|
||||
throw new Error(`Cannot call ${name_lookup[key] ?? key}(...) on the server`);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
return (...args) => client[key](...args);
|
||||
}
|
||||
}
|
||||
|
||||
export const stores = {
|
||||
url: /* @__PURE__ */ notifiable_store({}),
|
||||
page: /* @__PURE__ */ notifiable_store({}),
|
||||
navigating: /* @__PURE__ */ writable(
|
||||
/** @type {import('@sveltejs/kit').Navigation | null} */ (null)
|
||||
),
|
||||
updated: /* @__PURE__ */ create_updated_store()
|
||||
};
|
28
node_modules/@sveltejs/kit/src/runtime/client/start.js
generated
vendored
Normal file
28
node_modules/@sveltejs/kit/src/runtime/client/start.js
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { create_client } from './client.js';
|
||||
import { init } from './singletons.js';
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').SvelteKitApp} app
|
||||
* @param {HTMLElement} target
|
||||
* @param {Parameters<import('./types.js').Client['_hydrate']>[0]} [hydrate]
|
||||
*/
|
||||
export async function start(app, target, hydrate) {
|
||||
if (DEV && target === document.body) {
|
||||
console.warn(
|
||||
'Placing %sveltekit.body% directly inside <body> is not recommended, as your app may break for users who have certain browser extensions installed.\n\nConsider wrapping it in an element:\n\n<div style="display: contents">\n %sveltekit.body%\n</div>'
|
||||
);
|
||||
}
|
||||
|
||||
const client = create_client(app, target);
|
||||
|
||||
init({ client });
|
||||
|
||||
if (hydrate) {
|
||||
await client._hydrate(hydrate);
|
||||
} else {
|
||||
client.goto(location.href, { replaceState: true });
|
||||
}
|
||||
|
||||
client._start_router();
|
||||
}
|
123
node_modules/@sveltejs/kit/src/runtime/client/types.d.ts
generated
vendored
Normal file
123
node_modules/@sveltejs/kit/src/runtime/client/types.d.ts
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
import { applyAction } from '../app/forms.js';
|
||||
import {
|
||||
afterNavigate,
|
||||
beforeNavigate,
|
||||
onNavigate,
|
||||
goto,
|
||||
invalidate,
|
||||
invalidateAll,
|
||||
preloadCode,
|
||||
preloadData
|
||||
} from '../app/navigation.js';
|
||||
import { SvelteComponent } from 'svelte';
|
||||
import { ClientHooks, CSRPageNode, CSRPageNodeLoader, CSRRoute, TrailingSlash, Uses } from 'types';
|
||||
import { Page, ParamMatcher } from '@sveltejs/kit';
|
||||
|
||||
export interface SvelteKitApp {
|
||||
/**
|
||||
* A list of all the error/layout/page nodes used in the app
|
||||
*/
|
||||
nodes: CSRPageNodeLoader[];
|
||||
|
||||
/**
|
||||
* A list of all layout node ids that have a server load function.
|
||||
* Pages are not present because it's shorter to encode it on the leaf itself.
|
||||
*/
|
||||
server_loads: number[];
|
||||
|
||||
/**
|
||||
* A map of `[routeId: string]: [leaf, layouts, errors]` tuples, which
|
||||
* is parsed into an array of routes on startup. The numbers refer to the indices in `nodes`.
|
||||
* If the leaf number is negative, it means it does use a server load function and the complement is the node index.
|
||||
* The route layout and error nodes are not referenced, they are always number 0 and 1 and always apply.
|
||||
*/
|
||||
dictionary: Record<string, [leaf: number, layouts: number[], errors?: number[]]>;
|
||||
|
||||
matchers: Record<string, ParamMatcher>;
|
||||
|
||||
hooks: ClientHooks;
|
||||
|
||||
root: typeof SvelteComponent;
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
// public API, exposed via $app/navigation
|
||||
after_navigate: typeof afterNavigate;
|
||||
before_navigate: typeof beforeNavigate;
|
||||
on_navigate: typeof onNavigate;
|
||||
disable_scroll_handling(): void;
|
||||
goto: typeof goto;
|
||||
invalidate: typeof invalidate;
|
||||
invalidate_all: typeof invalidateAll;
|
||||
preload_code: typeof preloadCode;
|
||||
preload_data: typeof preloadData;
|
||||
apply_action: typeof applyAction;
|
||||
|
||||
// private API
|
||||
_hydrate(opts: {
|
||||
status: number;
|
||||
error: App.Error | null;
|
||||
node_ids: number[];
|
||||
params: Record<string, string>;
|
||||
route: { id: string | null };
|
||||
data: Array<import('types').ServerDataNode | null>;
|
||||
form: Record<string, any> | null;
|
||||
}): Promise<void>;
|
||||
_start_router(): void;
|
||||
}
|
||||
|
||||
export type NavigationIntent = {
|
||||
/** `url.pathname + url.search` */
|
||||
id: string;
|
||||
/** Whether we are invalidating or navigating */
|
||||
invalidating: boolean;
|
||||
/** The route parameters */
|
||||
params: Record<string, string>;
|
||||
/** The route that matches `path` */
|
||||
route: CSRRoute;
|
||||
/** The destination URL */
|
||||
url: URL;
|
||||
};
|
||||
|
||||
export type NavigationResult = NavigationRedirect | NavigationFinished;
|
||||
|
||||
export type NavigationRedirect = {
|
||||
type: 'redirect';
|
||||
location: string;
|
||||
};
|
||||
|
||||
export type NavigationFinished = {
|
||||
type: 'loaded';
|
||||
state: NavigationState;
|
||||
props: {
|
||||
constructors: Array<typeof SvelteComponent>;
|
||||
components?: Array<SvelteComponent>;
|
||||
page?: Page;
|
||||
form?: Record<string, any> | null;
|
||||
[key: `data_${number}`]: Record<string, any>;
|
||||
};
|
||||
};
|
||||
|
||||
export type BranchNode = {
|
||||
node: CSRPageNode;
|
||||
loader: CSRPageNodeLoader;
|
||||
server: DataNode | null;
|
||||
universal: DataNode | null;
|
||||
data: Record<string, any> | null;
|
||||
slash?: TrailingSlash;
|
||||
};
|
||||
|
||||
export interface DataNode {
|
||||
type: 'data';
|
||||
data: Record<string, any> | null;
|
||||
uses: Uses;
|
||||
slash?: TrailingSlash;
|
||||
}
|
||||
|
||||
export interface NavigationState {
|
||||
branch: Array<BranchNode | undefined>;
|
||||
error: App.Error | null;
|
||||
params: Record<string, string>;
|
||||
route: CSRRoute | null;
|
||||
url: URL;
|
||||
}
|
296
node_modules/@sveltejs/kit/src/runtime/client/utils.js
generated
vendored
Normal file
296
node_modules/@sveltejs/kit/src/runtime/client/utils.js
generated
vendored
Normal file
@ -0,0 +1,296 @@
|
||||
import { BROWSER, DEV } from 'esm-env';
|
||||
import { writable } from 'svelte/store';
|
||||
import { assets } from '__sveltekit/paths';
|
||||
import { version } from '__sveltekit/environment';
|
||||
import { PRELOAD_PRIORITIES } from './constants.js';
|
||||
|
||||
/* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */
|
||||
|
||||
export const origin = BROWSER ? location.origin : '';
|
||||
|
||||
/** @param {HTMLDocument} doc */
|
||||
export function get_base_uri(doc) {
|
||||
let baseURI = doc.baseURI;
|
||||
|
||||
if (!baseURI) {
|
||||
const baseTags = doc.getElementsByTagName('base');
|
||||
baseURI = baseTags.length ? baseTags[0].href : doc.URL;
|
||||
}
|
||||
|
||||
return baseURI;
|
||||
}
|
||||
|
||||
export function scroll_state() {
|
||||
return {
|
||||
x: pageXOffset,
|
||||
y: pageYOffset
|
||||
};
|
||||
}
|
||||
|
||||
const warned = new WeakSet();
|
||||
|
||||
/** @typedef {keyof typeof valid_link_options} LinkOptionName */
|
||||
|
||||
const valid_link_options = /** @type {const} */ ({
|
||||
'preload-code': ['', 'off', 'tap', 'hover', 'viewport', 'eager'],
|
||||
'preload-data': ['', 'off', 'tap', 'hover'],
|
||||
keepfocus: ['', 'true', 'off', 'false'],
|
||||
noscroll: ['', 'true', 'off', 'false'],
|
||||
reload: ['', 'true', 'off', 'false'],
|
||||
replacestate: ['', 'true', 'off', 'false']
|
||||
});
|
||||
|
||||
/**
|
||||
* @template {LinkOptionName} T
|
||||
* @typedef {typeof valid_link_options[T][number]} ValidLinkOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {LinkOptionName} T
|
||||
* @param {Element} element
|
||||
* @param {T} name
|
||||
*/
|
||||
function link_option(element, name) {
|
||||
const value = /** @type {ValidLinkOptions<T> | null} */ (
|
||||
element.getAttribute(`data-sveltekit-${name}`)
|
||||
);
|
||||
|
||||
if (DEV) {
|
||||
validate_link_option(element, name, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {LinkOptionName} T
|
||||
* @template {ValidLinkOptions<T> | null} U
|
||||
* @param {Element} element
|
||||
* @param {T} name
|
||||
* @param {U} value
|
||||
*/
|
||||
function validate_link_option(element, name, value) {
|
||||
if (value === null) return;
|
||||
|
||||
// @ts-expect-error - includes is dumb
|
||||
if (!warned.has(element) && !valid_link_options[name].includes(value)) {
|
||||
console.error(
|
||||
`Unexpected value for ${name} — should be one of ${valid_link_options[name]
|
||||
.map((option) => JSON.stringify(option))
|
||||
.join(', ')}`,
|
||||
element
|
||||
);
|
||||
|
||||
warned.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
const levels = {
|
||||
...PRELOAD_PRIORITIES,
|
||||
'': PRELOAD_PRIORITIES.hover
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
* @returns {Element | null}
|
||||
*/
|
||||
function parent_element(element) {
|
||||
let parent = element.assignedSlot ?? element.parentNode;
|
||||
|
||||
// @ts-expect-error handle shadow roots
|
||||
if (parent?.nodeType === 11) parent = parent.host;
|
||||
|
||||
return /** @type {Element} */ (parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
* @param {Element} target
|
||||
*/
|
||||
export function find_anchor(element, target) {
|
||||
while (element && element !== target) {
|
||||
if (element.nodeName.toUpperCase() === 'A' && element.hasAttribute('href')) {
|
||||
return /** @type {HTMLAnchorElement | SVGAElement} */ (element);
|
||||
}
|
||||
|
||||
element = /** @type {Element} */ (parent_element(element));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLAnchorElement | SVGAElement} a
|
||||
* @param {string} base
|
||||
*/
|
||||
export function get_link_info(a, base) {
|
||||
/** @type {URL | undefined} */
|
||||
let url;
|
||||
|
||||
try {
|
||||
url = new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
|
||||
} catch {}
|
||||
|
||||
const target = a instanceof SVGAElement ? a.target.baseVal : a.target;
|
||||
|
||||
const external =
|
||||
!url ||
|
||||
!!target ||
|
||||
is_external_url(url, base) ||
|
||||
(a.getAttribute('rel') || '').split(/\s+/).includes('external');
|
||||
|
||||
const download = url?.origin === origin && a.hasAttribute('download');
|
||||
|
||||
return { url, external, target, download };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLFormElement | HTMLAnchorElement | SVGAElement} element
|
||||
*/
|
||||
export function get_router_options(element) {
|
||||
/** @type {ValidLinkOptions<'keepfocus'> | null} */
|
||||
let keep_focus = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'noscroll'> | null} */
|
||||
let noscroll = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'preload-code'> | null} */
|
||||
let preload_code = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'preload-data'> | null} */
|
||||
let preload_data = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'reload'> | null} */
|
||||
let reload = null;
|
||||
|
||||
/** @type {ValidLinkOptions<'replacestate'> | null} */
|
||||
let replace_state = null;
|
||||
|
||||
/** @type {Element} */
|
||||
let el = element;
|
||||
|
||||
while (el && el !== document.documentElement) {
|
||||
if (preload_code === null) preload_code = link_option(el, 'preload-code');
|
||||
if (preload_data === null) preload_data = link_option(el, 'preload-data');
|
||||
if (keep_focus === null) keep_focus = link_option(el, 'keepfocus');
|
||||
if (noscroll === null) noscroll = link_option(el, 'noscroll');
|
||||
if (reload === null) reload = link_option(el, 'reload');
|
||||
if (replace_state === null) replace_state = link_option(el, 'replacestate');
|
||||
|
||||
el = /** @type {Element} */ (parent_element(el));
|
||||
}
|
||||
|
||||
/** @param {string | null} value */
|
||||
function get_option_state(value) {
|
||||
switch (value) {
|
||||
case '':
|
||||
case 'true':
|
||||
return true;
|
||||
case 'off':
|
||||
case 'false':
|
||||
return false;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
preload_code: levels[preload_code ?? 'off'],
|
||||
preload_data: levels[preload_data ?? 'off'],
|
||||
keep_focus: get_option_state(keep_focus),
|
||||
noscroll: get_option_state(noscroll),
|
||||
reload: get_option_state(reload),
|
||||
replace_state: get_option_state(replace_state)
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {any} value */
|
||||
export function notifiable_store(value) {
|
||||
const store = writable(value);
|
||||
let ready = true;
|
||||
|
||||
function notify() {
|
||||
ready = true;
|
||||
store.update((val) => val);
|
||||
}
|
||||
|
||||
/** @param {any} new_value */
|
||||
function set(new_value) {
|
||||
ready = false;
|
||||
store.set(new_value);
|
||||
}
|
||||
|
||||
/** @param {(value: any) => void} run */
|
||||
function subscribe(run) {
|
||||
/** @type {any} */
|
||||
let old_value;
|
||||
return store.subscribe((new_value) => {
|
||||
if (old_value === undefined || (ready && new_value !== old_value)) {
|
||||
run((old_value = new_value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { notify, set, subscribe };
|
||||
}
|
||||
|
||||
export function create_updated_store() {
|
||||
const { set, subscribe } = writable(false);
|
||||
|
||||
if (DEV || !BROWSER) {
|
||||
return {
|
||||
subscribe,
|
||||
check: async () => false
|
||||
};
|
||||
}
|
||||
|
||||
const interval = __SVELTEKIT_APP_VERSION_POLL_INTERVAL__;
|
||||
|
||||
/** @type {NodeJS.Timeout} */
|
||||
let timeout;
|
||||
|
||||
/** @type {() => Promise<boolean>} */
|
||||
async function check() {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (interval) timeout = setTimeout(check, interval);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${assets}/${__SVELTEKIT_APP_VERSION_FILE__}`, {
|
||||
headers: {
|
||||
pragma: 'no-cache',
|
||||
'cache-control': 'no-cache'
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const updated = data.version !== version;
|
||||
|
||||
if (updated) {
|
||||
set(true);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
return updated;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (interval) timeout = setTimeout(check, interval);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
check
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {URL} url
|
||||
* @param {string} base
|
||||
*/
|
||||
export function is_external_url(url, base) {
|
||||
return url.origin !== origin || !url.pathname.startsWith(base);
|
||||
}
|
6
node_modules/@sveltejs/kit/src/runtime/components/error.svelte
generated
vendored
Normal file
6
node_modules/@sveltejs/kit/src/runtime/components/error.svelte
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<h1>{$page.status}</h1>
|
||||
<p>{$page.error?.message}</p>
|
1
node_modules/@sveltejs/kit/src/runtime/components/layout.svelte
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/components/layout.svelte
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
<slot></slot>
|
66
node_modules/@sveltejs/kit/src/runtime/control.js
generated
vendored
Normal file
66
node_modules/@sveltejs/kit/src/runtime/control.js
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
export class HttpError {
|
||||
/**
|
||||
* @param {number} status
|
||||
* @param {{message: string} extends App.Error ? (App.Error | string | undefined) : App.Error} body
|
||||
*/
|
||||
constructor(status, body) {
|
||||
this.status = status;
|
||||
if (typeof body === 'string') {
|
||||
this.body = { message: body };
|
||||
} else if (body) {
|
||||
this.body = body;
|
||||
} else {
|
||||
this.body = { message: `Error: ${status}` };
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify(this.body);
|
||||
}
|
||||
}
|
||||
|
||||
export class Redirect {
|
||||
/**
|
||||
* @param {300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308} status
|
||||
* @param {string} location
|
||||
*/
|
||||
constructor(status, location) {
|
||||
this.status = status;
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {Record<string, unknown> | undefined} [T=undefined]
|
||||
*/
|
||||
export class ActionFailure {
|
||||
/**
|
||||
* @param {number} status
|
||||
* @param {T} [data]
|
||||
*/
|
||||
constructor(status, data) {
|
||||
this.status = status;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a grotesque hack that, in dev, allows us to replace the implementations
|
||||
* of these classes that you'd get by importing them from `@sveltejs/kit` with the
|
||||
* ones that are imported via Vite and loaded internally, so that instanceof
|
||||
* checks work even though SvelteKit imports this module via Vite and consumers
|
||||
* import it via Node
|
||||
* @param {{
|
||||
* ActionFailure: typeof ActionFailure;
|
||||
* HttpError: typeof HttpError;
|
||||
* Redirect: typeof Redirect;
|
||||
* }} implementations
|
||||
*/
|
||||
export function replace_implementations(implementations) {
|
||||
// @ts-expect-error
|
||||
ActionFailure = implementations.ActionFailure; // eslint-disable-line no-class-assign
|
||||
// @ts-expect-error
|
||||
HttpError = implementations.HttpError; // eslint-disable-line no-class-assign
|
||||
// @ts-expect-error
|
||||
Redirect = implementations.Redirect; // eslint-disable-line no-class-assign
|
||||
}
|
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/private.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/private.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { private_env as env } from '../../shared-server.js';
|
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/public.js
generated
vendored
Normal file
1
node_modules/@sveltejs/kit/src/runtime/env/dynamic/public.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { public_env as env } from '../../shared-server.js';
|
22
node_modules/@sveltejs/kit/src/runtime/hash.js
generated
vendored
Normal file
22
node_modules/@sveltejs/kit/src/runtime/hash.js
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Hash using djb2
|
||||
* @param {import('types').StrictBody[]} values
|
||||
*/
|
||||
export function hash(...values) {
|
||||
let hash = 5381;
|
||||
|
||||
for (const value of values) {
|
||||
if (typeof value === 'string') {
|
||||
let i = value.length;
|
||||
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
|
||||
} else if (ArrayBuffer.isView(value)) {
|
||||
const buffer = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
||||
let i = buffer.length;
|
||||
while (i) hash = (hash * 33) ^ buffer[--i];
|
||||
} else {
|
||||
throw new TypeError('value must be a string or TypedArray');
|
||||
}
|
||||
}
|
||||
|
||||
return (hash >>> 0).toString(36);
|
||||
}
|
8
node_modules/@sveltejs/kit/src/runtime/server/ambient.d.ts
generated
vendored
Normal file
8
node_modules/@sveltejs/kit/src/runtime/server/ambient.d.ts
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
declare module '__SERVER__/internal.js' {
|
||||
export const options: import('types').SSROptions;
|
||||
export const get_hooks: () => Promise<{
|
||||
handle?: import('@sveltejs/kit').Handle;
|
||||
handleError?: import('@sveltejs/kit').HandleServerError;
|
||||
handleFetch?: import('@sveltejs/kit').HandleFetch;
|
||||
}>;
|
||||
}
|
250
node_modules/@sveltejs/kit/src/runtime/server/cookie.js
generated
vendored
Normal file
250
node_modules/@sveltejs/kit/src/runtime/server/cookie.js
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
import { parse, serialize } from 'cookie';
|
||||
import { normalize_path } from '../../utils/url.js';
|
||||
|
||||
/**
|
||||
* Tracks all cookies set during dev mode so we can emit warnings
|
||||
* when we detect that there's likely cookie misusage due to wrong paths
|
||||
*
|
||||
* @type {Record<string, Set<string>>} */
|
||||
const cookie_paths = {};
|
||||
|
||||
/**
|
||||
* Cookies that are larger than this size (including the name and other
|
||||
* attributes) are discarded by browsers
|
||||
*/
|
||||
const MAX_COOKIE_SIZE = 4129;
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
* @param {URL} url
|
||||
* @param {import('types').TrailingSlash} trailing_slash
|
||||
*/
|
||||
export function get_cookies(request, url, trailing_slash) {
|
||||
const header = request.headers.get('cookie') ?? '';
|
||||
const initial_cookies = parse(header, { decode: (value) => value });
|
||||
|
||||
const normalized_url = normalize_path(url.pathname, trailing_slash);
|
||||
// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
|
||||
const default_path = normalized_url.split('/').slice(0, -1).join('/') || '/';
|
||||
|
||||
/** @type {Record<string, import('./page/types.js').Cookie>} */
|
||||
const new_cookies = {};
|
||||
|
||||
/** @type {import('cookie').CookieSerializeOptions} */
|
||||
const defaults = {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: url.hostname === 'localhost' && url.protocol === 'http:' ? false : true
|
||||
};
|
||||
|
||||
/** @type {import('@sveltejs/kit').Cookies} */
|
||||
const cookies = {
|
||||
// The JSDoc param annotations appearing below for get, set and delete
|
||||
// are necessary to expose the `cookie` library types to
|
||||
// typescript users. `@type {import('@sveltejs/kit').Cookies}` above is not
|
||||
// sufficient to do so.
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {import('cookie').CookieParseOptions} opts
|
||||
*/
|
||||
get(name, opts) {
|
||||
const c = new_cookies[name];
|
||||
if (
|
||||
c &&
|
||||
domain_matches(url.hostname, c.options.domain) &&
|
||||
path_matches(url.pathname, c.options.path)
|
||||
) {
|
||||
return c.value;
|
||||
}
|
||||
|
||||
const decoder = opts?.decode || decodeURIComponent;
|
||||
const req_cookies = parse(header, { decode: decoder });
|
||||
const cookie = req_cookies[name]; // the decoded string or undefined
|
||||
|
||||
// in development, if the cookie was set during this session with `cookies.set`,
|
||||
// but at a different path, warn the user. (ignore cookies from request headers,
|
||||
// since we don't know which path they were set at)
|
||||
if (__SVELTEKIT_DEV__ && !cookie) {
|
||||
const paths = Array.from(cookie_paths[name] ?? []).filter((path) => {
|
||||
// we only care about paths that are _more_ specific than the current path
|
||||
return path_matches(path, url.pathname) && path !== url.pathname;
|
||||
});
|
||||
|
||||
if (paths.length > 0) {
|
||||
console.warn(
|
||||
// prettier-ignore
|
||||
`'${name}' cookie does not exist for ${url.pathname}, but was previously set at ${conjoin([...paths])}. Did you mean to set its 'path' to '/' instead?`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return cookie;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import('cookie').CookieParseOptions} opts
|
||||
*/
|
||||
getAll(opts) {
|
||||
const decoder = opts?.decode || decodeURIComponent;
|
||||
const cookies = parse(header, { decode: decoder });
|
||||
|
||||
for (const c of Object.values(new_cookies)) {
|
||||
if (
|
||||
domain_matches(url.hostname, c.options.domain) &&
|
||||
path_matches(url.pathname, c.options.path)
|
||||
) {
|
||||
cookies[c.name] = c.value;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.entries(cookies).map(([name, value]) => ({ name, value }));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
set(name, value, opts = {}) {
|
||||
set_internal(name, value, { ...defaults, ...opts });
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
delete(name, opts = {}) {
|
||||
cookies.set(name, '', {
|
||||
...opts,
|
||||
maxAge: 0
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
serialize(name, value, opts) {
|
||||
return serialize(name, value, {
|
||||
...defaults,
|
||||
...opts
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {URL} destination
|
||||
* @param {string | null} header
|
||||
*/
|
||||
function get_cookie_header(destination, header) {
|
||||
/** @type {Record<string, string>} */
|
||||
const combined_cookies = {
|
||||
// cookies sent by the user agent have lowest precedence
|
||||
...initial_cookies
|
||||
};
|
||||
|
||||
// cookies previous set during this event with cookies.set have higher precedence
|
||||
for (const key in new_cookies) {
|
||||
const cookie = new_cookies[key];
|
||||
if (!domain_matches(destination.hostname, cookie.options.domain)) continue;
|
||||
if (!path_matches(destination.pathname, cookie.options.path)) continue;
|
||||
|
||||
const encoder = cookie.options.encode || encodeURIComponent;
|
||||
combined_cookies[cookie.name] = encoder(cookie.value);
|
||||
}
|
||||
|
||||
// explicit header has highest precedence
|
||||
if (header) {
|
||||
const parsed = parse(header, { decode: (value) => value });
|
||||
for (const name in parsed) {
|
||||
combined_cookies[name] = parsed[name];
|
||||
}
|
||||
}
|
||||
|
||||
return Object.entries(combined_cookies)
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @param {import('cookie').CookieSerializeOptions} opts
|
||||
*/
|
||||
function set_internal(name, value, opts) {
|
||||
const path = opts.path ?? default_path;
|
||||
|
||||
new_cookies[name] = {
|
||||
name,
|
||||
value,
|
||||
options: {
|
||||
...opts,
|
||||
path
|
||||
}
|
||||
};
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
const serialized = serialize(name, value, new_cookies[name].options);
|
||||
if (new TextEncoder().encode(serialized).byteLength > MAX_COOKIE_SIZE) {
|
||||
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
|
||||
}
|
||||
|
||||
cookie_paths[name] ??= new Set();
|
||||
|
||||
if (!value) {
|
||||
cookie_paths[name].delete(path);
|
||||
} else {
|
||||
cookie_paths[name].add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { cookies, new_cookies, get_cookie_header, set_internal };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} hostname
|
||||
* @param {string} [constraint]
|
||||
*/
|
||||
export function domain_matches(hostname, constraint) {
|
||||
if (!constraint) return true;
|
||||
|
||||
const normalized = constraint[0] === '.' ? constraint.slice(1) : constraint;
|
||||
|
||||
if (hostname === normalized) return true;
|
||||
return hostname.endsWith('.' + normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {string} [constraint]
|
||||
*/
|
||||
export function path_matches(path, constraint) {
|
||||
if (!constraint) return true;
|
||||
|
||||
const normalized = constraint.endsWith('/') ? constraint.slice(0, -1) : constraint;
|
||||
|
||||
if (path === normalized) return true;
|
||||
return path.startsWith(normalized + '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @param {import('./page/types.js').Cookie[]} cookies
|
||||
*/
|
||||
export function add_cookies_to_headers(headers, cookies) {
|
||||
for (const new_cookie of cookies) {
|
||||
const { name, value, options } = new_cookie;
|
||||
headers.append('set-cookie', serialize(name, value, options));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} array
|
||||
*/
|
||||
function conjoin(array) {
|
||||
if (array.length <= 2) return array.join(' and ');
|
||||
return `${array.slice(0, -1).join(', ')} and ${array.at(-1)}`;
|
||||
}
|
263
node_modules/@sveltejs/kit/src/runtime/server/data/index.js
generated
vendored
Normal file
263
node_modules/@sveltejs/kit/src/runtime/server/data/index.js
generated
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
import { HttpError, Redirect } from '../../control.js';
|
||||
import { normalize_error } from '../../../utils/error.js';
|
||||
import { once } from '../../../utils/functions.js';
|
||||
import { load_server_data } from '../page/load_data.js';
|
||||
import { clarify_devalue_error, handle_error_and_jsonify, stringify_uses } from '../utils.js';
|
||||
import { normalize_path } from '../../../utils/url.js';
|
||||
import { text } from '../../../exports/index.js';
|
||||
import * as devalue from 'devalue';
|
||||
import { create_async_iterator } from '../../../utils/streaming.js';
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSRRoute} route
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
||||
* @param {import('types').SSRState} state
|
||||
* @param {boolean[] | undefined} invalidated_data_nodes
|
||||
* @param {import('types').TrailingSlash} trailing_slash
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function render_data(
|
||||
event,
|
||||
route,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
invalidated_data_nodes,
|
||||
trailing_slash
|
||||
) {
|
||||
if (!route.page) {
|
||||
// requesting /__data.json should fail for a +server.js
|
||||
return new Response(undefined, {
|
||||
status: 404
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const node_ids = [...route.page.layouts, route.page.leaf];
|
||||
const invalidated = invalidated_data_nodes ?? node_ids.map(() => true);
|
||||
|
||||
let aborted = false;
|
||||
|
||||
const url = new URL(event.url);
|
||||
url.pathname = normalize_path(url.pathname, trailing_slash);
|
||||
|
||||
const new_event = { ...event, url };
|
||||
|
||||
const functions = node_ids.map((n, i) => {
|
||||
return once(async () => {
|
||||
try {
|
||||
if (aborted) {
|
||||
return /** @type {import('types').ServerDataSkippedNode} */ ({
|
||||
type: 'skip'
|
||||
});
|
||||
}
|
||||
|
||||
// == because it could be undefined (in dev) or null (in build, because of JSON.stringify)
|
||||
const node = n == undefined ? n : await manifest._.nodes[n]();
|
||||
// load this. for the child, return as is. for the final result, stream things
|
||||
return load_server_data({
|
||||
event: new_event,
|
||||
state,
|
||||
node,
|
||||
parent: async () => {
|
||||
/** @type {Record<string, any>} */
|
||||
const data = {};
|
||||
for (let j = 0; j < i; j += 1) {
|
||||
const parent = /** @type {import('types').ServerDataNode | null} */ (
|
||||
await functions[j]()
|
||||
);
|
||||
|
||||
if (parent) {
|
||||
Object.assign(data, parent.data);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
track_server_fetches: options.track_server_fetches
|
||||
});
|
||||
} catch (e) {
|
||||
aborted = true;
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const promises = functions.map(async (fn, i) => {
|
||||
if (!invalidated[i]) {
|
||||
return /** @type {import('types').ServerDataSkippedNode} */ ({
|
||||
type: 'skip'
|
||||
});
|
||||
}
|
||||
|
||||
return fn();
|
||||
});
|
||||
|
||||
let length = promises.length;
|
||||
const nodes = await Promise.all(
|
||||
promises.map((p, i) =>
|
||||
p.catch(async (error) => {
|
||||
if (error instanceof Redirect) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Math.min because array isn't guaranteed to resolve in order
|
||||
length = Math.min(length, i + 1);
|
||||
|
||||
return /** @type {import('types').ServerErrorNode} */ ({
|
||||
type: 'error',
|
||||
error: await handle_error_and_jsonify(event, options, error),
|
||||
status: error instanceof HttpError ? error.status : undefined
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const { data, chunks } = get_data_json(event, options, nodes);
|
||||
|
||||
if (!chunks) {
|
||||
// use a normal JSON response where possible, so we get `content-length`
|
||||
// and can use browser JSON devtools for easier inspecting
|
||||
return json_response(data);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue(encoder.encode(data));
|
||||
for await (const chunk of chunks) {
|
||||
controller.enqueue(encoder.encode(chunk));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
|
||||
type: 'bytes'
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
// we use a proprietary content type to prevent buffering.
|
||||
// the `text` prefix makes it inspectable
|
||||
'content-type': 'text/sveltekit-data',
|
||||
'cache-control': 'private, no-store'
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
const error = normalize_error(e);
|
||||
|
||||
if (error instanceof Redirect) {
|
||||
return redirect_json_response(error);
|
||||
} else {
|
||||
return json_response(await handle_error_and_jsonify(event, options, error), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, any> | string} json
|
||||
* @param {number} [status]
|
||||
*/
|
||||
function json_response(json, status = 200) {
|
||||
return text(typeof json === 'string' ? json : JSON.stringify(json), {
|
||||
status,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'cache-control': 'private, no-store'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Redirect} redirect
|
||||
*/
|
||||
export function redirect_json_response(redirect) {
|
||||
return json_response({
|
||||
type: 'redirect',
|
||||
location: redirect.location
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the serialized data contains promises, `chunks` will be an
|
||||
* async iterable containing their resolutions
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {Array<import('types').ServerDataSkippedNode | import('types').ServerDataNode | import('types').ServerErrorNode | null | undefined>} nodes
|
||||
* @returns {{ data: string, chunks: AsyncIterable<string> | null }}
|
||||
*/
|
||||
export function get_data_json(event, options, nodes) {
|
||||
let promise_id = 1;
|
||||
let count = 0;
|
||||
|
||||
const { iterator, push, done } = create_async_iterator();
|
||||
|
||||
const reducers = {
|
||||
/** @param {any} thing */
|
||||
Promise: (thing) => {
|
||||
if (typeof thing?.then === 'function') {
|
||||
const id = promise_id++;
|
||||
count += 1;
|
||||
|
||||
/** @type {'data' | 'error'} */
|
||||
let key = 'data';
|
||||
|
||||
thing
|
||||
.catch(
|
||||
/** @param {any} e */ async (e) => {
|
||||
key = 'error';
|
||||
return handle_error_and_jsonify(event, options, /** @type {any} */ (e));
|
||||
}
|
||||
)
|
||||
.then(
|
||||
/** @param {any} value */
|
||||
async (value) => {
|
||||
let str;
|
||||
try {
|
||||
str = devalue.stringify(value, reducers);
|
||||
} catch (e) {
|
||||
const error = await handle_error_and_jsonify(
|
||||
event,
|
||||
options,
|
||||
new Error(`Failed to serialize promise while rendering ${event.route.id}`)
|
||||
);
|
||||
|
||||
key = 'error';
|
||||
str = devalue.stringify(error, reducers);
|
||||
}
|
||||
|
||||
count -= 1;
|
||||
|
||||
push(`{"type":"chunk","id":${id},"${key}":${str}}\n`);
|
||||
if (count === 0) done();
|
||||
}
|
||||
);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const strings = nodes.map((node) => {
|
||||
if (!node) return 'null';
|
||||
|
||||
if (node.type === 'error' || node.type === 'skip') {
|
||||
return JSON.stringify(node);
|
||||
}
|
||||
|
||||
return `{"type":"data","data":${devalue.stringify(node.data, reducers)},${stringify_uses(
|
||||
node
|
||||
)}${node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''}}`;
|
||||
});
|
||||
|
||||
return {
|
||||
data: `{"type":"data","nodes":[${strings.join(',')}]}\n`,
|
||||
chunks: count > 0 ? iterator : null
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
|
||||
}
|
||||
}
|
94
node_modules/@sveltejs/kit/src/runtime/server/endpoint.js
generated
vendored
Normal file
94
node_modules/@sveltejs/kit/src/runtime/server/endpoint.js
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js';
|
||||
import { negotiate } from '../../utils/http.js';
|
||||
import { Redirect } from '../control.js';
|
||||
import { method_not_allowed } from './utils.js';
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSREndpoint} mod
|
||||
* @param {import('types').SSRState} state
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function render_endpoint(event, mod, state) {
|
||||
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
|
||||
|
||||
let handler = mod[method] || mod.fallback;
|
||||
|
||||
if (method === 'HEAD' && mod.GET && !mod.HEAD) {
|
||||
handler = mod.GET;
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
return method_not_allowed(mod, method);
|
||||
}
|
||||
|
||||
const prerender = mod.prerender ?? state.prerender_default;
|
||||
|
||||
if (prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
|
||||
throw new Error('Cannot prerender endpoints that have mutative methods');
|
||||
}
|
||||
|
||||
if (state.prerendering && !prerender) {
|
||||
if (state.depth > 0) {
|
||||
// if request came from a prerendered page, bail
|
||||
throw new Error(`${event.route.id} is not prerenderable`);
|
||||
} else {
|
||||
// if request came direct from the crawler, signal that
|
||||
// this route cannot be prerendered, but don't bail
|
||||
return new Response(undefined, { status: 204 });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let response = await handler(
|
||||
/** @type {import('@sveltejs/kit').RequestEvent<Record<string, any>>} */ (event)
|
||||
);
|
||||
|
||||
if (!(response instanceof Response)) {
|
||||
throw new Error(
|
||||
`Invalid response from route ${event.url.pathname}: handler should return a Response object`
|
||||
);
|
||||
}
|
||||
|
||||
if (state.prerendering) {
|
||||
// the returned Response might have immutable Headers
|
||||
// so we should clone them before trying to mutate them
|
||||
response = new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: new Headers(response.headers)
|
||||
});
|
||||
response.headers.set('x-sveltekit-prerender', String(prerender));
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
if (e instanceof Redirect) {
|
||||
return new Response(undefined, {
|
||||
status: e.status,
|
||||
headers: { location: e.location }
|
||||
});
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
*/
|
||||
export function is_endpoint_request(event) {
|
||||
const { method, headers } = event.request;
|
||||
|
||||
// These methods exist exclusively for endpoints
|
||||
if (ENDPOINT_METHODS.has(method) && !PAGE_METHODS.has(method)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// use:enhance uses a custom header to disambiguate
|
||||
if (method === 'POST' && headers.get('x-sveltekit-action') === 'true') return false;
|
||||
|
||||
// GET/POST requests may be for endpoints or pages. We prefer endpoints if this isn't a text/html request
|
||||
const accept = event.request.headers.get('accept') ?? '*/*';
|
||||
return negotiate(accept, ['*', 'text/html']) !== 'text/html';
|
||||
}
|
160
node_modules/@sveltejs/kit/src/runtime/server/fetch.js
generated
vendored
Normal file
160
node_modules/@sveltejs/kit/src/runtime/server/fetch.js
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
import * as set_cookie_parser from 'set-cookie-parser';
|
||||
import { respond } from './respond.js';
|
||||
import * as paths from '__sveltekit/paths';
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* options: import('types').SSROptions;
|
||||
* manifest: import('@sveltejs/kit').SSRManifest;
|
||||
* state: import('types').SSRState;
|
||||
* get_cookie_header: (url: URL, header: string | null) => string;
|
||||
* set_internal: (name: string, value: string, opts: import('cookie').CookieSerializeOptions) => void;
|
||||
* }} opts
|
||||
* @returns {typeof fetch}
|
||||
*/
|
||||
export function create_fetch({ event, options, manifest, state, get_cookie_header, set_internal }) {
|
||||
return async (info, init) => {
|
||||
const original_request = normalize_fetch_input(info, init, event.url);
|
||||
|
||||
// some runtimes (e.g. Cloudflare) error if you access `request.mode`,
|
||||
// annoyingly, so we need to read the value from the `init` object instead
|
||||
let mode = (info instanceof Request ? info.mode : init?.mode) ?? 'cors';
|
||||
let credentials =
|
||||
(info instanceof Request ? info.credentials : init?.credentials) ?? 'same-origin';
|
||||
|
||||
return await options.hooks.handleFetch({
|
||||
event,
|
||||
request: original_request,
|
||||
fetch: async (info, init) => {
|
||||
const request = normalize_fetch_input(info, init, event.url);
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (!request.headers.has('origin')) {
|
||||
request.headers.set('origin', event.url.origin);
|
||||
}
|
||||
|
||||
if (info !== original_request) {
|
||||
mode = (info instanceof Request ? info.mode : init?.mode) ?? 'cors';
|
||||
credentials =
|
||||
(info instanceof Request ? info.credentials : init?.credentials) ?? 'same-origin';
|
||||
}
|
||||
|
||||
// Remove Origin, according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description
|
||||
if (
|
||||
(request.method === 'GET' || request.method === 'HEAD') &&
|
||||
((mode === 'no-cors' && url.origin !== event.url.origin) ||
|
||||
url.origin === event.url.origin)
|
||||
) {
|
||||
request.headers.delete('origin');
|
||||
}
|
||||
|
||||
if (url.origin !== event.url.origin) {
|
||||
// Allow cookie passthrough for "credentials: same-origin" and "credentials: include"
|
||||
// if SvelteKit is serving my.domain.com:
|
||||
// - domain.com WILL NOT receive cookies
|
||||
// - my.domain.com WILL receive cookies
|
||||
// - api.domain.dom WILL NOT receive cookies
|
||||
// - sub.my.domain.com WILL receive cookies
|
||||
// ports do not affect the resolution
|
||||
// leading dot prevents mydomain.com matching domain.com
|
||||
// Do not forward other cookies for "credentials: include" because we don't know
|
||||
// which cookie belongs to which domain (browser does not pass this info)
|
||||
if (`.${url.hostname}`.endsWith(`.${event.url.hostname}`) && credentials !== 'omit') {
|
||||
const cookie = get_cookie_header(url, request.headers.get('cookie'));
|
||||
if (cookie) request.headers.set('cookie', cookie);
|
||||
}
|
||||
|
||||
return fetch(request);
|
||||
}
|
||||
|
||||
// handle fetch requests for static assets. e.g. prebaked data, etc.
|
||||
// we need to support everything the browser's fetch supports
|
||||
const prefix = paths.assets || paths.base;
|
||||
const decoded = decodeURIComponent(url.pathname);
|
||||
const filename = (
|
||||
decoded.startsWith(prefix) ? decoded.slice(prefix.length) : decoded
|
||||
).slice(1);
|
||||
const filename_html = `${filename}/index.html`; // path may also match path/index.html
|
||||
|
||||
const is_asset = manifest.assets.has(filename);
|
||||
const is_asset_html = manifest.assets.has(filename_html);
|
||||
|
||||
if (is_asset || is_asset_html) {
|
||||
const file = is_asset ? filename : filename_html;
|
||||
|
||||
if (state.read) {
|
||||
const type = is_asset
|
||||
? manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))]
|
||||
: 'text/html';
|
||||
|
||||
return new Response(state.read(file), {
|
||||
headers: type ? { 'content-type': type } : {}
|
||||
});
|
||||
}
|
||||
|
||||
return await fetch(request);
|
||||
}
|
||||
|
||||
if (credentials !== 'omit') {
|
||||
const cookie = get_cookie_header(url, request.headers.get('cookie'));
|
||||
if (cookie) {
|
||||
request.headers.set('cookie', cookie);
|
||||
}
|
||||
|
||||
const authorization = event.request.headers.get('authorization');
|
||||
if (authorization && !request.headers.has('authorization')) {
|
||||
request.headers.set('authorization', authorization);
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.headers.has('accept')) {
|
||||
request.headers.set('accept', '*/*');
|
||||
}
|
||||
|
||||
if (!request.headers.has('accept-language')) {
|
||||
request.headers.set(
|
||||
'accept-language',
|
||||
/** @type {string} */ (event.request.headers.get('accept-language'))
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {Response} */
|
||||
const response = await respond(request, options, manifest, {
|
||||
...state,
|
||||
depth: state.depth + 1
|
||||
});
|
||||
|
||||
const set_cookie = response.headers.get('set-cookie');
|
||||
if (set_cookie) {
|
||||
for (const str of set_cookie_parser.splitCookiesString(set_cookie)) {
|
||||
const { name, value, ...options } = set_cookie_parser.parseString(str);
|
||||
|
||||
// options.sameSite is string, something more specific is required - type cast is safe
|
||||
set_internal(
|
||||
name,
|
||||
value,
|
||||
/** @type {import('cookie').CookieSerializeOptions} */ (options)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RequestInfo | URL} info
|
||||
* @param {RequestInit | undefined} init
|
||||
* @param {URL} url
|
||||
*/
|
||||
function normalize_fetch_input(info, init, url) {
|
||||
if (info instanceof Request) {
|
||||
return info;
|
||||
}
|
||||
|
||||
return new Request(typeof info === 'string' ? new URL(info, url) : info, init);
|
||||
}
|
87
node_modules/@sveltejs/kit/src/runtime/server/index.js
generated
vendored
Normal file
87
node_modules/@sveltejs/kit/src/runtime/server/index.js
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
import { respond } from './respond.js';
|
||||
import { set_private_env, set_public_env } from '../shared-server.js';
|
||||
import { options, get_hooks } from '__SERVER__/internal.js';
|
||||
import { DEV } from 'esm-env';
|
||||
import { filter_private_env, filter_public_env } from '../../utils/env.js';
|
||||
|
||||
export class Server {
|
||||
/** @type {import('types').SSROptions} */
|
||||
#options;
|
||||
|
||||
/** @type {import('@sveltejs/kit').SSRManifest} */
|
||||
#manifest;
|
||||
|
||||
/** @param {import('@sveltejs/kit').SSRManifest} manifest */
|
||||
constructor(manifest) {
|
||||
/** @type {import('types').SSROptions} */
|
||||
this.#options = options;
|
||||
this.#manifest = manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* env: Record<string, string>
|
||||
* }} opts
|
||||
*/
|
||||
async init({ env }) {
|
||||
// Take care: Some adapters may have to call `Server.init` per-request to set env vars,
|
||||
// so anything that shouldn't be rerun should be wrapped in an `if` block to make sure it hasn't
|
||||
// been done already.
|
||||
// set env, in case it's used in initialisation
|
||||
set_private_env(
|
||||
filter_private_env(env, {
|
||||
public_prefix: this.#options.env_public_prefix,
|
||||
private_prefix: this.#options.env_private_prefix
|
||||
})
|
||||
);
|
||||
set_public_env(
|
||||
filter_public_env(env, {
|
||||
public_prefix: this.#options.env_public_prefix,
|
||||
private_prefix: this.#options.env_private_prefix
|
||||
})
|
||||
);
|
||||
|
||||
if (!this.#options.hooks) {
|
||||
try {
|
||||
const module = await get_hooks();
|
||||
|
||||
this.#options.hooks = {
|
||||
handle: module.handle || (({ event, resolve }) => resolve(event)),
|
||||
handleError: module.handleError || (({ error }) => console.error(error)),
|
||||
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request))
|
||||
};
|
||||
} catch (error) {
|
||||
if (DEV) {
|
||||
this.#options.hooks = {
|
||||
handle: () => {
|
||||
throw error;
|
||||
},
|
||||
handleError: ({ error }) => console.error(error),
|
||||
handleFetch: ({ request, fetch }) => fetch(request)
|
||||
};
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
* @param {import('types').RequestOptions} options
|
||||
*/
|
||||
async respond(request, options) {
|
||||
// TODO this should probably have been removed for 1.0 — i think we can get rid of it?
|
||||
if (!(request instanceof Request)) {
|
||||
throw new Error(
|
||||
'The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details'
|
||||
);
|
||||
}
|
||||
|
||||
return respond(request, this.#options, this.#manifest, {
|
||||
...options,
|
||||
error: false,
|
||||
depth: 0
|
||||
});
|
||||
}
|
||||
}
|
282
node_modules/@sveltejs/kit/src/runtime/server/page/actions.js
generated
vendored
Normal file
282
node_modules/@sveltejs/kit/src/runtime/server/page/actions.js
generated
vendored
Normal file
@ -0,0 +1,282 @@
|
||||
import * as devalue from 'devalue';
|
||||
import { error, json } from '../../../exports/index.js';
|
||||
import { normalize_error } from '../../../utils/error.js';
|
||||
import { is_form_content_type, negotiate } from '../../../utils/http.js';
|
||||
import { HttpError, Redirect, ActionFailure } from '../../control.js';
|
||||
import { handle_error_and_jsonify } from '../utils.js';
|
||||
|
||||
/** @param {import('@sveltejs/kit').RequestEvent} event */
|
||||
export function is_action_json_request(event) {
|
||||
const accept = negotiate(event.request.headers.get('accept') ?? '*/*', [
|
||||
'application/json',
|
||||
'text/html'
|
||||
]);
|
||||
|
||||
return accept === 'application/json' && event.request.method === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('types').SSRNode['server'] | undefined} server
|
||||
*/
|
||||
export async function handle_action_json_request(event, options, server) {
|
||||
const actions = server?.actions;
|
||||
|
||||
if (!actions) {
|
||||
// TODO should this be a different error altogether?
|
||||
const no_actions_error = error(405, 'POST method not allowed. No actions exist for this page');
|
||||
return action_json(
|
||||
{
|
||||
type: 'error',
|
||||
error: await handle_error_and_jsonify(event, options, no_actions_error)
|
||||
},
|
||||
{
|
||||
status: no_actions_error.status,
|
||||
headers: {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
||||
// "The server must generate an Allow header field in a 405 status code response"
|
||||
allow: 'GET'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
check_named_default_separate(actions);
|
||||
|
||||
try {
|
||||
const data = await call_action(event, actions);
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_action_return(data);
|
||||
}
|
||||
|
||||
if (data instanceof ActionFailure) {
|
||||
return action_json({
|
||||
type: 'failure',
|
||||
status: data.status,
|
||||
// @ts-expect-error we assign a string to what is supposed to be an object. That's ok
|
||||
// because we don't use the object outside, and this way we have better code navigation
|
||||
// through knowing where the related interface is used.
|
||||
data: stringify_action_response(data.data, /** @type {string} */ (event.route.id))
|
||||
});
|
||||
} else {
|
||||
return action_json({
|
||||
type: 'success',
|
||||
status: data ? 200 : 204,
|
||||
// @ts-expect-error see comment above
|
||||
data: stringify_action_response(data, /** @type {string} */ (event.route.id))
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
const err = normalize_error(e);
|
||||
|
||||
if (err instanceof Redirect) {
|
||||
return action_json_redirect(err);
|
||||
}
|
||||
|
||||
return action_json(
|
||||
{
|
||||
type: 'error',
|
||||
error: await handle_error_and_jsonify(event, options, check_incorrect_fail_use(err))
|
||||
},
|
||||
{
|
||||
status: err instanceof HttpError ? err.status : 500
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HttpError | Error} error
|
||||
*/
|
||||
function check_incorrect_fail_use(error) {
|
||||
return error instanceof ActionFailure
|
||||
? new Error('Cannot "throw fail()". Use "return fail()"')
|
||||
: error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').Redirect} redirect
|
||||
*/
|
||||
export function action_json_redirect(redirect) {
|
||||
return action_json({
|
||||
type: 'redirect',
|
||||
status: redirect.status,
|
||||
location: redirect.location
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').ActionResult} data
|
||||
* @param {ResponseInit} [init]
|
||||
*/
|
||||
function action_json(data, init) {
|
||||
return json(data, init);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
*/
|
||||
export function is_action_request(event) {
|
||||
return event.request.method === 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSRNode['server'] | undefined} server
|
||||
* @returns {Promise<import('@sveltejs/kit').ActionResult>}
|
||||
*/
|
||||
export async function handle_action_request(event, server) {
|
||||
const actions = server?.actions;
|
||||
|
||||
if (!actions) {
|
||||
// TODO should this be a different error altogether?
|
||||
event.setHeaders({
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
||||
// "The server must generate an Allow header field in a 405 status code response"
|
||||
allow: 'GET'
|
||||
});
|
||||
return {
|
||||
type: 'error',
|
||||
error: error(405, 'POST method not allowed. No actions exist for this page')
|
||||
};
|
||||
}
|
||||
|
||||
check_named_default_separate(actions);
|
||||
|
||||
try {
|
||||
const data = await call_action(event, actions);
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_action_return(data);
|
||||
}
|
||||
|
||||
if (data instanceof ActionFailure) {
|
||||
return {
|
||||
type: 'failure',
|
||||
status: data.status,
|
||||
data: data.data
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'success',
|
||||
status: 200,
|
||||
// @ts-expect-error this will be removed upon serialization, so `undefined` is the same as omission
|
||||
data
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
const err = normalize_error(e);
|
||||
|
||||
if (err instanceof Redirect) {
|
||||
return {
|
||||
type: 'redirect',
|
||||
status: err.status,
|
||||
location: err.location
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'error',
|
||||
error: check_incorrect_fail_use(err)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').Actions} actions
|
||||
*/
|
||||
function check_named_default_separate(actions) {
|
||||
if (actions.default && Object.keys(actions).length > 1) {
|
||||
throw new Error(
|
||||
'When using named actions, the default action cannot be used. See the docs for more info: https://kit.svelte.dev/docs/form-actions#named-actions'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {NonNullable<import('types').SSRNode['server']['actions']>} actions
|
||||
* @throws {Redirect | ActionFailure | HttpError | Error}
|
||||
*/
|
||||
async function call_action(event, actions) {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
let name = 'default';
|
||||
for (const param of url.searchParams) {
|
||||
if (param[0].startsWith('/')) {
|
||||
name = param[0].slice(1);
|
||||
if (name === 'default') {
|
||||
throw new Error('Cannot use reserved action name "default"');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const action = actions[name];
|
||||
if (!action) {
|
||||
throw new Error(`No action with name '${name}' found`);
|
||||
}
|
||||
|
||||
if (!is_form_content_type(event.request)) {
|
||||
throw new Error(
|
||||
`Actions expect form-encoded data (received ${event.request.headers.get('content-type')})`
|
||||
);
|
||||
}
|
||||
|
||||
return action(event);
|
||||
}
|
||||
|
||||
/** @param {any} data */
|
||||
function validate_action_return(data) {
|
||||
if (data instanceof Redirect) {
|
||||
throw new Error('Cannot `return redirect(...)` — use `throw redirect(...)` instead');
|
||||
}
|
||||
|
||||
if (data instanceof HttpError) {
|
||||
throw new Error(
|
||||
'Cannot `return error(...)` — use `throw error(...)` or `return fail(...)` instead'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to `devalue.uneval` the data object, and if it fails, return a proper Error with context
|
||||
* @param {any} data
|
||||
* @param {string} route_id
|
||||
*/
|
||||
export function uneval_action_response(data, route_id) {
|
||||
return try_deserialize(data, devalue.uneval, route_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to `devalue.stringify` the data object, and if it fails, return a proper Error with context
|
||||
* @param {any} data
|
||||
* @param {string} route_id
|
||||
*/
|
||||
function stringify_action_response(data, route_id) {
|
||||
return try_deserialize(data, devalue.stringify, route_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} data
|
||||
* @param {(data: any) => string} fn
|
||||
* @param {string} route_id
|
||||
*/
|
||||
function try_deserialize(data, fn, route_id) {
|
||||
try {
|
||||
return fn(data);
|
||||
} catch (e) {
|
||||
// If we're here, the data could not be serialized with devalue
|
||||
const error = /** @type {any} */ (e);
|
||||
|
||||
if ('path' in error) {
|
||||
let message = `Data returned from action inside ${route_id} is not serializable: ${error.message}`;
|
||||
if (error.path !== '') message += ` (data.${error.path})`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
239
node_modules/@sveltejs/kit/src/runtime/server/page/crypto.js
generated
vendored
Normal file
239
node_modules/@sveltejs/kit/src/runtime/server/page/crypto.js
generated
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
|
||||
* modified and redistributed under BSD license
|
||||
* @param {string} data
|
||||
*/
|
||||
export function sha256(data) {
|
||||
if (!key[0]) precompute();
|
||||
|
||||
const out = init.slice(0);
|
||||
const array = encode(data);
|
||||
|
||||
for (let i = 0; i < array.length; i += 16) {
|
||||
const w = array.subarray(i, i + 16);
|
||||
|
||||
let tmp;
|
||||
let a;
|
||||
let b;
|
||||
|
||||
let out0 = out[0];
|
||||
let out1 = out[1];
|
||||
let out2 = out[2];
|
||||
let out3 = out[3];
|
||||
let out4 = out[4];
|
||||
let out5 = out[5];
|
||||
let out6 = out[6];
|
||||
let out7 = out[7];
|
||||
|
||||
/* Rationale for placement of |0 :
|
||||
* If a value can overflow is original 32 bits by a factor of more than a few
|
||||
* million (2^23 ish), there is a possibility that it might overflow the
|
||||
* 53-bit mantissa and lose precision.
|
||||
*
|
||||
* To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
|
||||
* propagates around the loop, and on the hash state out[]. I don't believe
|
||||
* that the clamps on out4 and on out0 are strictly necessary, but it's close
|
||||
* (for out4 anyway), and better safe than sorry.
|
||||
*
|
||||
* The clamps on out[] are necessary for the output to be correct even in the
|
||||
* common case and for short inputs.
|
||||
*/
|
||||
|
||||
for (let i = 0; i < 64; i++) {
|
||||
// load up the input word for this round
|
||||
|
||||
if (i < 16) {
|
||||
tmp = w[i];
|
||||
} else {
|
||||
a = w[(i + 1) & 15];
|
||||
|
||||
b = w[(i + 14) & 15];
|
||||
|
||||
tmp = w[i & 15] =
|
||||
(((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
|
||||
((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
|
||||
w[i & 15] +
|
||||
w[(i + 9) & 15]) |
|
||||
0;
|
||||
}
|
||||
|
||||
tmp =
|
||||
tmp +
|
||||
out7 +
|
||||
((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
|
||||
(out6 ^ (out4 & (out5 ^ out6))) +
|
||||
key[i]; // | 0;
|
||||
|
||||
// shift register
|
||||
out7 = out6;
|
||||
out6 = out5;
|
||||
out5 = out4;
|
||||
|
||||
out4 = (out3 + tmp) | 0;
|
||||
|
||||
out3 = out2;
|
||||
out2 = out1;
|
||||
out1 = out0;
|
||||
|
||||
out0 =
|
||||
(tmp +
|
||||
((out1 & out2) ^ (out3 & (out1 ^ out2))) +
|
||||
((out1 >>> 2) ^
|
||||
(out1 >>> 13) ^
|
||||
(out1 >>> 22) ^
|
||||
(out1 << 30) ^
|
||||
(out1 << 19) ^
|
||||
(out1 << 10))) |
|
||||
0;
|
||||
}
|
||||
|
||||
out[0] = (out[0] + out0) | 0;
|
||||
out[1] = (out[1] + out1) | 0;
|
||||
out[2] = (out[2] + out2) | 0;
|
||||
out[3] = (out[3] + out3) | 0;
|
||||
out[4] = (out[4] + out4) | 0;
|
||||
out[5] = (out[5] + out5) | 0;
|
||||
out[6] = (out[6] + out6) | 0;
|
||||
out[7] = (out[7] + out7) | 0;
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(out.buffer);
|
||||
reverse_endianness(bytes);
|
||||
|
||||
return base64(bytes);
|
||||
}
|
||||
|
||||
/** The SHA-256 initialization vector */
|
||||
const init = new Uint32Array(8);
|
||||
|
||||
/** The SHA-256 hash key */
|
||||
const key = new Uint32Array(64);
|
||||
|
||||
/** Function to precompute init and key. */
|
||||
function precompute() {
|
||||
/** @param {number} x */
|
||||
function frac(x) {
|
||||
return (x - Math.floor(x)) * 0x100000000;
|
||||
}
|
||||
|
||||
let prime = 2;
|
||||
|
||||
for (let i = 0; i < 64; prime++) {
|
||||
let is_prime = true;
|
||||
|
||||
for (let factor = 2; factor * factor <= prime; factor++) {
|
||||
if (prime % factor === 0) {
|
||||
is_prime = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_prime) {
|
||||
if (i < 8) {
|
||||
init[i] = frac(prime ** (1 / 2));
|
||||
}
|
||||
|
||||
key[i] = frac(prime ** (1 / 3));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Uint8Array} bytes */
|
||||
function reverse_endianness(bytes) {
|
||||
for (let i = 0; i < bytes.length; i += 4) {
|
||||
const a = bytes[i + 0];
|
||||
const b = bytes[i + 1];
|
||||
const c = bytes[i + 2];
|
||||
const d = bytes[i + 3];
|
||||
|
||||
bytes[i + 0] = d;
|
||||
bytes[i + 1] = c;
|
||||
bytes[i + 2] = b;
|
||||
bytes[i + 3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} str */
|
||||
function encode(str) {
|
||||
const encoded = encoder.encode(str);
|
||||
const length = encoded.length * 8;
|
||||
|
||||
// result should be a multiple of 512 bits in length,
|
||||
// with room for a 1 (after the data) and two 32-bit
|
||||
// words containing the original input bit length
|
||||
const size = 512 * Math.ceil((length + 65) / 512);
|
||||
const bytes = new Uint8Array(size / 8);
|
||||
bytes.set(encoded);
|
||||
|
||||
// append a 1
|
||||
bytes[encoded.length] = 0b10000000;
|
||||
|
||||
reverse_endianness(bytes);
|
||||
|
||||
// add the input bit length
|
||||
const words = new Uint32Array(bytes.buffer);
|
||||
words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
|
||||
words[words.length - 1] = length;
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
/*
|
||||
Based on https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||
|
||||
MIT License
|
||||
Copyright (c) 2020 Egor Nepomnyaschih
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
||||
|
||||
/** @param {Uint8Array} bytes */
|
||||
export function base64(bytes) {
|
||||
const l = bytes.length;
|
||||
|
||||
let result = '';
|
||||
let i;
|
||||
|
||||
for (i = 2; i < l; i += 3) {
|
||||
result += chars[bytes[i - 2] >> 2];
|
||||
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||
result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
|
||||
result += chars[bytes[i] & 0x3f];
|
||||
}
|
||||
|
||||
if (i === l + 1) {
|
||||
// 1 octet yet to write
|
||||
result += chars[bytes[i - 2] >> 2];
|
||||
result += chars[(bytes[i - 2] & 0x03) << 4];
|
||||
result += '==';
|
||||
}
|
||||
|
||||
if (i === l) {
|
||||
// 2 octets yet to write
|
||||
result += chars[bytes[i - 2] >> 2];
|
||||
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||
result += chars[(bytes[i - 1] & 0x0f) << 2];
|
||||
result += '=';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
254
node_modules/@sveltejs/kit/src/runtime/server/page/csp.js
generated
vendored
Normal file
254
node_modules/@sveltejs/kit/src/runtime/server/page/csp.js
generated
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
import { escape_html_attr } from '../../../utils/escape.js';
|
||||
import { base64, sha256 } from './crypto.js';
|
||||
|
||||
const array = new Uint8Array(16);
|
||||
|
||||
function generate_nonce() {
|
||||
crypto.getRandomValues(array);
|
||||
return base64(array);
|
||||
}
|
||||
|
||||
const quoted = new Set([
|
||||
'self',
|
||||
'unsafe-eval',
|
||||
'unsafe-hashes',
|
||||
'unsafe-inline',
|
||||
'none',
|
||||
'strict-dynamic',
|
||||
'report-sample',
|
||||
'wasm-unsafe-eval',
|
||||
'script'
|
||||
]);
|
||||
|
||||
const crypto_pattern = /^(nonce|sha\d\d\d)-/;
|
||||
|
||||
// CSP and CSP Report Only are extremely similar with a few caveats
|
||||
// the easiest/DRYest way to express this is with some private encapsulation
|
||||
class BaseProvider {
|
||||
/** @type {boolean} */
|
||||
#use_hashes;
|
||||
|
||||
/** @type {boolean} */
|
||||
#script_needs_csp;
|
||||
|
||||
/** @type {boolean} */
|
||||
#style_needs_csp;
|
||||
|
||||
/** @type {import('types').CspDirectives} */
|
||||
#directives;
|
||||
|
||||
/** @type {import('types').Csp.Source[]} */
|
||||
#script_src;
|
||||
|
||||
/** @type {import('types').Csp.Source[]} */
|
||||
#style_src;
|
||||
|
||||
/** @type {string} */
|
||||
#nonce;
|
||||
|
||||
/**
|
||||
* @param {boolean} use_hashes
|
||||
* @param {import('types').CspDirectives} directives
|
||||
* @param {string} nonce
|
||||
*/
|
||||
constructor(use_hashes, directives, nonce) {
|
||||
this.#use_hashes = use_hashes;
|
||||
this.#directives = __SVELTEKIT_DEV__ ? { ...directives } : directives; // clone in dev so we can safely mutate
|
||||
|
||||
const d = this.#directives;
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
// remove strict-dynamic in dev...
|
||||
// TODO reinstate this if we can figure out how to make strict-dynamic work
|
||||
// if (d['default-src']) {
|
||||
// d['default-src'] = d['default-src'].filter((name) => name !== 'strict-dynamic');
|
||||
// if (d['default-src'].length === 0) delete d['default-src'];
|
||||
// }
|
||||
|
||||
// if (d['script-src']) {
|
||||
// d['script-src'] = d['script-src'].filter((name) => name !== 'strict-dynamic');
|
||||
// if (d['script-src'].length === 0) delete d['script-src'];
|
||||
// }
|
||||
|
||||
const effective_style_src = d['style-src'] || d['default-src'];
|
||||
|
||||
// ...and add unsafe-inline so we can inject <style> elements
|
||||
if (effective_style_src && !effective_style_src.includes('unsafe-inline')) {
|
||||
d['style-src'] = [...effective_style_src, 'unsafe-inline'];
|
||||
}
|
||||
}
|
||||
|
||||
this.#script_src = [];
|
||||
this.#style_src = [];
|
||||
|
||||
const effective_script_src = d['script-src'] || d['default-src'];
|
||||
const effective_style_src = d['style-src'] || d['default-src'];
|
||||
|
||||
this.#script_needs_csp =
|
||||
!!effective_script_src &&
|
||||
effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0;
|
||||
|
||||
this.#style_needs_csp =
|
||||
!__SVELTEKIT_DEV__ &&
|
||||
!!effective_style_src &&
|
||||
effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0;
|
||||
|
||||
this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes;
|
||||
this.style_needs_nonce = this.#style_needs_csp && !this.#use_hashes;
|
||||
this.#nonce = nonce;
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_script(content) {
|
||||
if (this.#script_needs_csp) {
|
||||
if (this.#use_hashes) {
|
||||
this.#script_src.push(`sha256-${sha256(content)}`);
|
||||
} else if (this.#script_src.length === 0) {
|
||||
this.#script_src.push(`nonce-${this.#nonce}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_style(content) {
|
||||
if (this.#style_needs_csp) {
|
||||
if (this.#use_hashes) {
|
||||
this.#style_src.push(`sha256-${sha256(content)}`);
|
||||
} else if (this.#style_src.length === 0) {
|
||||
this.#style_src.push(`nonce-${this.#nonce}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [is_meta]
|
||||
*/
|
||||
get_header(is_meta = false) {
|
||||
const header = [];
|
||||
|
||||
// due to browser inconsistencies, we can't append sources to default-src
|
||||
// (specifically, Firefox appears to not ignore nonce-{nonce} directives
|
||||
// on default-src), so we ensure that script-src and style-src exist
|
||||
|
||||
const directives = { ...this.#directives };
|
||||
|
||||
if (this.#style_src.length > 0) {
|
||||
directives['style-src'] = [
|
||||
...(directives['style-src'] || directives['default-src'] || []),
|
||||
...this.#style_src
|
||||
];
|
||||
}
|
||||
|
||||
if (this.#script_src.length > 0) {
|
||||
directives['script-src'] = [
|
||||
...(directives['script-src'] || directives['default-src'] || []),
|
||||
...this.#script_src
|
||||
];
|
||||
}
|
||||
|
||||
for (const key in directives) {
|
||||
if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
|
||||
// these values cannot be used with a <meta> tag
|
||||
// TODO warn?
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-expect-error gimme a break typescript, `key` is obviously a member of internal_directives
|
||||
const value = /** @type {string[] | true} */ (directives[key]);
|
||||
|
||||
if (!value) continue;
|
||||
|
||||
const directive = [key];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((value) => {
|
||||
if (quoted.has(value) || crypto_pattern.test(value)) {
|
||||
directive.push(`'${value}'`);
|
||||
} else {
|
||||
directive.push(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
header.push(directive.join(' '));
|
||||
}
|
||||
|
||||
return header.join('; ');
|
||||
}
|
||||
}
|
||||
|
||||
class CspProvider extends BaseProvider {
|
||||
get_meta() {
|
||||
const content = this.get_header(true);
|
||||
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
return `<meta http-equiv="content-security-policy" content=${escape_html_attr(content)}>`;
|
||||
}
|
||||
}
|
||||
|
||||
class CspReportOnlyProvider extends BaseProvider {
|
||||
/**
|
||||
* @param {boolean} use_hashes
|
||||
* @param {import('types').CspDirectives} directives
|
||||
* @param {string} nonce
|
||||
*/
|
||||
constructor(use_hashes, directives, nonce) {
|
||||
super(use_hashes, directives, nonce);
|
||||
|
||||
if (Object.values(directives).filter((v) => !!v).length > 0) {
|
||||
// If we're generating content-security-policy-report-only,
|
||||
// if there are any directives, we need a report-uri or report-to (or both)
|
||||
// else it's just an expensive noop.
|
||||
const has_report_to = directives['report-to']?.length ?? 0 > 0;
|
||||
const has_report_uri = directives['report-uri']?.length ?? 0 > 0;
|
||||
if (!has_report_to && !has_report_uri) {
|
||||
throw Error(
|
||||
'`content-security-policy-report-only` must be specified with either the `report-to` or `report-uri` directives, or both'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Csp {
|
||||
/** @readonly */
|
||||
nonce = generate_nonce();
|
||||
|
||||
/** @type {CspProvider} */
|
||||
csp_provider;
|
||||
|
||||
/** @type {CspReportOnlyProvider} */
|
||||
report_only_provider;
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').CspConfig} config
|
||||
* @param {import('./types.js').CspOpts} opts
|
||||
*/
|
||||
constructor({ mode, directives, reportOnly }, { prerender }) {
|
||||
const use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
|
||||
this.csp_provider = new CspProvider(use_hashes, directives, this.nonce);
|
||||
this.report_only_provider = new CspReportOnlyProvider(use_hashes, reportOnly, this.nonce);
|
||||
}
|
||||
|
||||
get script_needs_nonce() {
|
||||
return this.csp_provider.script_needs_nonce || this.report_only_provider.script_needs_nonce;
|
||||
}
|
||||
|
||||
get style_needs_nonce() {
|
||||
return this.csp_provider.style_needs_nonce || this.report_only_provider.style_needs_nonce;
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_script(content) {
|
||||
this.csp_provider.add_script(content);
|
||||
this.report_only_provider.add_script(content);
|
||||
}
|
||||
|
||||
/** @param {string} content */
|
||||
add_style(content) {
|
||||
this.csp_provider.add_style(content);
|
||||
this.report_only_provider.add_style(content);
|
||||
}
|
||||
}
|
315
node_modules/@sveltejs/kit/src/runtime/server/page/index.js
generated
vendored
Normal file
315
node_modules/@sveltejs/kit/src/runtime/server/page/index.js
generated
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
import { text } from '../../../exports/index.js';
|
||||
import { compact } from '../../../utils/array.js';
|
||||
import { normalize_error } from '../../../utils/error.js';
|
||||
import { add_data_suffix } from '../../../utils/url.js';
|
||||
import { HttpError, Redirect } from '../../control.js';
|
||||
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js';
|
||||
import {
|
||||
handle_action_json_request,
|
||||
handle_action_request,
|
||||
is_action_json_request,
|
||||
is_action_request
|
||||
} from './actions.js';
|
||||
import { load_data, load_server_data } from './load_data.js';
|
||||
import { render_response } from './render.js';
|
||||
import { respond_with_error } from './respond_with_error.js';
|
||||
import { get_option } from '../../../utils/options.js';
|
||||
import { get_data_json } from '../data/index.js';
|
||||
|
||||
/**
|
||||
* The maximum request depth permitted before assuming we're stuck in an infinite loop
|
||||
*/
|
||||
const MAX_DEPTH = 10;
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').PageNodeIndexes} page
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
||||
* @param {import('types').SSRState} state
|
||||
* @param {import('types').RequiredResolveOptions} resolve_opts
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function render_page(event, page, options, manifest, state, resolve_opts) {
|
||||
if (state.depth > MAX_DEPTH) {
|
||||
// infinite request cycle detected
|
||||
return text(`Not found: ${event.url.pathname}`, {
|
||||
status: 404 // TODO in some cases this should be 500. not sure how to differentiate
|
||||
});
|
||||
}
|
||||
|
||||
if (is_action_json_request(event)) {
|
||||
const node = await manifest._.nodes[page.leaf]();
|
||||
return handle_action_json_request(event, options, node?.server);
|
||||
}
|
||||
|
||||
try {
|
||||
const nodes = await Promise.all([
|
||||
// we use == here rather than === because [undefined] serializes as "[null]"
|
||||
...page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
|
||||
manifest._.nodes[page.leaf]()
|
||||
]);
|
||||
|
||||
const leaf_node = /** @type {import('types').SSRNode} */ (nodes.at(-1));
|
||||
|
||||
let status = 200;
|
||||
|
||||
/** @type {import('@sveltejs/kit').ActionResult | undefined} */
|
||||
let action_result = undefined;
|
||||
|
||||
if (is_action_request(event)) {
|
||||
// for action requests, first call handler in +page.server.js
|
||||
// (this also determines status code)
|
||||
action_result = await handle_action_request(event, leaf_node.server);
|
||||
if (action_result?.type === 'redirect') {
|
||||
return redirect_response(action_result.status, action_result.location);
|
||||
}
|
||||
if (action_result?.type === 'error') {
|
||||
const error = action_result.error;
|
||||
status = error instanceof HttpError ? error.status : 500;
|
||||
}
|
||||
if (action_result?.type === 'failure') {
|
||||
status = action_result.status;
|
||||
}
|
||||
}
|
||||
|
||||
const should_prerender_data = nodes.some((node) => node?.server);
|
||||
const data_pathname = add_data_suffix(event.url.pathname);
|
||||
|
||||
// it's crucial that we do this before returning the non-SSR response, otherwise
|
||||
// SvelteKit will erroneously believe that the path has been prerendered,
|
||||
// causing functions to be omitted from the manifesst generated later
|
||||
const should_prerender = get_option(nodes, 'prerender') ?? false;
|
||||
if (should_prerender) {
|
||||
const mod = leaf_node.server;
|
||||
if (mod?.actions) {
|
||||
throw new Error('Cannot prerender pages with actions');
|
||||
}
|
||||
} else if (state.prerendering) {
|
||||
// if the page isn't marked as prerenderable, then bail out at this point
|
||||
return new Response(undefined, {
|
||||
status: 204
|
||||
});
|
||||
}
|
||||
|
||||
// if we fetch any endpoints while loading data for this page, they should
|
||||
// inherit the prerender option of the page
|
||||
state.prerender_default = should_prerender;
|
||||
|
||||
/** @type {import('./types.js').Fetched[]} */
|
||||
const fetched = [];
|
||||
|
||||
if (get_option(nodes, 'ssr') === false && !state.prerendering) {
|
||||
return await render_response({
|
||||
branch: [],
|
||||
fetched,
|
||||
page_config: {
|
||||
ssr: false,
|
||||
csr: get_option(nodes, 'csr') ?? true
|
||||
},
|
||||
status,
|
||||
error: null,
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Array<import('./types.js').Loaded | null>} */
|
||||
const branch = [];
|
||||
|
||||
/** @type {Error | null} */
|
||||
let load_error = null;
|
||||
|
||||
/** @type {Array<Promise<import('types').ServerDataNode | null>>} */
|
||||
const server_promises = nodes.map((node, i) => {
|
||||
if (load_error) {
|
||||
// if an error happens immediately, don't bother with the rest of the nodes
|
||||
throw load_error;
|
||||
}
|
||||
|
||||
return Promise.resolve().then(async () => {
|
||||
try {
|
||||
if (node === leaf_node && action_result?.type === 'error') {
|
||||
// we wait until here to throw the error so that we can use
|
||||
// any nested +error.svelte components that were defined
|
||||
throw action_result.error;
|
||||
}
|
||||
|
||||
return await load_server_data({
|
||||
event,
|
||||
state,
|
||||
node,
|
||||
parent: async () => {
|
||||
/** @type {Record<string, any>} */
|
||||
const data = {};
|
||||
for (let j = 0; j < i; j += 1) {
|
||||
const parent = await server_promises[j];
|
||||
if (parent) Object.assign(data, await parent.data);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
track_server_fetches: options.track_server_fetches
|
||||
});
|
||||
} catch (e) {
|
||||
load_error = /** @type {Error} */ (e);
|
||||
throw load_error;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const csr = get_option(nodes, 'csr') ?? true;
|
||||
|
||||
/** @type {Array<Promise<Record<string, any> | null>>} */
|
||||
const load_promises = nodes.map((node, i) => {
|
||||
if (load_error) throw load_error;
|
||||
return Promise.resolve().then(async () => {
|
||||
try {
|
||||
return await load_data({
|
||||
event,
|
||||
fetched,
|
||||
node,
|
||||
parent: async () => {
|
||||
const data = {};
|
||||
for (let j = 0; j < i; j += 1) {
|
||||
Object.assign(data, await load_promises[j]);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
resolve_opts,
|
||||
server_data_promise: server_promises[i],
|
||||
state,
|
||||
csr
|
||||
});
|
||||
} catch (e) {
|
||||
load_error = /** @type {Error} */ (e);
|
||||
throw load_error;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// if we don't do this, rejections will be unhandled
|
||||
for (const p of server_promises) p.catch(() => {});
|
||||
for (const p of load_promises) p.catch(() => {});
|
||||
|
||||
for (let i = 0; i < nodes.length; i += 1) {
|
||||
const node = nodes[i];
|
||||
|
||||
if (node) {
|
||||
try {
|
||||
const server_data = await server_promises[i];
|
||||
const data = await load_promises[i];
|
||||
|
||||
branch.push({ node, server_data, data });
|
||||
} catch (e) {
|
||||
const err = normalize_error(e);
|
||||
|
||||
if (err instanceof Redirect) {
|
||||
if (state.prerendering && should_prerender_data) {
|
||||
const body = JSON.stringify({
|
||||
type: 'redirect',
|
||||
location: err.location
|
||||
});
|
||||
|
||||
state.prerendering.dependencies.set(data_pathname, {
|
||||
response: text(body),
|
||||
body
|
||||
});
|
||||
}
|
||||
|
||||
return redirect_response(err.status, err.location);
|
||||
}
|
||||
|
||||
const status = err instanceof HttpError ? err.status : 500;
|
||||
const error = await handle_error_and_jsonify(event, options, err);
|
||||
|
||||
while (i--) {
|
||||
if (page.errors[i]) {
|
||||
const index = /** @type {number} */ (page.errors[i]);
|
||||
const node = await manifest._.nodes[index]();
|
||||
|
||||
let j = i;
|
||||
while (!branch[j]) j -= 1;
|
||||
|
||||
return await render_response({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
resolve_opts,
|
||||
page_config: { ssr: true, csr: true },
|
||||
status,
|
||||
error,
|
||||
branch: compact(branch.slice(0, j + 1)).concat({
|
||||
node,
|
||||
data: null,
|
||||
server_data: null
|
||||
}),
|
||||
fetched
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if we're still here, it means the error happened in the root layout,
|
||||
// which means we have to fall back to error.html
|
||||
return static_error_page(options, status, error.message);
|
||||
}
|
||||
} else {
|
||||
// push an empty slot so we can rewind past gaps to the
|
||||
// layout that corresponds with an +error.svelte page
|
||||
branch.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.prerendering && should_prerender_data) {
|
||||
// ndjson format
|
||||
let { data, chunks } = get_data_json(
|
||||
event,
|
||||
options,
|
||||
branch.map((node) => node?.server_data)
|
||||
);
|
||||
|
||||
if (chunks) {
|
||||
for await (const chunk of chunks) {
|
||||
data += chunk;
|
||||
}
|
||||
}
|
||||
|
||||
state.prerendering.dependencies.set(data_pathname, {
|
||||
response: text(data),
|
||||
body: data
|
||||
});
|
||||
}
|
||||
|
||||
return await render_response({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
resolve_opts,
|
||||
page_config: {
|
||||
csr: get_option(nodes, 'csr') ?? true,
|
||||
ssr: true
|
||||
},
|
||||
status,
|
||||
error: null,
|
||||
branch: compact(branch),
|
||||
action_result,
|
||||
fetched
|
||||
});
|
||||
} catch (e) {
|
||||
// if we end up here, it means the data loaded successfully
|
||||
// but the page failed to render, or that a prerendering error occurred
|
||||
return await respond_with_error({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
status: 500,
|
||||
error: e,
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
}
|
369
node_modules/@sveltejs/kit/src/runtime/server/page/load_data.js
generated
vendored
Normal file
369
node_modules/@sveltejs/kit/src/runtime/server/page/load_data.js
generated
vendored
Normal file
@ -0,0 +1,369 @@
|
||||
import { disable_search, make_trackable } from '../../../utils/url.js';
|
||||
import { unwrap_promises } from '../../../utils/promises.js';
|
||||
import { DEV } from 'esm-env';
|
||||
import { validate_depends } from '../../shared.js';
|
||||
|
||||
/**
|
||||
* Calls the user's server `load` function.
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* state: import('types').SSRState;
|
||||
* node: import('types').SSRNode | undefined;
|
||||
* parent: () => Promise<Record<string, any>>;
|
||||
* track_server_fetches: boolean;
|
||||
* }} opts
|
||||
* @returns {Promise<import('types').ServerDataNode | null>}
|
||||
*/
|
||||
export async function load_server_data({
|
||||
event,
|
||||
state,
|
||||
node,
|
||||
parent,
|
||||
// TODO 2.0: Remove this
|
||||
track_server_fetches
|
||||
}) {
|
||||
if (!node?.server) return null;
|
||||
|
||||
let done = false;
|
||||
|
||||
const uses = {
|
||||
dependencies: new Set(),
|
||||
params: new Set(),
|
||||
parent: false,
|
||||
route: false,
|
||||
url: false
|
||||
};
|
||||
|
||||
const url = make_trackable(event.url, () => {
|
||||
if (DEV && done && !uses.url) {
|
||||
console.warn(
|
||||
`${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.url = true;
|
||||
});
|
||||
|
||||
if (state.prerendering) {
|
||||
disable_search(url);
|
||||
}
|
||||
|
||||
const result = await node.server.load?.call(null, {
|
||||
...event,
|
||||
fetch: (info, init) => {
|
||||
const url = new URL(info instanceof Request ? info.url : info, event.url);
|
||||
|
||||
if (DEV && done && !uses.dependencies.has(url.href)) {
|
||||
console.warn(
|
||||
`${node.server_id}: Calling \`event.fetch(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
|
||||
);
|
||||
}
|
||||
|
||||
// TODO 2.0: Remove this
|
||||
if (track_server_fetches) {
|
||||
uses.dependencies.add(url.href);
|
||||
}
|
||||
|
||||
return event.fetch(info, init);
|
||||
},
|
||||
/** @param {string[]} deps */
|
||||
depends: (...deps) => {
|
||||
for (const dep of deps) {
|
||||
const { href } = new URL(dep, event.url);
|
||||
|
||||
if (DEV) {
|
||||
validate_depends(node.server_id, dep);
|
||||
|
||||
if (done && !uses.dependencies.has(href)) {
|
||||
console.warn(
|
||||
`${node.server_id}: Calling \`depends(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uses.dependencies.add(href);
|
||||
}
|
||||
},
|
||||
params: new Proxy(event.params, {
|
||||
get: (target, key) => {
|
||||
if (DEV && done && typeof key === 'string' && !uses.params.has(key)) {
|
||||
console.warn(
|
||||
`${node.server_id}: Accessing \`params.${String(
|
||||
key
|
||||
)}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the param changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.params.add(key);
|
||||
return target[/** @type {string} */ (key)];
|
||||
}
|
||||
}),
|
||||
parent: async () => {
|
||||
if (DEV && done && !uses.parent) {
|
||||
console.warn(
|
||||
`${node.server_id}: Calling \`parent(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when parent data changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.parent = true;
|
||||
return parent();
|
||||
},
|
||||
route: new Proxy(event.route, {
|
||||
get: (target, key) => {
|
||||
if (DEV && done && typeof key === 'string' && !uses.route) {
|
||||
console.warn(
|
||||
`${node.server_id}: Accessing \`route.${String(
|
||||
key
|
||||
)}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the route changes`
|
||||
);
|
||||
}
|
||||
|
||||
uses.route = true;
|
||||
return target[/** @type {'id'} */ (key)];
|
||||
}
|
||||
}),
|
||||
url
|
||||
});
|
||||
|
||||
const data = result ? await unwrap_promises(result) : null;
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_load_response(data, /** @type {string} */ (event.route.id));
|
||||
}
|
||||
|
||||
done = true;
|
||||
|
||||
return {
|
||||
type: 'data',
|
||||
data,
|
||||
uses,
|
||||
slash: node.server.trailingSlash
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the user's `load` function.
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* fetched: import('./types.js').Fetched[];
|
||||
* node: import('types').SSRNode | undefined;
|
||||
* parent: () => Promise<Record<string, any>>;
|
||||
* resolve_opts: import('types').RequiredResolveOptions;
|
||||
* server_data_promise: Promise<import('types').ServerDataNode | null>;
|
||||
* state: import('types').SSRState;
|
||||
* csr: boolean;
|
||||
* }} opts
|
||||
* @returns {Promise<Record<string, any | Promise<any>> | null>}
|
||||
*/
|
||||
export async function load_data({
|
||||
event,
|
||||
fetched,
|
||||
node,
|
||||
parent,
|
||||
server_data_promise,
|
||||
state,
|
||||
resolve_opts,
|
||||
csr
|
||||
}) {
|
||||
const server_data_node = await server_data_promise;
|
||||
|
||||
if (!node?.universal?.load) {
|
||||
return server_data_node?.data ?? null;
|
||||
}
|
||||
|
||||
const result = await node.universal.load.call(null, {
|
||||
url: event.url,
|
||||
params: event.params,
|
||||
data: server_data_node?.data ?? null,
|
||||
route: event.route,
|
||||
fetch: create_universal_fetch(event, state, fetched, csr, resolve_opts),
|
||||
setHeaders: event.setHeaders,
|
||||
depends: () => {},
|
||||
parent
|
||||
});
|
||||
|
||||
const data = result ? await unwrap_promises(result) : null;
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
validate_load_response(data, /** @type {string} */ (event.route.id));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Pick<import('@sveltejs/kit').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} event
|
||||
* @param {import('types').SSRState} state
|
||||
* @param {import('./types.js').Fetched[]} fetched
|
||||
* @param {boolean} csr
|
||||
* @param {Pick<Required<import('@sveltejs/kit').ResolveOptions>, 'filterSerializedResponseHeaders'>} resolve_opts
|
||||
*/
|
||||
export function create_universal_fetch(event, state, fetched, csr, resolve_opts) {
|
||||
/**
|
||||
* @param {URL | RequestInfo} input
|
||||
* @param {RequestInit} [init]
|
||||
*/
|
||||
return async (input, init) => {
|
||||
const cloned_body = input instanceof Request && input.body ? input.clone().body : null;
|
||||
|
||||
const cloned_headers =
|
||||
input instanceof Request && [...input.headers].length
|
||||
? new Headers(input.headers)
|
||||
: init?.headers;
|
||||
|
||||
let response = await event.fetch(input, init);
|
||||
|
||||
const url = new URL(input instanceof Request ? input.url : input, event.url);
|
||||
const same_origin = url.origin === event.url.origin;
|
||||
|
||||
/** @type {import('types').PrerenderDependency} */
|
||||
let dependency;
|
||||
|
||||
if (same_origin) {
|
||||
if (state.prerendering) {
|
||||
dependency = { response, body: null };
|
||||
state.prerendering.dependencies.set(url.pathname, dependency);
|
||||
}
|
||||
} else {
|
||||
// simulate CORS errors and "no access to body in no-cors mode" server-side for consistency with client-side behaviour
|
||||
const mode = input instanceof Request ? input.mode : init?.mode ?? 'cors';
|
||||
if (mode === 'no-cors') {
|
||||
response = new Response('', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers
|
||||
});
|
||||
} else {
|
||||
const acao = response.headers.get('access-control-allow-origin');
|
||||
if (!acao || (acao !== event.url.origin && acao !== '*')) {
|
||||
throw new Error(
|
||||
`CORS error: ${
|
||||
acao ? 'Incorrect' : 'No'
|
||||
} 'Access-Control-Allow-Origin' header is present on the requested resource`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const proxy = new Proxy(response, {
|
||||
get(response, key, _receiver) {
|
||||
async function text() {
|
||||
const body = await response.text();
|
||||
|
||||
if (!body || typeof body === 'string') {
|
||||
const status_number = Number(response.status);
|
||||
if (isNaN(status_number)) {
|
||||
throw new Error(
|
||||
`response.status is not a number. value: "${
|
||||
response.status
|
||||
}" type: ${typeof response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
fetched.push({
|
||||
url: same_origin ? url.href.slice(event.url.origin.length) : url.href,
|
||||
method: event.request.method,
|
||||
request_body: /** @type {string | ArrayBufferView | undefined} */ (
|
||||
input instanceof Request && cloned_body
|
||||
? await stream_to_string(cloned_body)
|
||||
: init?.body
|
||||
),
|
||||
request_headers: cloned_headers,
|
||||
response_body: body,
|
||||
response
|
||||
});
|
||||
}
|
||||
|
||||
if (dependency) {
|
||||
dependency.body = body;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
if (key === 'arrayBuffer') {
|
||||
return async () => {
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
if (dependency) {
|
||||
dependency.body = new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
// TODO should buffer be inlined into the page (albeit base64'd)?
|
||||
// any conditions in which it shouldn't be?
|
||||
|
||||
return buffer;
|
||||
};
|
||||
}
|
||||
|
||||
if (key === 'text') {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (key === 'json') {
|
||||
return async () => {
|
||||
return JSON.parse(await text());
|
||||
};
|
||||
}
|
||||
|
||||
return Reflect.get(response, key, response);
|
||||
}
|
||||
});
|
||||
|
||||
if (csr) {
|
||||
// ensure that excluded headers can't be read
|
||||
const get = response.headers.get;
|
||||
response.headers.get = (key) => {
|
||||
const lower = key.toLowerCase();
|
||||
const value = get.call(response.headers, lower);
|
||||
if (value && !lower.startsWith('x-sveltekit-')) {
|
||||
const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
|
||||
if (!included) {
|
||||
throw new Error(
|
||||
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#server-hooks-handle (at ${event.route.id})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
return proxy;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ReadableStream<Uint8Array>} stream
|
||||
*/
|
||||
async function stream_to_string(stream) {
|
||||
let result = '';
|
||||
const reader = stream.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
result += decoder.decode(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} data
|
||||
* @param {string} [routeId]
|
||||
*/
|
||||
function validate_load_response(data, routeId) {
|
||||
if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
|
||||
throw new Error(
|
||||
`a load function related to route '${routeId}' returned ${
|
||||
typeof data !== 'object'
|
||||
? `a ${typeof data}`
|
||||
: data instanceof Response
|
||||
? 'a Response object'
|
||||
: Array.isArray(data)
|
||||
? 'an array'
|
||||
: 'a non-plain object'
|
||||
}, but must return a plain object at the top level (i.e. \`return {...}\`)`
|
||||
);
|
||||
}
|
||||
}
|
564
node_modules/@sveltejs/kit/src/runtime/server/page/render.js
generated
vendored
Normal file
564
node_modules/@sveltejs/kit/src/runtime/server/page/render.js
generated
vendored
Normal file
@ -0,0 +1,564 @@
|
||||
import * as devalue from 'devalue';
|
||||
import { readable, writable } from 'svelte/store';
|
||||
import { DEV } from 'esm-env';
|
||||
import * as paths from '__sveltekit/paths';
|
||||
import { hash } from '../../hash.js';
|
||||
import { serialize_data } from './serialize_data.js';
|
||||
import { s } from '../../../utils/misc.js';
|
||||
import { Csp } from './csp.js';
|
||||
import { uneval_action_response } from './actions.js';
|
||||
import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from '../utils.js';
|
||||
import { public_env } from '../../shared-server.js';
|
||||
import { text } from '../../../exports/index.js';
|
||||
import { create_async_iterator } from '../../../utils/streaming.js';
|
||||
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
||||
import { SCHEME } from '../../../utils/url.js';
|
||||
|
||||
// TODO rename this function/module
|
||||
|
||||
const updated = {
|
||||
...readable(false),
|
||||
check: () => false
|
||||
};
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
* Creates the HTML response.
|
||||
* @param {{
|
||||
* branch: Array<import('./types.js').Loaded>;
|
||||
* fetched: Array<import('./types.js').Fetched>;
|
||||
* options: import('types').SSROptions;
|
||||
* manifest: import('@sveltejs/kit').SSRManifest;
|
||||
* state: import('types').SSRState;
|
||||
* page_config: { ssr: boolean; csr: boolean };
|
||||
* status: number;
|
||||
* error: App.Error | null;
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* resolve_opts: import('types').RequiredResolveOptions;
|
||||
* action_result?: import('@sveltejs/kit').ActionResult;
|
||||
* }} opts
|
||||
*/
|
||||
export async function render_response({
|
||||
branch,
|
||||
fetched,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
page_config,
|
||||
status,
|
||||
error = null,
|
||||
event,
|
||||
resolve_opts,
|
||||
action_result
|
||||
}) {
|
||||
if (state.prerendering) {
|
||||
if (options.csp.mode === 'nonce') {
|
||||
throw new Error('Cannot use prerendering if config.kit.csp.mode === "nonce"');
|
||||
}
|
||||
|
||||
if (options.app_template_contains_nonce) {
|
||||
throw new Error('Cannot use prerendering if page template contains %sveltekit.nonce%');
|
||||
}
|
||||
}
|
||||
|
||||
const { client } = manifest._;
|
||||
|
||||
const modulepreloads = new Set(client.imports);
|
||||
const stylesheets = new Set(client.stylesheets);
|
||||
const fonts = new Set(client.fonts);
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const link_header_preloads = new Set();
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
// TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
|
||||
const inline_styles = new Map();
|
||||
|
||||
let rendered;
|
||||
|
||||
const form_value =
|
||||
action_result?.type === 'success' || action_result?.type === 'failure'
|
||||
? action_result.data ?? null
|
||||
: null;
|
||||
|
||||
/** @type {string} */
|
||||
let base = paths.base;
|
||||
|
||||
/** @type {string} */
|
||||
let assets = paths.assets;
|
||||
|
||||
/**
|
||||
* An expression that will evaluate in the client to determine the resolved base path.
|
||||
* We use a relative path when possible to support IPFS, the internet archive, etc.
|
||||
*/
|
||||
let base_expression = s(paths.base);
|
||||
|
||||
// if appropriate, use relative paths for greater portability
|
||||
if (paths.relative !== false && !state.prerendering?.fallback) {
|
||||
const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2);
|
||||
|
||||
base = segments.map(() => '..').join('/') || '.';
|
||||
|
||||
// resolve e.g. '../..' against current location, then remove trailing slash
|
||||
base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`;
|
||||
|
||||
if (!paths.assets || (paths.assets[0] === '/' && paths.assets !== SVELTE_KIT_ASSETS)) {
|
||||
assets = base;
|
||||
}
|
||||
}
|
||||
|
||||
if (page_config.ssr) {
|
||||
if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) {
|
||||
// Can only be the leaf, layouts have a fallback component generated
|
||||
throw new Error(`Missing +page.svelte component for route ${event.route.id}`);
|
||||
}
|
||||
|
||||
/** @type {Record<string, any>} */
|
||||
const props = {
|
||||
stores: {
|
||||
page: writable(null),
|
||||
navigating: writable(null),
|
||||
updated
|
||||
},
|
||||
constructors: await Promise.all(branch.map(({ node }) => node.component())),
|
||||
form: form_value
|
||||
};
|
||||
|
||||
let data = {};
|
||||
|
||||
// props_n (instead of props[n]) makes it easy to avoid
|
||||
// unnecessary updates for layout components
|
||||
for (let i = 0; i < branch.length; i += 1) {
|
||||
data = { ...data, ...branch[i].data };
|
||||
props[`data_${i}`] = data;
|
||||
}
|
||||
|
||||
props.page = {
|
||||
error,
|
||||
params: /** @type {Record<string, any>} */ (event.params),
|
||||
route: event.route,
|
||||
status,
|
||||
url: event.url,
|
||||
data,
|
||||
form: form_value
|
||||
};
|
||||
|
||||
// use relative paths during rendering, so that the resulting HTML is as
|
||||
// portable as possible, but reset afterwards
|
||||
if (paths.relative) paths.override({ base, assets });
|
||||
|
||||
if (__SVELTEKIT_DEV__) {
|
||||
const fetch = globalThis.fetch;
|
||||
let warned = false;
|
||||
globalThis.fetch = (info, init) => {
|
||||
if (typeof info === 'string' && !SCHEME.test(info)) {
|
||||
throw new Error(
|
||||
`Cannot call \`fetch\` eagerly during server side rendering with relative URL (${info}) — put your \`fetch\` calls inside \`onMount\` or a \`load\` function instead`
|
||||
);
|
||||
} else if (!warned) {
|
||||
console.warn(
|
||||
'Avoid calling `fetch` eagerly during server side rendering — put your `fetch` calls inside `onMount` or a `load` function instead'
|
||||
);
|
||||
warned = true;
|
||||
}
|
||||
|
||||
return fetch(info, init);
|
||||
};
|
||||
|
||||
try {
|
||||
rendered = options.root.render(props);
|
||||
} finally {
|
||||
globalThis.fetch = fetch;
|
||||
paths.reset();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
rendered = options.root.render(props);
|
||||
} finally {
|
||||
paths.reset();
|
||||
}
|
||||
}
|
||||
|
||||
for (const { node } of branch) {
|
||||
for (const url of node.imports) modulepreloads.add(url);
|
||||
for (const url of node.stylesheets) stylesheets.add(url);
|
||||
for (const url of node.fonts) fonts.add(url);
|
||||
|
||||
if (node.inline_styles) {
|
||||
Object.entries(await node.inline_styles()).forEach(([k, v]) => inline_styles.set(k, v));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rendered = { head: '', html: '', css: { code: '', map: null } };
|
||||
}
|
||||
|
||||
let head = '';
|
||||
let body = rendered.html;
|
||||
|
||||
const csp = new Csp(options.csp, {
|
||||
prerender: !!state.prerendering
|
||||
});
|
||||
|
||||
/** @param {string} path */
|
||||
const prefixed = (path) => {
|
||||
if (path.startsWith('/')) {
|
||||
// Vite makes the start script available through the base path and without it.
|
||||
// We load it via the base path in order to support remote IDE environments which proxy
|
||||
// all URLs under the base path during development.
|
||||
return paths.base + path;
|
||||
}
|
||||
return `${assets}/${path}`;
|
||||
};
|
||||
|
||||
if (inline_styles.size > 0) {
|
||||
const content = Array.from(inline_styles.values()).join('\n');
|
||||
|
||||
const attributes = __SVELTEKIT_DEV__ ? [' data-sveltekit'] : [];
|
||||
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
|
||||
|
||||
csp.add_style(content);
|
||||
|
||||
head += `\n\t<style${attributes.join('')}>${content}</style>`;
|
||||
}
|
||||
|
||||
for (const dep of stylesheets) {
|
||||
const path = prefixed(dep);
|
||||
|
||||
const attributes = ['rel="stylesheet"'];
|
||||
|
||||
if (inline_styles.has(dep)) {
|
||||
// don't load stylesheets that are already inlined
|
||||
// include them in disabled state so that Vite can detect them and doesn't try to add them
|
||||
attributes.push('disabled', 'media="(max-width: 0)"');
|
||||
} else {
|
||||
if (resolve_opts.preload({ type: 'css', path })) {
|
||||
const preload_atts = ['rel="preload"', 'as="style"'];
|
||||
link_header_preloads.add(`<${encodeURI(path)}>; ${preload_atts.join(';')}; nopush`);
|
||||
}
|
||||
}
|
||||
|
||||
head += `\n\t\t<link href="${path}" ${attributes.join(' ')}>`;
|
||||
}
|
||||
|
||||
for (const dep of fonts) {
|
||||
const path = prefixed(dep);
|
||||
|
||||
if (resolve_opts.preload({ type: 'font', path })) {
|
||||
const ext = dep.slice(dep.lastIndexOf('.') + 1);
|
||||
const attributes = [
|
||||
'rel="preload"',
|
||||
'as="font"',
|
||||
`type="font/${ext}"`,
|
||||
`href="${path}"`,
|
||||
'crossorigin'
|
||||
];
|
||||
|
||||
head += `\n\t\t<link ${attributes.join(' ')}>`;
|
||||
}
|
||||
}
|
||||
|
||||
const global = __SVELTEKIT_DEV__ ? '__sveltekit_dev' : `__sveltekit_${options.version_hash}`;
|
||||
|
||||
const { data, chunks } = get_data(
|
||||
event,
|
||||
options,
|
||||
branch.map((b) => b.server_data),
|
||||
global
|
||||
);
|
||||
|
||||
if (page_config.ssr && page_config.csr) {
|
||||
body += `\n\t\t\t${fetched
|
||||
.map((item) =>
|
||||
serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
|
||||
)
|
||||
.join('\n\t\t\t')}`;
|
||||
}
|
||||
|
||||
if (page_config.csr) {
|
||||
const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter(
|
||||
(path) => resolve_opts.preload({ type: 'js', path })
|
||||
);
|
||||
|
||||
for (const path of included_modulepreloads) {
|
||||
// see the kit.output.preloadStrategy option for details on why we have multiple options here
|
||||
link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
|
||||
if (options.preload_strategy !== 'modulepreload') {
|
||||
head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
|
||||
} else if (state.prerendering) {
|
||||
head += `\n\t\t<link rel="modulepreload" href="${path}">`;
|
||||
}
|
||||
}
|
||||
|
||||
const blocks = [];
|
||||
|
||||
const properties = [
|
||||
paths.assets && `assets: ${s(paths.assets)}`,
|
||||
`base: ${base_expression}`,
|
||||
`env: ${s(public_env)}`
|
||||
].filter(Boolean);
|
||||
|
||||
if (chunks) {
|
||||
blocks.push('const deferred = new Map();');
|
||||
|
||||
properties.push(`defer: (id) => new Promise((fulfil, reject) => {
|
||||
deferred.set(id, { fulfil, reject });
|
||||
})`);
|
||||
|
||||
properties.push(`resolve: ({ id, data, error }) => {
|
||||
const { fulfil, reject } = deferred.get(id);
|
||||
deferred.delete(id);
|
||||
|
||||
if (error) reject(error);
|
||||
else fulfil(data);
|
||||
}`);
|
||||
}
|
||||
|
||||
blocks.push(`${global} = {
|
||||
${properties.join(',\n\t\t\t\t\t\t')}
|
||||
};`);
|
||||
|
||||
const args = ['app', 'element'];
|
||||
|
||||
blocks.push('const element = document.currentScript.parentElement;');
|
||||
|
||||
if (page_config.ssr) {
|
||||
const serialized = { form: 'null', error: 'null' };
|
||||
|
||||
blocks.push(`const data = ${data};`);
|
||||
|
||||
if (form_value) {
|
||||
serialized.form = uneval_action_response(
|
||||
form_value,
|
||||
/** @type {string} */ (event.route.id)
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
serialized.error = devalue.uneval(error);
|
||||
}
|
||||
|
||||
const hydrate = [
|
||||
`node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`,
|
||||
'data',
|
||||
`form: ${serialized.form}`,
|
||||
`error: ${serialized.error}`
|
||||
];
|
||||
|
||||
if (status !== 200) {
|
||||
hydrate.push(`status: ${status}`);
|
||||
}
|
||||
|
||||
if (options.embedded) {
|
||||
hydrate.push(`params: ${devalue.uneval(event.params)}`, `route: ${s(event.route)}`);
|
||||
}
|
||||
|
||||
args.push(`{\n\t\t\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t\t\t')}\n\t\t\t\t\t\t}`);
|
||||
}
|
||||
|
||||
blocks.push(`Promise.all([
|
||||
import(${s(prefixed(client.start))}),
|
||||
import(${s(prefixed(client.app))})
|
||||
]).then(([kit, app]) => {
|
||||
kit.start(${args.join(', ')});
|
||||
});`);
|
||||
|
||||
if (options.service_worker) {
|
||||
const opts = __SVELTEKIT_DEV__ ? ", { type: 'module' }" : '';
|
||||
|
||||
// we use an anonymous function instead of an arrow function to support
|
||||
// older browsers (https://github.com/sveltejs/kit/pull/5417)
|
||||
blocks.push(`if ('serviceWorker' in navigator) {
|
||||
addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
|
||||
});
|
||||
}`);
|
||||
}
|
||||
|
||||
const init_app = `
|
||||
{
|
||||
${blocks.join('\n\n\t\t\t\t\t')}
|
||||
}
|
||||
`;
|
||||
csp.add_script(init_app);
|
||||
|
||||
body += `\n\t\t\t<script${
|
||||
csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''
|
||||
}>${init_app}</script>\n\t\t`;
|
||||
}
|
||||
|
||||
const headers = new Headers({
|
||||
'x-sveltekit-page': 'true',
|
||||
'content-type': 'text/html'
|
||||
});
|
||||
|
||||
if (state.prerendering) {
|
||||
// TODO read headers set with setHeaders and convert into http-equiv where possible
|
||||
const http_equiv = [];
|
||||
|
||||
const csp_headers = csp.csp_provider.get_meta();
|
||||
if (csp_headers) {
|
||||
http_equiv.push(csp_headers);
|
||||
}
|
||||
|
||||
if (state.prerendering.cache) {
|
||||
http_equiv.push(`<meta http-equiv="cache-control" content="${state.prerendering.cache}">`);
|
||||
}
|
||||
|
||||
if (http_equiv.length > 0) {
|
||||
head = http_equiv.join('\n') + head;
|
||||
}
|
||||
} else {
|
||||
const csp_header = csp.csp_provider.get_header();
|
||||
if (csp_header) {
|
||||
headers.set('content-security-policy', csp_header);
|
||||
}
|
||||
const report_only_header = csp.report_only_provider.get_header();
|
||||
if (report_only_header) {
|
||||
headers.set('content-security-policy-report-only', report_only_header);
|
||||
}
|
||||
|
||||
if (link_header_preloads.size) {
|
||||
headers.set('link', Array.from(link_header_preloads).join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
// add the content after the script/css links so the link elements are parsed first
|
||||
head += rendered.head;
|
||||
|
||||
const html = options.templates.app({
|
||||
head,
|
||||
body,
|
||||
assets,
|
||||
nonce: /** @type {string} */ (csp.nonce),
|
||||
env: public_env
|
||||
});
|
||||
|
||||
// TODO flush chunks as early as we can
|
||||
const transformed =
|
||||
(await resolve_opts.transformPageChunk({
|
||||
html,
|
||||
done: true
|
||||
})) || '';
|
||||
|
||||
if (!chunks) {
|
||||
headers.set('etag', `"${hash(transformed)}"`);
|
||||
}
|
||||
|
||||
if (DEV) {
|
||||
if (page_config.csr) {
|
||||
if (transformed.split('<!--').length < html.split('<!--').length) {
|
||||
// the \u001B stuff is ANSI codes, so that we don't need to add a library to the runtime
|
||||
// https://svelte.dev/repl/1b3f49696f0c44c881c34587f2537aa2
|
||||
console.warn(
|
||||
"\u001B[1m\u001B[31mRemoving comments in transformPageChunk can break Svelte's hydration\u001B[39m\u001B[22m"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (chunks) {
|
||||
console.warn(
|
||||
'\u001B[1m\u001B[31mReturning promises from server `load` functions will only work if `csr === true`\u001B[39m\u001B[22m'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !chunks
|
||||
? text(transformed, {
|
||||
status,
|
||||
headers
|
||||
})
|
||||
: new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue(encoder.encode(transformed + '\n'));
|
||||
for await (const chunk of chunks) {
|
||||
controller.enqueue(encoder.encode(chunk));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
|
||||
type: 'bytes'
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'text/html'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the serialized data contains promises, `chunks` will be an
|
||||
* async iterable containing their resolutions
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {Array<import('types').ServerDataNode | null>} nodes
|
||||
* @param {string} global
|
||||
* @returns {{ data: string, chunks: AsyncIterable<string> | null }}
|
||||
*/
|
||||
function get_data(event, options, nodes, global) {
|
||||
let promise_id = 1;
|
||||
let count = 0;
|
||||
|
||||
const { iterator, push, done } = create_async_iterator();
|
||||
|
||||
/** @param {any} thing */
|
||||
function replacer(thing) {
|
||||
if (typeof thing?.then === 'function') {
|
||||
const id = promise_id++;
|
||||
count += 1;
|
||||
|
||||
thing
|
||||
.then(/** @param {any} data */ (data) => ({ data }))
|
||||
.catch(
|
||||
/** @param {any} error */ async (error) => ({
|
||||
error: await handle_error_and_jsonify(event, options, error)
|
||||
})
|
||||
)
|
||||
.then(
|
||||
/**
|
||||
* @param {{data: any; error: any}} result
|
||||
*/
|
||||
async ({ data, error }) => {
|
||||
count -= 1;
|
||||
|
||||
let str;
|
||||
try {
|
||||
str = devalue.uneval({ id, data, error }, replacer);
|
||||
} catch (e) {
|
||||
error = await handle_error_and_jsonify(
|
||||
event,
|
||||
options,
|
||||
new Error(`Failed to serialize promise while rendering ${event.route.id}`)
|
||||
);
|
||||
data = undefined;
|
||||
str = devalue.uneval({ id, data, error }, replacer);
|
||||
}
|
||||
|
||||
push(`<script>${global}.resolve(${str})</script>\n`);
|
||||
if (count === 0) done();
|
||||
}
|
||||
);
|
||||
|
||||
return `${global}.defer(${id})`;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const strings = nodes.map((node) => {
|
||||
if (!node) return 'null';
|
||||
|
||||
return `{"type":"data","data":${devalue.uneval(node.data, replacer)},${stringify_uses(node)}${
|
||||
node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''
|
||||
}}`;
|
||||
});
|
||||
|
||||
return {
|
||||
data: `[${strings.join(',')}]`,
|
||||
chunks: count > 0 ? iterator : null
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
|
||||
}
|
||||
}
|
111
node_modules/@sveltejs/kit/src/runtime/server/page/respond_with_error.js
generated
vendored
Normal file
111
node_modules/@sveltejs/kit/src/runtime/server/page/respond_with_error.js
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
import { render_response } from './render.js';
|
||||
import { load_data, load_server_data } from './load_data.js';
|
||||
import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
|
||||
import { get_option } from '../../../utils/options.js';
|
||||
import { HttpError, Redirect } from '../../control.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./types.js').Loaded} Loaded
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* event: import('@sveltejs/kit').RequestEvent;
|
||||
* options: import('types').SSROptions;
|
||||
* manifest: import('@sveltejs/kit').SSRManifest;
|
||||
* state: import('types').SSRState;
|
||||
* status: number;
|
||||
* error: unknown;
|
||||
* resolve_opts: import('types').RequiredResolveOptions;
|
||||
* }} opts
|
||||
*/
|
||||
export async function respond_with_error({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
status,
|
||||
error,
|
||||
resolve_opts
|
||||
}) {
|
||||
// reroute to the fallback page to prevent an infinite chain of requests.
|
||||
if (event.request.headers.get('x-sveltekit-error')) {
|
||||
return static_error_page(options, status, /** @type {Error} */ (error).message);
|
||||
}
|
||||
|
||||
/** @type {import('./types.js').Fetched[]} */
|
||||
const fetched = [];
|
||||
|
||||
try {
|
||||
const branch = [];
|
||||
const default_layout = await manifest._.nodes[0](); // 0 is always the root layout
|
||||
const ssr = get_option([default_layout], 'ssr') ?? true;
|
||||
const csr = get_option([default_layout], 'csr') ?? true;
|
||||
|
||||
if (ssr) {
|
||||
state.error = true;
|
||||
|
||||
const server_data_promise = load_server_data({
|
||||
event,
|
||||
state,
|
||||
node: default_layout,
|
||||
parent: async () => ({}),
|
||||
track_server_fetches: options.track_server_fetches
|
||||
});
|
||||
|
||||
const server_data = await server_data_promise;
|
||||
|
||||
const data = await load_data({
|
||||
event,
|
||||
fetched,
|
||||
node: default_layout,
|
||||
parent: async () => ({}),
|
||||
resolve_opts,
|
||||
server_data_promise,
|
||||
state,
|
||||
csr
|
||||
});
|
||||
|
||||
branch.push(
|
||||
{
|
||||
node: default_layout,
|
||||
server_data,
|
||||
data
|
||||
},
|
||||
{
|
||||
node: await manifest._.nodes[1](), // 1 is always the root error
|
||||
data: null,
|
||||
server_data: null
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return await render_response({
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
page_config: {
|
||||
ssr,
|
||||
csr: get_option([default_layout], 'csr') ?? true
|
||||
},
|
||||
status,
|
||||
error: await handle_error_and_jsonify(event, options, error),
|
||||
branch,
|
||||
fetched,
|
||||
event,
|
||||
resolve_opts
|
||||
});
|
||||
} catch (e) {
|
||||
// Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
|
||||
// we end up here.
|
||||
if (e instanceof Redirect) {
|
||||
return redirect_response(e.status, e.location);
|
||||
}
|
||||
|
||||
return static_error_page(
|
||||
options,
|
||||
e instanceof HttpError ? e.status : 500,
|
||||
(await handle_error_and_jsonify(event, options, e)).message
|
||||
);
|
||||
}
|
||||
}
|
103
node_modules/@sveltejs/kit/src/runtime/server/page/serialize_data.js
generated
vendored
Normal file
103
node_modules/@sveltejs/kit/src/runtime/server/page/serialize_data.js
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
import { escape_html_attr } from '../../../utils/escape.js';
|
||||
import { hash } from '../../hash.js';
|
||||
|
||||
/**
|
||||
* Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
|
||||
*
|
||||
* The first closes the script element, so everything after is treated as raw HTML.
|
||||
* The second disables further parsing until `-->`, so the script element might be unexpectedly
|
||||
* kept open until until an unrelated HTML comment in the page.
|
||||
*
|
||||
* U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
|
||||
* browsers.
|
||||
*
|
||||
* @see tests for unsafe parsing examples.
|
||||
* @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
|
||||
* @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
|
||||
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
|
||||
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
|
||||
* @see https://github.com/tc39/proposal-json-superset
|
||||
* @type {Record<string, string>}
|
||||
*/
|
||||
const replacements = {
|
||||
'<': '\\u003C',
|
||||
'\u2028': '\\u2028',
|
||||
'\u2029': '\\u2029'
|
||||
};
|
||||
|
||||
const pattern = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
|
||||
|
||||
/**
|
||||
* Generates a raw HTML string containing a safe script element carrying data and associated attributes.
|
||||
*
|
||||
* It escapes all the special characters needed to guarantee the element is unbroken, but care must
|
||||
* be taken to ensure it is inserted in the document at an acceptable position for a script element,
|
||||
* and that the resulting string isn't further modified.
|
||||
*
|
||||
* @param {import('./types.js').Fetched} fetched
|
||||
* @param {(name: string, value: string) => boolean} filter
|
||||
* @param {boolean} [prerendering]
|
||||
* @returns {string} The raw HTML of a script element carrying the JSON payload.
|
||||
* @example const html = serialize_data('/data.json', null, { foo: 'bar' });
|
||||
*/
|
||||
export function serialize_data(fetched, filter, prerendering = false) {
|
||||
/** @type {Record<string, string>} */
|
||||
const headers = {};
|
||||
|
||||
let cache_control = null;
|
||||
let age = null;
|
||||
let varyAny = false;
|
||||
|
||||
for (const [key, value] of fetched.response.headers) {
|
||||
if (filter(key, value)) {
|
||||
headers[key] = value;
|
||||
}
|
||||
|
||||
if (key === 'cache-control') cache_control = value;
|
||||
else if (key === 'age') age = value;
|
||||
else if (key === 'vary' && value.trim() === '*') varyAny = true;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
status: fetched.response.status,
|
||||
statusText: fetched.response.statusText,
|
||||
headers,
|
||||
body: fetched.response_body
|
||||
};
|
||||
|
||||
const safe_payload = JSON.stringify(payload).replace(pattern, (match) => replacements[match]);
|
||||
|
||||
const attrs = [
|
||||
'type="application/json"',
|
||||
'data-sveltekit-fetched',
|
||||
`data-url=${escape_html_attr(fetched.url)}`
|
||||
];
|
||||
|
||||
if (fetched.request_headers || fetched.request_body) {
|
||||
/** @type {import('types').StrictBody[]} */
|
||||
const values = [];
|
||||
|
||||
if (fetched.request_headers) {
|
||||
values.push([...new Headers(fetched.request_headers)].join(','));
|
||||
}
|
||||
|
||||
if (fetched.request_body) {
|
||||
values.push(fetched.request_body);
|
||||
}
|
||||
|
||||
attrs.push(`data-hash="${hash(...values)}"`);
|
||||
}
|
||||
|
||||
// Compute the time the response should be cached, taking into account max-age and age.
|
||||
// Do not cache at all if a `Vary: *` header is present, as this indicates that the
|
||||
// cache is likely to get busted.
|
||||
if (!prerendering && fetched.method === 'GET' && cache_control && !varyAny) {
|
||||
const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control);
|
||||
if (match) {
|
||||
const ttl = +match[1] - +(age ?? '0');
|
||||
attrs.push(`data-ttl="${ttl}"`);
|
||||
}
|
||||
}
|
||||
|
||||
return `<script ${attrs.join(' ')}>${safe_payload}</script>`;
|
||||
}
|
35
node_modules/@sveltejs/kit/src/runtime/server/page/types.d.ts
generated
vendored
Normal file
35
node_modules/@sveltejs/kit/src/runtime/server/page/types.d.ts
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
import { CookieSerializeOptions } from 'cookie';
|
||||
import { SSRNode, CspDirectives, ServerDataNode } from 'types';
|
||||
|
||||
export interface Fetched {
|
||||
url: string;
|
||||
method: string;
|
||||
request_body?: string | ArrayBufferView | null;
|
||||
request_headers?: HeadersInit | undefined;
|
||||
response_body: string;
|
||||
response: Response;
|
||||
}
|
||||
|
||||
export type Loaded = {
|
||||
node: SSRNode;
|
||||
data: Record<string, any> | null;
|
||||
server_data: ServerDataNode | null;
|
||||
};
|
||||
|
||||
type CspMode = 'hash' | 'nonce' | 'auto';
|
||||
|
||||
export interface CspConfig {
|
||||
mode: CspMode;
|
||||
directives: CspDirectives;
|
||||
reportOnly: CspDirectives;
|
||||
}
|
||||
|
||||
export interface CspOpts {
|
||||
prerender: boolean;
|
||||
}
|
||||
|
||||
export interface Cookie {
|
||||
name: string;
|
||||
value: string;
|
||||
options: CookieSerializeOptions;
|
||||
}
|
511
node_modules/@sveltejs/kit/src/runtime/server/respond.js
generated
vendored
Normal file
511
node_modules/@sveltejs/kit/src/runtime/server/respond.js
generated
vendored
Normal file
@ -0,0 +1,511 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { base } from '__sveltekit/paths';
|
||||
import { is_endpoint_request, render_endpoint } from './endpoint.js';
|
||||
import { render_page } from './page/index.js';
|
||||
import { render_response } from './page/render.js';
|
||||
import { respond_with_error } from './page/respond_with_error.js';
|
||||
import { is_form_content_type } from '../../utils/http.js';
|
||||
import { handle_fatal_error, method_not_allowed, redirect_response } from './utils.js';
|
||||
import {
|
||||
decode_pathname,
|
||||
decode_params,
|
||||
disable_search,
|
||||
has_data_suffix,
|
||||
normalize_path,
|
||||
strip_data_suffix
|
||||
} from '../../utils/url.js';
|
||||
import { exec } from '../../utils/routing.js';
|
||||
import { redirect_json_response, render_data } from './data/index.js';
|
||||
import { add_cookies_to_headers, get_cookies } from './cookie.js';
|
||||
import { create_fetch } from './fetch.js';
|
||||
import { Redirect } from '../control.js';
|
||||
import {
|
||||
validate_layout_exports,
|
||||
validate_layout_server_exports,
|
||||
validate_page_exports,
|
||||
validate_page_server_exports,
|
||||
validate_server_exports
|
||||
} from '../../utils/exports.js';
|
||||
import { get_option } from '../../utils/options.js';
|
||||
import { error, json, text } from '../../exports/index.js';
|
||||
import { action_json_redirect, is_action_json_request } from './page/actions.js';
|
||||
import { INVALIDATED_PARAM, TRAILING_SLASH_PARAM } from '../shared.js';
|
||||
|
||||
/* global __SVELTEKIT_ADAPTER_NAME__ */
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions['transformPageChunk']} */
|
||||
const default_transform = ({ html }) => html;
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions['filterSerializedResponseHeaders']} */
|
||||
const default_filter = () => false;
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions['preload']} */
|
||||
const default_preload = ({ type }) => type === 'js' || type === 'css';
|
||||
|
||||
const page_methods = new Set(['GET', 'HEAD', 'POST']);
|
||||
|
||||
const allowed_page_methods = new Set(['GET', 'HEAD', 'OPTIONS']);
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {import('@sveltejs/kit').SSRManifest} manifest
|
||||
* @param {import('types').SSRState} state
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function respond(request, options, manifest, state) {
|
||||
/** URL but stripped from the potential `/__data.json` suffix and its search param */
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (options.csrf_check_origin) {
|
||||
const forbidden =
|
||||
is_form_content_type(request) &&
|
||||
(request.method === 'POST' ||
|
||||
request.method === 'PUT' ||
|
||||
request.method === 'PATCH' ||
|
||||
request.method === 'DELETE') &&
|
||||
request.headers.get('origin') !== url.origin;
|
||||
|
||||
if (forbidden) {
|
||||
const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);
|
||||
if (request.headers.get('accept') === 'application/json') {
|
||||
return json(csrf_error.body, { status: csrf_error.status });
|
||||
}
|
||||
return text(csrf_error.body.message, { status: csrf_error.status });
|
||||
}
|
||||
}
|
||||
|
||||
let decoded;
|
||||
try {
|
||||
decoded = decode_pathname(url.pathname);
|
||||
} catch {
|
||||
return text('Malformed URI', { status: 400 });
|
||||
}
|
||||
|
||||
/** @type {import('types').SSRRoute | null} */
|
||||
let route = null;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
let params = {};
|
||||
|
||||
if (base && !state.prerendering?.fallback) {
|
||||
if (!decoded.startsWith(base)) {
|
||||
return text('Not found', { status: 404 });
|
||||
}
|
||||
decoded = decoded.slice(base.length) || '/';
|
||||
}
|
||||
|
||||
const is_data_request = has_data_suffix(decoded);
|
||||
/** @type {boolean[] | undefined} */
|
||||
let invalidated_data_nodes;
|
||||
if (is_data_request) {
|
||||
decoded = strip_data_suffix(decoded) || '/';
|
||||
url.pathname =
|
||||
strip_data_suffix(url.pathname) +
|
||||
(url.searchParams.get(TRAILING_SLASH_PARAM) === '1' ? '/' : '') || '/';
|
||||
url.searchParams.delete(TRAILING_SLASH_PARAM);
|
||||
invalidated_data_nodes = url.searchParams
|
||||
.get(INVALIDATED_PARAM)
|
||||
?.split('')
|
||||
.map((node) => node === '1');
|
||||
url.searchParams.delete(INVALIDATED_PARAM);
|
||||
}
|
||||
|
||||
if (!state.prerendering?.fallback) {
|
||||
// TODO this could theoretically break — should probably be inside a try-catch
|
||||
const matchers = await manifest._.matchers();
|
||||
|
||||
for (const candidate of manifest._.routes) {
|
||||
const match = candidate.pattern.exec(decoded);
|
||||
if (!match) continue;
|
||||
|
||||
const matched = exec(match, candidate.params, matchers);
|
||||
if (matched) {
|
||||
route = candidate;
|
||||
params = decode_params(matched);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('types').TrailingSlash | void} */
|
||||
let trailing_slash = undefined;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const headers = {};
|
||||
|
||||
/** @type {Record<string, import('./page/types.js').Cookie>} */
|
||||
let cookies_to_add = {};
|
||||
|
||||
/** @type {import('@sveltejs/kit').RequestEvent} */
|
||||
const event = {
|
||||
// @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself
|
||||
cookies: null,
|
||||
// @ts-expect-error
|
||||
fetch: null,
|
||||
getClientAddress:
|
||||
state.getClientAddress ||
|
||||
(() => {
|
||||
throw new Error(
|
||||
`${__SVELTEKIT_ADAPTER_NAME__} does not specify getClientAddress. Please raise an issue`
|
||||
);
|
||||
}),
|
||||
locals: {},
|
||||
params,
|
||||
platform: state.platform,
|
||||
request,
|
||||
route: { id: route?.id ?? null },
|
||||
setHeaders: (new_headers) => {
|
||||
for (const key in new_headers) {
|
||||
const lower = key.toLowerCase();
|
||||
const value = new_headers[key];
|
||||
|
||||
if (lower === 'set-cookie') {
|
||||
throw new Error(
|
||||
'Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies'
|
||||
);
|
||||
} else if (lower in headers) {
|
||||
throw new Error(`"${key}" header is already set`);
|
||||
} else {
|
||||
headers[lower] = value;
|
||||
|
||||
if (state.prerendering && lower === 'cache-control') {
|
||||
state.prerendering.cache = /** @type {string} */ (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
url,
|
||||
isDataRequest: is_data_request,
|
||||
isSubRequest: state.depth > 0
|
||||
};
|
||||
|
||||
/** @type {import('types').RequiredResolveOptions} */
|
||||
let resolve_opts = {
|
||||
transformPageChunk: default_transform,
|
||||
filterSerializedResponseHeaders: default_filter,
|
||||
preload: default_preload
|
||||
};
|
||||
|
||||
try {
|
||||
// determine whether we need to redirect to add/remove a trailing slash
|
||||
if (route) {
|
||||
// if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`,
|
||||
// regardless of the `trailingSlash` route option
|
||||
if (url.pathname === base || url.pathname === base + '/') {
|
||||
trailing_slash = 'always';
|
||||
} else if (route.page) {
|
||||
const nodes = await Promise.all([
|
||||
// we use == here rather than === because [undefined] serializes as "[null]"
|
||||
...route.page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
|
||||
manifest._.nodes[route.page.leaf]()
|
||||
]);
|
||||
|
||||
if (DEV) {
|
||||
const layouts = nodes.slice(0, -1);
|
||||
const page = nodes.at(-1);
|
||||
|
||||
for (const layout of layouts) {
|
||||
if (layout) {
|
||||
validate_layout_server_exports(
|
||||
layout.server,
|
||||
/** @type {string} */ (layout.server_id)
|
||||
);
|
||||
validate_layout_exports(
|
||||
layout.universal,
|
||||
/** @type {string} */ (layout.universal_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (page) {
|
||||
validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
|
||||
validate_page_exports(page.universal, /** @type {string} */ (page.universal_id));
|
||||
}
|
||||
}
|
||||
|
||||
trailing_slash = get_option(nodes, 'trailingSlash');
|
||||
} else if (route.endpoint) {
|
||||
const node = await route.endpoint();
|
||||
trailing_slash = node.trailingSlash;
|
||||
|
||||
if (DEV) {
|
||||
validate_server_exports(node, /** @type {string} */ (route.endpoint_id));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_data_request) {
|
||||
const normalized = normalize_path(url.pathname, trailing_slash ?? 'never');
|
||||
|
||||
if (normalized !== url.pathname && !state.prerendering?.fallback) {
|
||||
return new Response(undefined, {
|
||||
status: 308,
|
||||
headers: {
|
||||
'x-sveltekit-normalize': '1',
|
||||
location:
|
||||
// ensure paths starting with '//' are not treated as protocol-relative
|
||||
(normalized.startsWith('//') ? url.origin + normalized : normalized) +
|
||||
(url.search === '?' ? '' : url.search)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { cookies, new_cookies, get_cookie_header, set_internal } = get_cookies(
|
||||
request,
|
||||
url,
|
||||
trailing_slash ?? 'never'
|
||||
);
|
||||
|
||||
cookies_to_add = new_cookies;
|
||||
event.cookies = cookies;
|
||||
event.fetch = create_fetch({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
get_cookie_header,
|
||||
set_internal
|
||||
});
|
||||
|
||||
if (state.prerendering && !state.prerendering.fallback) disable_search(url);
|
||||
|
||||
const response = await options.hooks.handle({
|
||||
event,
|
||||
resolve: (event, opts) =>
|
||||
resolve(event, opts).then((response) => {
|
||||
// add headers/cookies here, rather than inside `resolve`, so that we
|
||||
// can do it once for all responses instead of once per `return`
|
||||
for (const key in headers) {
|
||||
const value = headers[key];
|
||||
response.headers.set(key, /** @type {string} */ (value));
|
||||
}
|
||||
|
||||
add_cookies_to_headers(response.headers, Object.values(cookies_to_add));
|
||||
|
||||
if (state.prerendering && event.route.id !== null) {
|
||||
response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id));
|
||||
}
|
||||
|
||||
return response;
|
||||
})
|
||||
});
|
||||
|
||||
// respond with 304 if etag matches
|
||||
if (response.status === 200 && response.headers.has('etag')) {
|
||||
let if_none_match_value = request.headers.get('if-none-match');
|
||||
|
||||
// ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
|
||||
if (if_none_match_value?.startsWith('W/"')) {
|
||||
if_none_match_value = if_none_match_value.substring(2);
|
||||
}
|
||||
|
||||
const etag = /** @type {string} */ (response.headers.get('etag'));
|
||||
|
||||
if (if_none_match_value === etag) {
|
||||
const headers = new Headers({ etag });
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie
|
||||
for (const key of [
|
||||
'cache-control',
|
||||
'content-location',
|
||||
'date',
|
||||
'expires',
|
||||
'vary',
|
||||
'set-cookie'
|
||||
]) {
|
||||
const value = response.headers.get(key);
|
||||
if (value) headers.set(key, value);
|
||||
}
|
||||
|
||||
return new Response(undefined, {
|
||||
status: 304,
|
||||
headers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case: If user does `return Response(30x)` in handle hook while processing a data request,
|
||||
// we need to transform the redirect response to a corresponding JSON response.
|
||||
if (is_data_request && response.status >= 300 && response.status <= 308) {
|
||||
const location = response.headers.get('location');
|
||||
if (location) {
|
||||
return redirect_json_response(new Redirect(/** @type {any} */ (response.status), location));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
if (e instanceof Redirect) {
|
||||
const response = is_data_request
|
||||
? redirect_json_response(e)
|
||||
: route?.page && is_action_json_request(event)
|
||||
? action_json_redirect(e)
|
||||
: redirect_response(e.status, e.location);
|
||||
add_cookies_to_headers(response.headers, Object.values(cookies_to_add));
|
||||
return response;
|
||||
}
|
||||
return await handle_fatal_error(event, options, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('@sveltejs/kit').ResolveOptions} [opts]
|
||||
*/
|
||||
async function resolve(event, opts) {
|
||||
try {
|
||||
if (opts) {
|
||||
if ('ssr' in opts) {
|
||||
throw new Error(
|
||||
'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
|
||||
);
|
||||
}
|
||||
|
||||
resolve_opts = {
|
||||
transformPageChunk: opts.transformPageChunk || default_transform,
|
||||
filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter,
|
||||
preload: opts.preload || default_preload
|
||||
};
|
||||
}
|
||||
|
||||
if (state.prerendering?.fallback) {
|
||||
return await render_response({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
page_config: { ssr: false, csr: true },
|
||||
status: 200,
|
||||
error: null,
|
||||
branch: [],
|
||||
fetched: [],
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
|
||||
if (route) {
|
||||
const method = /** @type {import('types').HttpMethod} */ (event.request.method);
|
||||
|
||||
/** @type {Response} */
|
||||
let response;
|
||||
|
||||
if (is_data_request) {
|
||||
response = await render_data(
|
||||
event,
|
||||
route,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
invalidated_data_nodes,
|
||||
trailing_slash ?? 'never'
|
||||
);
|
||||
} else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
|
||||
response = await render_endpoint(event, await route.endpoint(), state);
|
||||
} else if (route.page) {
|
||||
if (page_methods.has(method)) {
|
||||
response = await render_page(event, route.page, options, manifest, state, resolve_opts);
|
||||
} else {
|
||||
const allowed_methods = new Set(allowed_page_methods);
|
||||
const node = await manifest._.nodes[route.page.leaf]();
|
||||
if (node?.server?.actions) {
|
||||
allowed_methods.add('POST');
|
||||
}
|
||||
|
||||
if (method === 'OPTIONS') {
|
||||
// This will deny CORS preflight requests implicitly because we don't
|
||||
// add the required CORS headers to the response.
|
||||
response = new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
allow: Array.from(allowed_methods.values()).join(', ')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const mod = [...allowed_methods].reduce((acc, curr) => {
|
||||
acc[curr] = true;
|
||||
return acc;
|
||||
}, /** @type {Record<string, any>} */ ({}));
|
||||
response = method_not_allowed(mod, method);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// a route will always have a page or an endpoint, but TypeScript
|
||||
// doesn't know that
|
||||
throw new Error('This should never happen');
|
||||
}
|
||||
|
||||
// If the route contains a page and an endpoint, we need to add a
|
||||
// `Vary: Accept` header to the response because of browser caching
|
||||
if (request.method === 'GET' && route.page && route.endpoint) {
|
||||
const vary = response.headers
|
||||
.get('vary')
|
||||
?.split(',')
|
||||
?.map((v) => v.trim().toLowerCase());
|
||||
if (!(vary?.includes('accept') || vary?.includes('*'))) {
|
||||
// the returned response might have immutable headers,
|
||||
// so we have to clone them before trying to mutate them
|
||||
response = new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: new Headers(response.headers)
|
||||
});
|
||||
response.headers.append('Vary', 'Accept');
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
if (state.error && event.isSubRequest) {
|
||||
return await fetch(request, {
|
||||
headers: {
|
||||
'x-sveltekit-error': 'true'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state.error) {
|
||||
return text('Internal Server Error', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
// if this request came direct from the user, rather than
|
||||
// via our own `fetch`, render a 404 page
|
||||
if (state.depth === 0) {
|
||||
return await respond_with_error({
|
||||
event,
|
||||
options,
|
||||
manifest,
|
||||
state,
|
||||
status: 404,
|
||||
error: new Error(`Not found: ${event.url.pathname}`),
|
||||
resolve_opts
|
||||
});
|
||||
}
|
||||
|
||||
if (state.prerendering) {
|
||||
return text('not found', { status: 404 });
|
||||
}
|
||||
|
||||
// we can't load the endpoint from our own manifest,
|
||||
// so we need to make an actual HTTP request
|
||||
return await fetch(request);
|
||||
} catch (e) {
|
||||
// TODO if `e` is instead named `error`, some fucked up Vite transformation happens
|
||||
// and I don't even know how to describe it. need to investigate at some point
|
||||
|
||||
// HttpError from endpoint can end up here - TODO should it be handled there instead?
|
||||
return await handle_fatal_error(event, options, e);
|
||||
} finally {
|
||||
event.cookies.set = () => {
|
||||
throw new Error('Cannot use `cookies.set(...)` after the response has been generated');
|
||||
};
|
||||
|
||||
event.setHeaders = () => {
|
||||
throw new Error('Cannot use `setHeaders(...)` after the response has been generated');
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
161
node_modules/@sveltejs/kit/src/runtime/server/utils.js
generated
vendored
Normal file
161
node_modules/@sveltejs/kit/src/runtime/server/utils.js
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
import { DEV } from 'esm-env';
|
||||
import { json, text } from '../../exports/index.js';
|
||||
import { coalesce_to_error } from '../../utils/error.js';
|
||||
import { negotiate } from '../../utils/http.js';
|
||||
import { HttpError } from '../control.js';
|
||||
import { fix_stack_trace } from '../shared-server.js';
|
||||
import { ENDPOINT_METHODS } from '../../constants.js';
|
||||
|
||||
/** @param {any} body */
|
||||
export function is_pojo(body) {
|
||||
if (typeof body !== 'object') return false;
|
||||
|
||||
if (body) {
|
||||
if (body instanceof Uint8Array) return false;
|
||||
if (body instanceof ReadableStream) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Partial<Record<import('types').HttpMethod, any>>} mod
|
||||
* @param {import('types').HttpMethod} method
|
||||
*/
|
||||
export function method_not_allowed(mod, method) {
|
||||
return text(`${method} method not allowed`, {
|
||||
status: 405,
|
||||
headers: {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
||||
// "The server must generate an Allow header field in a 405 status code response"
|
||||
allow: allowed_methods(mod).join(', ')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
|
||||
export function allowed_methods(mod) {
|
||||
const allowed = Array.from(ENDPOINT_METHODS).filter((method) => method in mod);
|
||||
|
||||
if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD');
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as a response that renders the error.html
|
||||
*
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {number} status
|
||||
* @param {string} message
|
||||
*/
|
||||
export function static_error_page(options, status, message) {
|
||||
let page = options.templates.error({ status, message });
|
||||
|
||||
if (DEV) {
|
||||
// inject Vite HMR client, for easier debugging
|
||||
page = page.replace('</head>', '<script type="module" src="/@vite/client"></script></head>');
|
||||
}
|
||||
|
||||
return text(page, {
|
||||
headers: { 'content-type': 'text/html; charset=utf-8' },
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {unknown} error
|
||||
*/
|
||||
export async function handle_fatal_error(event, options, error) {
|
||||
error = error instanceof HttpError ? error : coalesce_to_error(error);
|
||||
const status = error instanceof HttpError ? error.status : 500;
|
||||
const body = await handle_error_and_jsonify(event, options, error);
|
||||
|
||||
// ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
|
||||
const type = negotiate(event.request.headers.get('accept') || 'text/html', [
|
||||
'application/json',
|
||||
'text/html'
|
||||
]);
|
||||
|
||||
if (event.isDataRequest || type === 'application/json') {
|
||||
return json(body, {
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
return static_error_page(options, status, body.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {import('types').SSROptions} options
|
||||
* @param {any} error
|
||||
* @returns {Promise<App.Error>}
|
||||
*/
|
||||
export async function handle_error_and_jsonify(event, options, error) {
|
||||
if (error instanceof HttpError) {
|
||||
return error.body;
|
||||
} else {
|
||||
if (__SVELTEKIT_DEV__ && typeof error == 'object') {
|
||||
fix_stack_trace(error);
|
||||
}
|
||||
|
||||
return (
|
||||
(await options.hooks.handleError({ error, event })) ?? {
|
||||
message: event.route.id != null ? 'Internal Error' : 'Not Found'
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} status
|
||||
* @param {string} location
|
||||
*/
|
||||
export function redirect_response(status, location) {
|
||||
const response = new Response(undefined, {
|
||||
status,
|
||||
headers: { location }
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@sveltejs/kit').RequestEvent} event
|
||||
* @param {Error & { path: string }} error
|
||||
*/
|
||||
export function clarify_devalue_error(event, error) {
|
||||
if (error.path) {
|
||||
return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error.message} (data${error.path})`;
|
||||
}
|
||||
|
||||
if (error.path === '') {
|
||||
return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`;
|
||||
}
|
||||
|
||||
// belt and braces — this should never happen
|
||||
return error.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('types').ServerDataNode} node
|
||||
*/
|
||||
export function stringify_uses(node) {
|
||||
const uses = [];
|
||||
|
||||
if (node.uses && node.uses.dependencies.size > 0) {
|
||||
uses.push(`"dependencies":${JSON.stringify(Array.from(node.uses.dependencies))}`);
|
||||
}
|
||||
|
||||
if (node.uses && node.uses.params.size > 0) {
|
||||
uses.push(`"params":${JSON.stringify(Array.from(node.uses.params))}`);
|
||||
}
|
||||
|
||||
if (node.uses?.parent) uses.push('"parent":1');
|
||||
if (node.uses?.route) uses.push('"route":1');
|
||||
if (node.uses?.url) uses.push('"url":1');
|
||||
|
||||
return `"uses":{${uses.join(',')}}`;
|
||||
}
|
23
node_modules/@sveltejs/kit/src/runtime/shared-server.js
generated
vendored
Normal file
23
node_modules/@sveltejs/kit/src/runtime/shared-server.js
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/** @type {Record<string, string>} */
|
||||
export let private_env = {};
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
export let public_env = {};
|
||||
|
||||
/** @param {any} error */
|
||||
export let fix_stack_trace = (error) => error?.stack;
|
||||
|
||||
/** @type {(environment: Record<string, string>) => void} */
|
||||
export function set_private_env(environment) {
|
||||
private_env = environment;
|
||||
}
|
||||
|
||||
/** @type {(environment: Record<string, string>) => void} */
|
||||
export function set_public_env(environment) {
|
||||
public_env = environment;
|
||||
}
|
||||
|
||||
/** @param {(error: Error) => string} value */
|
||||
export function set_fix_stack_trace(value) {
|
||||
fix_stack_trace = value;
|
||||
}
|
16
node_modules/@sveltejs/kit/src/runtime/shared.js
generated
vendored
Normal file
16
node_modules/@sveltejs/kit/src/runtime/shared.js
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @param {string} route_id
|
||||
* @param {string} dep
|
||||
*/
|
||||
export function validate_depends(route_id, dep) {
|
||||
const match = /^(moz-icon|view-source|jar):/.exec(dep);
|
||||
if (match) {
|
||||
console.warn(
|
||||
`${route_id}: Calling \`depends('${dep}')\` will throw an error in Firefox because \`${match[1]}\` is a special URI scheme`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
|
||||
|
||||
export const TRAILING_SLASH_PARAM = 'x-sveltekit-trailing-slash';
|
11
node_modules/@sveltejs/kit/src/types/ambient-private.d.ts
generated
vendored
Normal file
11
node_modules/@sveltejs/kit/src/types/ambient-private.d.ts
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
declare global {
|
||||
const __SVELTEKIT_ADAPTER_NAME__: string;
|
||||
const __SVELTEKIT_APP_VERSION_FILE__: string;
|
||||
const __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: number;
|
||||
const __SVELTEKIT_DEV__: boolean;
|
||||
const __SVELTEKIT_EMBEDDED__: boolean;
|
||||
var Bun: object;
|
||||
var Deno: object;
|
||||
}
|
||||
|
||||
export {};
|
108
node_modules/@sveltejs/kit/src/types/ambient.d.ts
generated
vendored
Normal file
108
node_modules/@sveltejs/kit/src/types/ambient.d.ts
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* It's possible to tell SvelteKit how to type objects inside your app by declaring the `App` namespace. By default, a new project will have a file called `src/app.d.ts` containing the following:
|
||||
*
|
||||
* ```ts
|
||||
* declare global {
|
||||
* namespace App {
|
||||
* // interface Error {}
|
||||
* // interface Locals {}
|
||||
* // interface PageData {}
|
||||
* // interface Platform {}
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export {};
|
||||
* ```
|
||||
*
|
||||
* The `export {}` line exists because without it, the file would be treated as an _ambient module_ which prevents you from adding `import` declarations.
|
||||
* If you need to add ambient `declare module` declarations, do so in a separate file like `src/ambient.d.ts`.
|
||||
*
|
||||
* By populating these interfaces, you will gain type safety when using `event.locals`, `event.platform`, and `data` from `load` functions.
|
||||
*/
|
||||
declare namespace App {
|
||||
/**
|
||||
* Defines the common shape of expected and unexpected errors. Expected errors are thrown using the `error` function. Unexpected errors are handled by the `handleError` hooks which should return this shape.
|
||||
*/
|
||||
export interface Error {
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface that defines `event.locals`, which can be accessed in [hooks](https://kit.svelte.dev/docs/hooks) (`handle`, and `handleError`), server-only `load` functions, and `+server.js` files.
|
||||
*/
|
||||
export interface Locals {}
|
||||
|
||||
/**
|
||||
* Defines the common shape of the [$page.data store](https://kit.svelte.dev/docs/modules#$app-stores-page) - that is, the data that is shared between all pages.
|
||||
* The `Load` and `ServerLoad` functions in `./$types` will be narrowed accordingly.
|
||||
* Use optional properties for data that is only present on specific pages. Do not add an index signature (`[key: string]: any`).
|
||||
*/
|
||||
export interface PageData {}
|
||||
|
||||
/**
|
||||
* If your adapter provides [platform-specific context](https://kit.svelte.dev/docs/adapters#platform-specific-context) via `event.platform`, you can specify it here.
|
||||
*/
|
||||
export interface Platform {}
|
||||
}
|
||||
|
||||
/**
|
||||
* This module is only available to [service workers](https://kit.svelte.dev/docs/service-workers).
|
||||
*/
|
||||
declare module '$service-worker' {
|
||||
/**
|
||||
* The `base` path of the deployment. Typically this is equivalent to `config.kit.paths.base`, but it is calculated from `location.pathname` meaning that it will continue to work correctly if the site is deployed to a subdirectory.
|
||||
* Note that there is a `base` but no `assets`, since service workers cannot be used if `config.kit.paths.assets` is specified.
|
||||
*/
|
||||
export const base: string;
|
||||
/**
|
||||
* An array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`.
|
||||
* During development, this is an empty array.
|
||||
*/
|
||||
export const build: string[];
|
||||
/**
|
||||
* An array of URL strings representing the files in your static directory, or whatever directory is specified by `config.kit.files.assets`. You can customize which files are included from `static` directory using [`config.kit.serviceWorker.files`](https://kit.svelte.dev/docs/configuration)
|
||||
*/
|
||||
export const files: string[];
|
||||
/**
|
||||
* An array of pathnames corresponding to prerendered pages and endpoints.
|
||||
* During development, this is an empty array.
|
||||
*/
|
||||
export const prerendered: string[];
|
||||
/**
|
||||
* See [`config.kit.version`](https://kit.svelte.dev/docs/configuration#version). It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches.
|
||||
*/
|
||||
export const version: string;
|
||||
}
|
||||
|
||||
/** Internal version of $app/environment */
|
||||
declare module '__sveltekit/environment' {
|
||||
/**
|
||||
* SvelteKit analyses your app during the `build` step by running it. During this process, `building` is `true`. This also applies during prerendering.
|
||||
*/
|
||||
export const building: boolean;
|
||||
/**
|
||||
* The value of `config.kit.version.name`.
|
||||
*/
|
||||
export const version: string;
|
||||
export function set_building(): void;
|
||||
}
|
||||
|
||||
/** Internal version of $app/paths */
|
||||
declare module '__sveltekit/paths' {
|
||||
/**
|
||||
* A string that matches [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths).
|
||||
*
|
||||
* Example usage: `<a href="{base}/your-page">Link</a>`
|
||||
*/
|
||||
export let base: '' | `/${string}`;
|
||||
/**
|
||||
* An absolute path that matches [`config.kit.paths.assets`](https://kit.svelte.dev/docs/configuration#paths).
|
||||
*
|
||||
* > If a value for `config.kit.paths.assets` is specified, it will be replaced with `'/_svelte_kit_assets'` during `vite dev` or `vite preview`, since the assets don't yet live at their eventual URL.
|
||||
*/
|
||||
export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets';
|
||||
export let relative: boolean | undefined; // TODO in 2.0, make this a `boolean` that defaults to `true`
|
||||
export function reset(): void;
|
||||
export function override(paths: { base: string; assets: string }): void;
|
||||
export function set_assets(path: string): void;
|
||||
}
|
418
node_modules/@sveltejs/kit/src/types/internal.d.ts
generated
vendored
Normal file
418
node_modules/@sveltejs/kit/src/types/internal.d.ts
generated
vendored
Normal file
@ -0,0 +1,418 @@
|
||||
import { SvelteComponent } from 'svelte';
|
||||
import {
|
||||
Config,
|
||||
ServerLoad,
|
||||
Handle,
|
||||
HandleServerError,
|
||||
KitConfig,
|
||||
Load,
|
||||
RequestHandler,
|
||||
ResolveOptions,
|
||||
Server,
|
||||
ServerInitOptions,
|
||||
HandleFetch,
|
||||
Actions,
|
||||
HandleClientError
|
||||
} from '@sveltejs/kit';
|
||||
import {
|
||||
HttpMethod,
|
||||
MaybePromise,
|
||||
PrerenderOption,
|
||||
RequestOptions,
|
||||
TrailingSlash
|
||||
} from './private.js';
|
||||
|
||||
export interface ServerModule {
|
||||
Server: typeof InternalServer;
|
||||
}
|
||||
|
||||
export interface ServerInternalModule {
|
||||
set_building(building: boolean): void;
|
||||
set_assets(path: string): void;
|
||||
set_private_env(environment: Record<string, string>): void;
|
||||
set_public_env(environment: Record<string, string>): void;
|
||||
set_version(version: string): void;
|
||||
set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void;
|
||||
}
|
||||
|
||||
export interface Asset {
|
||||
file: string;
|
||||
size: number;
|
||||
type: string | null;
|
||||
}
|
||||
|
||||
export interface AssetDependencies {
|
||||
file: string;
|
||||
imports: string[];
|
||||
stylesheets: string[];
|
||||
fonts: string[];
|
||||
}
|
||||
|
||||
export interface BuildData {
|
||||
app_dir: string;
|
||||
app_path: string;
|
||||
manifest_data: ManifestData;
|
||||
service_worker: string | null;
|
||||
client: {
|
||||
start: string;
|
||||
app: string;
|
||||
imports: string[];
|
||||
stylesheets: string[];
|
||||
fonts: string[];
|
||||
} | null;
|
||||
server_manifest: import('vite').Manifest;
|
||||
}
|
||||
|
||||
export interface CSRPageNode {
|
||||
component: typeof SvelteComponent;
|
||||
universal: {
|
||||
load?: Load;
|
||||
trailingSlash?: TrailingSlash;
|
||||
};
|
||||
}
|
||||
|
||||
export type CSRPageNodeLoader = () => Promise<CSRPageNode>;
|
||||
|
||||
/**
|
||||
* Definition of a client side route.
|
||||
* The boolean in the tuples indicates whether the route has a server load.
|
||||
*/
|
||||
export type CSRRoute = {
|
||||
id: string;
|
||||
exec(path: string): undefined | Record<string, string>;
|
||||
errors: Array<CSRPageNodeLoader | undefined>;
|
||||
layouts: Array<[has_server_load: boolean, node_loader: CSRPageNodeLoader] | undefined>;
|
||||
leaf: [has_server_load: boolean, node_loader: CSRPageNodeLoader];
|
||||
};
|
||||
|
||||
export interface Deferred {
|
||||
fulfil: (value: any) => void;
|
||||
reject: (error: Error) => void;
|
||||
}
|
||||
|
||||
export type GetParams = (match: RegExpExecArray) => Record<string, string>;
|
||||
|
||||
export interface ServerHooks {
|
||||
handleFetch: HandleFetch;
|
||||
handle: Handle;
|
||||
handleError: HandleServerError;
|
||||
}
|
||||
|
||||
export interface ClientHooks {
|
||||
handleError: HandleClientError;
|
||||
}
|
||||
|
||||
export interface Env {
|
||||
private: Record<string, string>;
|
||||
public: Record<string, string>;
|
||||
}
|
||||
|
||||
export class InternalServer extends Server {
|
||||
init(options: ServerInitOptions): Promise<void>;
|
||||
respond(
|
||||
request: Request,
|
||||
options: RequestOptions & {
|
||||
prerendering?: PrerenderOptions;
|
||||
read: (file: string) => Buffer;
|
||||
}
|
||||
): Promise<Response>;
|
||||
}
|
||||
|
||||
export interface ManifestData {
|
||||
assets: Asset[];
|
||||
nodes: PageNode[];
|
||||
routes: RouteData[];
|
||||
matchers: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface PageNode {
|
||||
depth: number;
|
||||
component?: string; // TODO supply default component if it's missing (bit of an edge case)
|
||||
universal?: string;
|
||||
server?: string;
|
||||
parent_id?: string;
|
||||
parent?: PageNode;
|
||||
/**
|
||||
* Filled with the pages that reference this layout (if this is a layout)
|
||||
*/
|
||||
child_pages?: PageNode[];
|
||||
}
|
||||
|
||||
export interface PrerenderDependency {
|
||||
response: Response;
|
||||
body: null | string | Uint8Array;
|
||||
}
|
||||
|
||||
export interface PrerenderOptions {
|
||||
cache?: string; // including this here is a bit of a hack, but it makes it easy to add <meta http-equiv>
|
||||
fallback?: boolean;
|
||||
dependencies: Map<string, PrerenderDependency>;
|
||||
}
|
||||
|
||||
export type RecursiveRequired<T> = {
|
||||
// Recursive implementation of TypeScript's Required utility type.
|
||||
// Will recursively continue until it reaches a primitive or Function
|
||||
[K in keyof T]-?: Extract<T[K], Function> extends never // If it does not have a Function type
|
||||
? RecursiveRequired<T[K]> // recursively continue through.
|
||||
: T[K]; // Use the exact type for everything else
|
||||
};
|
||||
|
||||
export type RequiredResolveOptions = Required<ResolveOptions>;
|
||||
|
||||
export interface RouteParam {
|
||||
name: string;
|
||||
matcher: string;
|
||||
optional: boolean;
|
||||
rest: boolean;
|
||||
chained: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a route segment in the app. It can either be an intermediate node
|
||||
* with only layout/error pages, or a leaf, at which point either `page` and `leaf`
|
||||
* or `endpoint` is set.
|
||||
*/
|
||||
export interface RouteData {
|
||||
id: string;
|
||||
parent: RouteData | null;
|
||||
|
||||
segment: string;
|
||||
pattern: RegExp;
|
||||
params: RouteParam[];
|
||||
|
||||
layout: PageNode | null;
|
||||
error: PageNode | null;
|
||||
leaf: PageNode | null;
|
||||
|
||||
page: {
|
||||
layouts: Array<number | undefined>;
|
||||
errors: Array<number | undefined>;
|
||||
leaf: number;
|
||||
} | null;
|
||||
|
||||
endpoint: {
|
||||
file: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export type ServerRedirectNode = {
|
||||
type: 'redirect';
|
||||
location: string;
|
||||
};
|
||||
|
||||
export type ServerNodesResponse = {
|
||||
type: 'data';
|
||||
/**
|
||||
* If `null`, then there was no load function <- TODO is this outdated now with the recent changes?
|
||||
*/
|
||||
nodes: Array<ServerDataNode | ServerDataSkippedNode | ServerErrorNode | null>;
|
||||
};
|
||||
|
||||
export type ServerDataResponse = ServerRedirectNode | ServerNodesResponse;
|
||||
|
||||
/**
|
||||
* Signals a successful response of the server `load` function.
|
||||
* The `uses` property tells the client when it's possible to reuse this data
|
||||
* in a subsequent request.
|
||||
*/
|
||||
export interface ServerDataNode {
|
||||
type: 'data';
|
||||
/**
|
||||
* The serialized version of this contains a serialized representation of any deferred promises,
|
||||
* which will be resolved later through chunk nodes.
|
||||
*/
|
||||
data: Record<string, any> | null;
|
||||
uses: Uses;
|
||||
slash?: TrailingSlash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved data/error of a deferred promise.
|
||||
*/
|
||||
export interface ServerDataChunkNode {
|
||||
type: 'chunk';
|
||||
id: number;
|
||||
data?: Record<string, any>;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the server `load` function was not run, and the
|
||||
* client should use what it has in memory
|
||||
*/
|
||||
export interface ServerDataSkippedNode {
|
||||
type: 'skip';
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the server `load` function failed
|
||||
*/
|
||||
export interface ServerErrorNode {
|
||||
type: 'error';
|
||||
error: App.Error;
|
||||
/**
|
||||
* Only set for HttpErrors
|
||||
*/
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export interface ServerMetadataRoute {
|
||||
config: any;
|
||||
api: {
|
||||
methods: Array<HttpMethod | '*'>;
|
||||
};
|
||||
page: {
|
||||
methods: Array<'GET' | 'POST'>;
|
||||
};
|
||||
methods: Array<HttpMethod | '*'>;
|
||||
prerender: PrerenderOption | undefined;
|
||||
entries: string[] | undefined;
|
||||
}
|
||||
|
||||
export interface ServerMetadata {
|
||||
nodes: Array<{ has_server_load: boolean }>;
|
||||
routes: Map<string, ServerMetadataRoute>;
|
||||
}
|
||||
|
||||
export interface SSRComponent {
|
||||
default: {
|
||||
render(props: Record<string, any>): {
|
||||
html: string;
|
||||
head: string;
|
||||
css: {
|
||||
code: string;
|
||||
map: any; // TODO
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type SSRComponentLoader = () => Promise<SSRComponent>;
|
||||
|
||||
export interface SSRNode {
|
||||
component: SSRComponentLoader;
|
||||
/** index into the `components` array in client/manifest.js */
|
||||
index: number;
|
||||
/** external JS files */
|
||||
imports: string[];
|
||||
/** external CSS files */
|
||||
stylesheets: string[];
|
||||
/** external font files */
|
||||
fonts: string[];
|
||||
/** inlined styles */
|
||||
inline_styles?(): MaybePromise<Record<string, string>>;
|
||||
|
||||
universal: {
|
||||
load?: Load;
|
||||
prerender?: PrerenderOption;
|
||||
ssr?: boolean;
|
||||
csr?: boolean;
|
||||
trailingSlash?: TrailingSlash;
|
||||
config?: any;
|
||||
entries?: PrerenderEntryGenerator;
|
||||
};
|
||||
|
||||
server: {
|
||||
load?: ServerLoad;
|
||||
prerender?: PrerenderOption;
|
||||
ssr?: boolean;
|
||||
csr?: boolean;
|
||||
trailingSlash?: TrailingSlash;
|
||||
actions?: Actions;
|
||||
config?: any;
|
||||
entries?: PrerenderEntryGenerator;
|
||||
};
|
||||
|
||||
universal_id: string;
|
||||
server_id: string;
|
||||
}
|
||||
|
||||
export type SSRNodeLoader = () => Promise<SSRNode>;
|
||||
|
||||
export interface SSROptions {
|
||||
app_template_contains_nonce: boolean;
|
||||
csp: ValidatedConfig['kit']['csp'];
|
||||
csrf_check_origin: boolean;
|
||||
track_server_fetches: boolean;
|
||||
embedded: boolean;
|
||||
env_public_prefix: string;
|
||||
env_private_prefix: string;
|
||||
hooks: ServerHooks;
|
||||
preload_strategy: ValidatedConfig['kit']['output']['preloadStrategy'];
|
||||
root: SSRComponent['default'];
|
||||
service_worker: boolean;
|
||||
templates: {
|
||||
app(values: {
|
||||
head: string;
|
||||
body: string;
|
||||
assets: string;
|
||||
nonce: string;
|
||||
env: Record<string, string>;
|
||||
}): string;
|
||||
error(values: { message: string; status: number }): string;
|
||||
};
|
||||
version_hash: string;
|
||||
}
|
||||
|
||||
export interface PageNodeIndexes {
|
||||
errors: Array<number | undefined>;
|
||||
layouts: Array<number | undefined>;
|
||||
leaf: number;
|
||||
}
|
||||
|
||||
export type PrerenderEntryGenerator = () => MaybePromise<Array<Record<string, string>>>;
|
||||
|
||||
export type SSREndpoint = Partial<Record<HttpMethod, RequestHandler>> & {
|
||||
prerender?: PrerenderOption;
|
||||
trailingSlash?: TrailingSlash;
|
||||
config?: any;
|
||||
entries?: PrerenderEntryGenerator;
|
||||
fallback?: RequestHandler;
|
||||
};
|
||||
|
||||
export interface SSRRoute {
|
||||
id: string;
|
||||
pattern: RegExp;
|
||||
params: RouteParam[];
|
||||
page: PageNodeIndexes | null;
|
||||
endpoint: (() => Promise<SSREndpoint>) | null;
|
||||
endpoint_id?: string;
|
||||
}
|
||||
|
||||
export interface SSRState {
|
||||
fallback?: string;
|
||||
getClientAddress(): string;
|
||||
/**
|
||||
* True if we're currently attempting to render an error page
|
||||
*/
|
||||
error: boolean;
|
||||
/**
|
||||
* Allows us to prevent `event.fetch` from making infinitely looping internal requests
|
||||
*/
|
||||
depth: number;
|
||||
platform?: any;
|
||||
prerendering?: PrerenderOptions;
|
||||
/**
|
||||
* When fetching data from a +server.js endpoint in `load`, the page's
|
||||
* prerender option is inherited by the endpoint, unless overridden
|
||||
*/
|
||||
prerender_default?: PrerenderOption;
|
||||
read?: (file: string) => Buffer;
|
||||
}
|
||||
|
||||
export type StrictBody = string | ArrayBufferView;
|
||||
|
||||
export interface Uses {
|
||||
dependencies: Set<string>;
|
||||
params: Set<string>;
|
||||
parent: boolean;
|
||||
route: boolean;
|
||||
url: boolean;
|
||||
}
|
||||
|
||||
export type ValidatedConfig = RecursiveRequired<Config>;
|
||||
|
||||
export type ValidatedKitConfig = RecursiveRequired<KitConfig>;
|
||||
|
||||
export * from '../exports/index.js';
|
||||
export * from './private.js';
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user