├── demo ├── .npmrc ├── func │ ├── HelloWorld │ │ ├── sample.dat │ │ └── index.js │ ├── package.json │ └── host.json ├── static │ ├── robots.txt │ └── favicon.png ├── src │ ├── lib │ │ └── images │ │ │ ├── svelte-welcome.png │ │ │ ├── svelte-welcome.webp │ │ │ ├── github.svg │ │ │ └── svelte-logo.svg │ ├── routes │ │ ├── +page.js │ │ ├── about │ │ │ ├── +page.js │ │ │ └── +page.svelte │ │ ├── sverdle │ │ │ ├── how-to-play │ │ │ │ ├── +page.js │ │ │ │ └── +page.svelte │ │ │ ├── game.js │ │ │ ├── +page.server.js │ │ │ └── +page.svelte │ │ ├── methods │ │ │ └── +server.js │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ ├── Counter.svelte │ │ └── Header.svelte │ ├── app.d.ts │ ├── app.html │ └── app.css ├── swa-cli.config.json ├── vite.config.js ├── svelte.config.js ├── playwright.config.js ├── .gitignore ├── jsconfig.json ├── package.json ├── tests │ └── app.spec.js ├── README.md └── package-lock.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── files ├── api │ ├── local.settings.json │ ├── package.json │ ├── host.json │ └── .funcignore ├── entry.d.ts ├── headers.js └── entry.js ├── vite.config.js ├── tsconfig.json ├── .github └── workflows │ ├── test.yml │ ├── release.yml │ └── azure-static-web-apps-polite-desert-00b80111e.yml ├── CONTRIBUTING.md ├── LICENSE ├── package.json ├── index.d.ts ├── types └── swa.d.ts ├── test ├── json.js ├── headers.test.js └── index.test.js ├── index.js ├── CHANGELOG.md └── README.md /demo/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | *.tgz 4 | .vscode/ -------------------------------------------------------------------------------- /demo/func/HelloWorld/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | coverage/ 3 | build/ 4 | .svelte-kit/ 5 | sk_render/ -------------------------------------------------------------------------------- /demo/static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /demo/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffrich/svelte-adapter-azure-swa/HEAD/demo/static/favicon.png -------------------------------------------------------------------------------- /demo/func/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "**/index.js", 3 | "dependencies": { 4 | "@azure/functions": "^4" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[json]": { 4 | "editor.formatOnSave": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /files/api/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "FUNCTIONS_WORKER_RUNTIME": "node" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /files/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "sk_render/index.js", 3 | "dependencies": { 4 | "@azure/functions": "^4" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /demo/src/lib/images/svelte-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffrich/svelte-adapter-azure-swa/HEAD/demo/src/lib/images/svelte-welcome.png -------------------------------------------------------------------------------- /demo/src/lib/images/svelte-welcome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffrich/svelte-adapter-azure-swa/HEAD/demo/src/lib/images/svelte-welcome.webp -------------------------------------------------------------------------------- /demo/swa-cli.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "app": { 4 | "outputLocation": "./build/static", 5 | "apiLocation": "./build/server" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/func/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "extensionBundle": { 4 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 5 | "version": "[4.0.0, 5.0.0)" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /files/api/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "extensionBundle": { 4 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 5 | "version": "[4.0.0, 5.0.0)" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/src/routes/+page.js: -------------------------------------------------------------------------------- 1 | // since there's no dynamic data here, we can prerender 2 | // it so that it gets served as a static asset in production 3 | export const prerender = true; 4 | -------------------------------------------------------------------------------- /demo/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /files/api/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | __azurite_db*__.json 6 | __blobstorage__ 7 | __queuestorage__ 8 | local.settings.json 9 | test 10 | tsconfig.json -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { configDefaults, defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | exclude: [...configDefaults.exclude, 'demo/**'] 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /files/entry.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'SERVER' { 2 | export { Server } from '@sveltejs/kit'; 3 | } 4 | 5 | declare module 'MANIFEST' { 6 | import { SSRManifest } from '@sveltejs/kit'; 7 | export const manifest: SSRManifest; 8 | } 9 | -------------------------------------------------------------------------------- /demo/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from 'svelte-adapter-azure-swa'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | adapter: adapter({ 7 | apiDir: './func' 8 | }) 9 | } 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /demo/playwright.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@playwright/test').PlaywrightTestConfig} */ 2 | const config = { 3 | webServer: process.env.CI 4 | ? undefined 5 | : { 6 | command: 'npm run build && npm run preview', 7 | port: 4173 8 | } 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /demo/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "noEmit": true, 6 | "noImplicitAny": true, 7 | "target": "es2022", 8 | "module": "es2022", 9 | "moduleResolution": "node", 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2023"] 12 | }, 13 | "include": ["./index.js", "files"] 14 | } 15 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | test-results 2 | node_modules 3 | 4 | # Output 5 | .output 6 | .vercel 7 | .netlify 8 | .wrangler 9 | /.svelte-kit 10 | /build 11 | 12 | # OS 13 | .DS_Store 14 | Thumbs.db 15 | 16 | # Env 17 | .env 18 | .env.* 19 | !.env.example 20 | !.env.test 21 | 22 | # Vite 23 | vite.config.js.timestamp-* 24 | vite.config.ts.timestamp-* 25 | 26 | sk_render/ -------------------------------------------------------------------------------- /demo/src/routes/about/+page.js: -------------------------------------------------------------------------------- 1 | import { dev } from '$app/environment'; 2 | 3 | // we don't need any JS on this page, though we'll load 4 | // it in dev so that we get hot module replacement 5 | export const csr = dev; 6 | 7 | // since there's no dynamic data here, we can prerender 8 | // it so that it gets served as a static asset in production 9 | export const prerender = true; 10 | -------------------------------------------------------------------------------- /demo/src/routes/sverdle/how-to-play/+page.js: -------------------------------------------------------------------------------- 1 | import { dev } from '$app/environment'; 2 | 3 | // we don't need any JS on this page, though we'll load 4 | // it in dev so that we get hot module replacement 5 | export const csr = dev; 6 | 7 | // since there's no dynamic data here, we can prerender 8 | // it so that it gets served as a static asset in production 9 | export const prerender = true; 10 | -------------------------------------------------------------------------------- /demo/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/src/routes/methods/+server.js: -------------------------------------------------------------------------------- 1 | export const GET = () => { 2 | return new Response('getted'); 3 | }; 4 | 5 | export const POST = () => { 6 | return new Response('posted'); 7 | }; 8 | 9 | export const PUT = () => { 10 | return new Response('putted'); 11 | }; 12 | 13 | export const DELETE = () => { 14 | return new Response('deleted'); 15 | }; 16 | 17 | export const PATCH = () => { 18 | return new Response('patched'); 19 | }; 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Setup Node 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '20.x' 19 | - run: npm ci 20 | - run: npm run check-types 21 | - run: npm run check-format 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /demo/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /demo/func/HelloWorld/index.js: -------------------------------------------------------------------------------- 1 | const { app } = require('@azure/functions'); 2 | 3 | app.http('HelloWorld', { 4 | methods: ['GET', 'POST'], 5 | handler: async (req, context) => { 6 | context.log('JavaScript HTTP trigger function processed a request.'); 7 | 8 | let name; 9 | if (req.query.has('name')) { 10 | name = req.query.get('name'); 11 | } else if (req.headers.get('content-type') === 'application/json') { 12 | let body = await req.json(); 13 | name = body.name; 14 | } 15 | 16 | const responseMessage = name 17 | ? 'Hello, ' + name + '. This HTTP triggered function executed successfully.' 18 | : 'This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.'; 19 | 20 | return { body: responseMessage }; 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /demo/src/routes/about/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | About 3 | 4 | 5 | 6 |
7 |

About this app

8 | 9 |

10 | This is a SvelteKit app. You can make your own by typing 11 | the following into your command line and following the prompts: 12 |

13 | 14 |
npx sv create
15 | 16 |

17 | The page you're looking at is purely static HTML, with no client-side interactivity needed. 18 | Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening 19 | the devtools network panel and reloading. 20 |

21 | 22 |

23 | The Sverdle page illustrates SvelteKit's data loading and form handling. Try 24 | using it with JavaScript disabled! 25 |

26 |
27 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit-azure-swa-demo", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "scripts": { 6 | "prepare": "(cd .. && npm i) && (svelte-kit sync || echo '')", 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", 12 | "test": "playwright test" 13 | }, 14 | "devDependencies": { 15 | "@fontsource/fira-mono": "^5.0.0", 16 | "@neoconfetti/svelte": "^2.0.0", 17 | "@playwright/test": "^1.49.1", 18 | "@sveltejs/adapter-auto": "^4.0.0", 19 | "@sveltejs/kit": "^2.49.0", 20 | "@sveltejs/vite-plugin-svelte": "^6.2.1", 21 | "svelte": "^5.45.1", 22 | "svelte-adapter-azure-swa": "file:..", 23 | "svelte-check": "^4.0.0", 24 | "typescript": "^5.0.0", 25 | "vite": "^7.2.4" 26 | }, 27 | "engines": { 28 | "node": ">=20.19" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## How to contribute 4 | 5 | - Open a [GitHub Issue](https://github.com/geoffrich/svelte-adapter-azure-swa/issues) for a discussion of your idea before working on it 6 | - Fork this repo, develop your solution and submit a PR 7 | 8 | ## What to contribute 9 | 10 | See the [GitHub Issues](https://github.com/geoffrich/svelte-adapter-azure-swa/issues) list for any open Issue. 11 | 12 | General improvements to any aspect of this adapter are welcome, just ensure major work is preceded by a conversation in a [GitHub Issue](https://github.com/geoffrich/svelte-adapter-azure-swa/issues). 13 | 14 | ## Package scripts 15 | 16 | - `npm run format`: format the code using Prettier 17 | - `npm run format`: check that the code is correctly formatted 18 | - `npm run test`: run the unit tests using [Vitest](https://vitest.dev/) 19 | - `npm run test:coverage`: check test coverage 20 | 21 | _This was based on the contribution guidelines in [svelte-adapter-firebase](https://github.com/jthegedus/svelte-adapter-firebase/blob/main/CONTRIBUTING.md)_ 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 [these people](https://github.com/geoffrich/svelte-adapter-azure-swa/graphs/contributors) 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /demo/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 12 |
13 | {@render children()} 14 |
15 | 16 | 21 |
22 | 23 | 59 | -------------------------------------------------------------------------------- /demo/tests/app.spec.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('about page has expected h1', async ({ page }) => { 4 | await page.goto('/about'); 5 | expect(await page.textContent('h1')).toBe('About this app'); 6 | }); 7 | 8 | test('submits sverdle guess', async ({ page }) => { 9 | await page.goto('/sverdle'); 10 | const input = page.locator('input[name=guess]').first(); 11 | await expect(input).not.toBeDisabled(); 12 | await input.focus(); 13 | 14 | await page.keyboard.type('AZURE'); 15 | await page.keyboard.press('Enter'); 16 | 17 | await expect(input).toHaveValue('a'); 18 | await expect(input).toBeDisabled(); 19 | }); 20 | 21 | test('can call custom API azure function', async ({ request }) => { 22 | const response = await request.post('/api/HelloWorld', { 23 | data: { 24 | name: 'Geoff' 25 | } 26 | }); 27 | expect(response.ok()).toBeTruthy(); 28 | }); 29 | 30 | for (const verb of ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) { 31 | test(`can call ${verb} method on server endpoint`, async ({ request }) => { 32 | const response = await request.fetch(`/methods/`, { 33 | method: verb 34 | }); 35 | expect(response.ok()).toBeTruthy(); 36 | expect(await response.text()).toContain(verb.toLowerCase()); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /demo/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | Home 9 | 10 | 11 | 12 |
13 |

14 | 15 | 16 | 17 | Welcome 18 | 19 | 20 | 21 | to your new
SvelteKit app 22 |

23 | 24 |

25 | try editing src/routes/+page.svelte 26 |

27 | 28 | 29 |
30 | 31 | 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: GoogleCloudPlatform/release-please-action@v2 14 | id: release 15 | with: 16 | release-type: node 17 | bump-minor-pre-major: true # remove this to enable breaking changes causing 1.0.0 tag 18 | 19 | # The logic below handles the npm publication: 20 | # The if statements ensure that a publication only occurs when a new release is created 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | with: 24 | fetch-depth: 0 25 | persist-credentials: false 26 | if: ${{ steps.release.outputs.release_created }} 27 | 28 | - uses: actions/setup-node@v1 29 | with: 30 | node-version: 20 31 | registry-url: 'https://registry.npmjs.org' 32 | if: ${{ steps.release.outputs.release_created }} 33 | 34 | - run: npm install 35 | if: ${{ steps.release.outputs.release_created }} 36 | 37 | - run: npm publish 38 | env: 39 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 40 | if: ${{ steps.release.outputs.release_created }} 41 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # SvelteKit Azure SWA demo 2 | 3 | This is a repo demonstrating how to use [svelte-adapter-azure-swa](https://www.npmjs.com/package/svelte-adapter-azure-swa) with [SvelteKit](https://kit.svelte.dev/). 4 | 5 | This demo uses the local version of the adapter to make testing unreleased changes easier. In your app, you should install `svelte-adapter-azure-swa` from npm. 6 | 7 | This demo also uses a custom Azure function to make testing that integration easier. If you do not need a custom Azure function, you do not need the `func/` folder or need to set the `apiDir` option in `svelte.config.js`. 8 | 9 | [Deployed demo](https://polite-desert-00b80111e.2.azurestaticapps.net/) 10 | 11 | ## Developing 12 | 13 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 14 | 15 | ```bash 16 | npm run dev 17 | 18 | # or start the server and open the app in a new browser tab 19 | npm run dev -- --open 20 | ``` 21 | 22 | ## Building 23 | 24 | To create a production version of your app: 25 | 26 | ```bash 27 | npm run build 28 | ``` 29 | 30 | You can preview the production build with `npm run preview`. 31 | 32 | > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-adapter-azure-swa", 3 | "description": "SvelteKit adapter for Azure Static Web Apps.", 4 | "version": "0.22.1", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "format": "npm run check-format -- --write", 9 | "check-format": "prettier --check .", 10 | "check-types": "tsc --skipLibCheck", 11 | "test": "vitest", 12 | "test:coverage": "vitest run --coverage" 13 | }, 14 | "exports": { 15 | ".": { 16 | "types": "./index.d.ts", 17 | "import": "./index.js" 18 | }, 19 | "./package.json": "./package.json" 20 | }, 21 | "license": "MIT", 22 | "keywords": [ 23 | "svelte", 24 | "sveltekit", 25 | "sveltekit-adapter", 26 | "azure", 27 | "staticwebapp" 28 | ], 29 | "homepage": "https://github.com/geoffrich/svelte-adapter-azure-swa#readme", 30 | "bugs": { 31 | "url": "https://github.com/geoffrich/svelte-adapter-azure-swa/issues" 32 | }, 33 | "repository": "github:geoffrich/svelte-adapter-azure-swa", 34 | "peerDependencies": { 35 | "@sveltejs/kit": "^2.0.0" 36 | }, 37 | "devDependencies": { 38 | "@azure/functions": "^4", 39 | "@sveltejs/kit": "^2.17.3", 40 | "@types/node": "^22.0.0", 41 | "@types/set-cookie-parser": "^2.4.7", 42 | "prettier": "^3.1.1", 43 | "typescript": "^5.0.0", 44 | "vitest": "^3.0.0" 45 | }, 46 | "dependencies": { 47 | "esbuild": "^0.19.9", 48 | "set-cookie-parser": "^2.6.0" 49 | }, 50 | "files": [ 51 | "files", 52 | "index.js", 53 | "index.d.ts", 54 | "types" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Adapter } from '@sveltejs/kit'; 2 | import { ClientPrincipal, CustomStaticWebAppConfig } from './types/swa'; 3 | import { HttpRequestUser, InvocationContext } from '@azure/functions'; 4 | import esbuild from 'esbuild'; 5 | 6 | export * from './types/swa'; 7 | 8 | export type Options = { 9 | debug?: boolean; 10 | customStaticWebAppConfig?: CustomStaticWebAppConfig; 11 | esbuildOptions?: Pick; 12 | apiDir?: string; 13 | staticDir?: string; 14 | allowReservedSwaRoutes?: boolean; 15 | }; 16 | 17 | export default function plugin(options?: Options): Adapter; 18 | 19 | declare global { 20 | namespace App { 21 | export interface Platform { 22 | /** 23 | * Client Principal as passed from Azure 24 | * 25 | * @remarks 26 | * 27 | * Due to a possible in bug in SWA, the client principal is only passed 28 | * to the render function on routes specifically designated as 29 | * protected. Protected in this case means that the `allowedRoles` 30 | * field is populated and does not contain the `anonymous` role. 31 | * 32 | * @see The {@link https://learn.microsoft.com/en-us/azure/static-web-apps/user-information?tabs=javascript#api-functions SWA documentation} 33 | */ 34 | 35 | /** 36 | * The Azure function request context. 37 | * 38 | * @see The {@link https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node#context-object Azure function documentation} 39 | */ 40 | context: InvocationContext; 41 | 42 | user: HttpRequestUser | null; 43 | 44 | clientPrincipal?: ClientPrincipal; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /demo/src/lib/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 16 | -------------------------------------------------------------------------------- /demo/src/lib/images/svelte-logo.svg: -------------------------------------------------------------------------------- 1 | svelte-logo -------------------------------------------------------------------------------- /demo/src/routes/sverdle/game.js: -------------------------------------------------------------------------------- 1 | import { words, allowed } from './words.server'; 2 | 3 | export class Game { 4 | /** 5 | * Create a game object from the player's cookie, or initialise a new game 6 | * @param {string | undefined} serialized 7 | */ 8 | constructor(serialized = undefined) { 9 | if (serialized) { 10 | const [index, guesses, answers] = serialized.split('-'); 11 | 12 | this.index = +index; 13 | this.guesses = guesses ? guesses.split(' ') : []; 14 | this.answers = answers ? answers.split(' ') : []; 15 | } else { 16 | this.index = Math.floor(Math.random() * words.length); 17 | this.guesses = ['', '', '', '', '', '']; 18 | this.answers = /** @type {string[]} */ ([]); 19 | } 20 | 21 | this.answer = words[this.index]; 22 | } 23 | 24 | /** 25 | * Update game state based on a guess of a five-letter word. Returns 26 | * true if the guess was valid, false otherwise 27 | * @param {string[]} letters 28 | */ 29 | enter(letters) { 30 | const word = letters.join(''); 31 | const valid = allowed.has(word); 32 | 33 | if (!valid) return false; 34 | 35 | this.guesses[this.answers.length] = word; 36 | 37 | const available = Array.from(this.answer); 38 | const answer = Array(5).fill('_'); 39 | 40 | // first, find exact matches 41 | for (let i = 0; i < 5; i += 1) { 42 | if (letters[i] === available[i]) { 43 | answer[i] = 'x'; 44 | available[i] = ' '; 45 | } 46 | } 47 | 48 | // then find close matches (this has to happen 49 | // in a second step, otherwise an early close 50 | // match can prevent a later exact match) 51 | for (let i = 0; i < 5; i += 1) { 52 | if (answer[i] === '_') { 53 | const index = available.indexOf(letters[i]); 54 | if (index !== -1) { 55 | answer[i] = 'c'; 56 | available[index] = ' '; 57 | } 58 | } 59 | } 60 | 61 | this.answers.push(answer.join('')); 62 | 63 | return true; 64 | } 65 | 66 | /** 67 | * Serialize game state so it can be set as a cookie 68 | */ 69 | toString() { 70 | return `${this.index}-${this.guesses.join(' ')}-${this.answers.join(' ')}`; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /demo/src/routes/sverdle/+page.server.js: -------------------------------------------------------------------------------- 1 | import { fail } from '@sveltejs/kit'; 2 | import { Game } from './game'; 3 | 4 | /** @satisfies {import('./$types').PageServerLoad} */ 5 | export const load = ({ cookies }) => { 6 | const game = new Game(cookies.get('sverdle')); 7 | 8 | return { 9 | /** 10 | * The player's guessed words so far 11 | */ 12 | guesses: game.guesses, 13 | 14 | /** 15 | * An array of strings like '__x_c' corresponding to the guesses, where 'x' means 16 | * an exact match, and 'c' means a close match (right letter, wrong place) 17 | */ 18 | answers: game.answers, 19 | 20 | /** 21 | * The correct answer, revealed if the game is over 22 | */ 23 | answer: game.answers.length >= 6 ? game.answer : null 24 | }; 25 | }; 26 | 27 | /** @satisfies {import('./$types').Actions} */ 28 | export const actions = { 29 | /** 30 | * Modify game state in reaction to a keypress. If client-side JavaScript 31 | * is available, this will happen in the browser instead of here 32 | */ 33 | update: async ({ request, cookies }) => { 34 | const game = new Game(cookies.get('sverdle')); 35 | 36 | const data = await request.formData(); 37 | const key = data.get('key'); 38 | 39 | const i = game.answers.length; 40 | 41 | if (key === 'backspace') { 42 | game.guesses[i] = game.guesses[i].slice(0, -1); 43 | } else { 44 | game.guesses[i] += key; 45 | } 46 | 47 | cookies.set('sverdle', game.toString(), { path: '/' }); 48 | }, 49 | 50 | /** 51 | * Modify game state in reaction to a guessed word. This logic always runs on 52 | * the server, so that people can't cheat by peeking at the JavaScript 53 | */ 54 | enter: async ({ request, cookies }) => { 55 | const game = new Game(cookies.get('sverdle')); 56 | 57 | const data = await request.formData(); 58 | const guess = /** @type {string[]} */ (data.getAll('guess')); 59 | 60 | if (!game.enter(guess)) { 61 | return fail(400, { badGuess: true }); 62 | } 63 | 64 | cookies.set('sverdle', game.toString(), { path: '/' }); 65 | }, 66 | 67 | restart: async ({ cookies }) => { 68 | cookies.delete('sverdle', { path: '/' }); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /files/headers.js: -------------------------------------------------------------------------------- 1 | import * as set_cookie_parser from 'set-cookie-parser'; 2 | 3 | /** 4 | * @typedef {import('@azure/functions').Cookie} Cookie 5 | */ 6 | 7 | /** 8 | * Splits 'set-cookie' headers into individual cookies 9 | * @param {Headers} headers 10 | * @returns {{ 11 | * headers: Headers, 12 | * cookies: Cookie[] 13 | * }} 14 | */ 15 | export function splitCookiesFromHeaders(headers) { 16 | /** @type {Record} */ 17 | const resHeaders = {}; 18 | 19 | /** @type {Cookie[]} */ 20 | const resCookies = []; 21 | 22 | headers.forEach((value, key) => { 23 | if (key === 'set-cookie') { 24 | const cookieStrings = set_cookie_parser.splitCookiesString(value); 25 | // @ts-expect-error - one cookie type has a stricter sameSite type 26 | resCookies.push(...set_cookie_parser.parse(cookieStrings)); 27 | } else { 28 | resHeaders[key] = value; 29 | } 30 | }); 31 | 32 | return { headers: new Headers(resHeaders), cookies: resCookies }; 33 | } 34 | 35 | /** 36 | * Gets client IP from 'x-forwarded-for' header, ignoring socket and intermediate proxies. 37 | * @param {Headers} headers 38 | * @returns {string} Client IP 39 | */ 40 | export function getClientIPFromHeaders(headers) { 41 | /** @type {string} */ 42 | const resHeader = headers.get('x-forwarded-for') ?? '127.0.0.1'; 43 | const [origin] = resHeader.split(', '); 44 | const [ipAddress] = origin.split(':'); 45 | 46 | return ipAddress; 47 | } 48 | 49 | /** 50 | * Gets the client principal from `x-ms-client-principal` header. 51 | * @param {Headers} headers 52 | * @returns {import('../types/swa').ClientPrincipal | undefined} The client principal 53 | */ 54 | export function getClientPrincipalFromHeaders(headers) { 55 | // Code adapted from the official SWA documentation 56 | // https://learn.microsoft.com/en-us/azure/static-web-apps/user-information?tabs=javascript#api-functions 57 | const header = headers.get('x-ms-client-principal'); 58 | if (!header) { 59 | return undefined; 60 | } 61 | 62 | try { 63 | const encoded = Buffer.from(header, 'base64'); 64 | const decoded = encoded.toString('ascii'); 65 | const clientPrincipal = JSON.parse(decoded); 66 | 67 | return clientPrincipal; 68 | } catch (e) { 69 | console.log('Unable to parse client principal:', e); 70 | return undefined; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /demo/src/app.css: -------------------------------------------------------------------------------- 1 | @import '@fontsource/fira-mono'; 2 | 3 | :root { 4 | --font-body: 5 | Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 6 | 'Open Sans', 'Helvetica Neue', sans-serif; 7 | --font-mono: 'Fira Mono', monospace; 8 | --color-bg-0: rgb(202, 216, 228); 9 | --color-bg-1: hsl(209, 36%, 86%); 10 | --color-bg-2: hsl(224, 44%, 95%); 11 | --color-theme-1: #ff3e00; 12 | --color-theme-2: #4075a6; 13 | --color-text: rgba(0, 0, 0, 0.7); 14 | --column-width: 42rem; 15 | --column-margin-top: 4rem; 16 | font-family: var(--font-body); 17 | color: var(--color-text); 18 | } 19 | 20 | body { 21 | min-height: 100vh; 22 | margin: 0; 23 | background-attachment: fixed; 24 | background-color: var(--color-bg-1); 25 | background-size: 100vw 100vh; 26 | background-image: 27 | radial-gradient(50% 50% at 50% 50%, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0) 100%), 28 | linear-gradient(180deg, var(--color-bg-0) 0%, var(--color-bg-1) 15%, var(--color-bg-2) 50%); 29 | } 30 | 31 | h1, 32 | h2, 33 | p { 34 | font-weight: 400; 35 | } 36 | 37 | p { 38 | line-height: 1.5; 39 | } 40 | 41 | a { 42 | color: var(--color-theme-1); 43 | text-decoration: none; 44 | } 45 | 46 | a:hover { 47 | text-decoration: underline; 48 | } 49 | 50 | h1 { 51 | font-size: 2rem; 52 | text-align: center; 53 | } 54 | 55 | h2 { 56 | font-size: 1rem; 57 | } 58 | 59 | pre { 60 | font-size: 16px; 61 | font-family: var(--font-mono); 62 | background-color: rgba(255, 255, 255, 0.45); 63 | border-radius: 3px; 64 | box-shadow: 2px 2px 6px rgb(255 255 255 / 25%); 65 | padding: 0.5em; 66 | overflow-x: auto; 67 | color: var(--color-text); 68 | } 69 | 70 | .text-column { 71 | display: flex; 72 | max-width: 48rem; 73 | flex: 0.6; 74 | flex-direction: column; 75 | justify-content: center; 76 | margin: 0 auto; 77 | } 78 | 79 | input, 80 | button { 81 | font-size: inherit; 82 | font-family: inherit; 83 | } 84 | 85 | button:focus:not(:focus-visible) { 86 | outline: none; 87 | } 88 | 89 | @media (min-width: 720px) { 90 | h1 { 91 | font-size: 2.4rem; 92 | } 93 | } 94 | 95 | .visually-hidden { 96 | border: 0; 97 | clip: rect(0 0 0 0); 98 | height: auto; 99 | margin: 0; 100 | overflow: hidden; 101 | padding: 0; 102 | position: absolute; 103 | width: 1px; 104 | white-space: nowrap; 105 | } 106 | -------------------------------------------------------------------------------- /demo/src/routes/Counter.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | 23 | 24 |
25 |
26 | 27 | {Math.floor(count.current)} 28 |
29 |
30 | 31 | 36 |
37 | 38 | 104 | -------------------------------------------------------------------------------- /demo/src/routes/sverdle/how-to-play/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | How to play Sverdle 3 | 4 | 5 | 6 |
7 |

How to play Sverdle

8 | 9 |

10 | Sverdle is a clone of Wordle, the 11 | word guessing game. To play, enter a five-letter English word. For example: 12 |

13 | 14 |
15 | r 16 | i 17 | t 18 | z 19 | y 20 |
21 | 22 |

23 | The y is in the right place. r and 24 | t 25 | are the right letters, but in the wrong place. The other letters are wrong, and can be discarded. 26 | Let's make another guess: 27 |

28 | 29 |
30 | p 31 | a 32 | r 33 | t 34 | y 35 |
36 | 37 |

This time we guessed right! You have six guesses to get the word.

38 | 39 |

40 | Unlike the original Wordle, Sverdle runs on the server instead of in the browser, making it 41 | impossible to cheat. It uses <form> and cookies to submit data, meaning you can 42 | even play with JavaScript disabled! 43 |

44 |
45 | 46 | 96 | -------------------------------------------------------------------------------- /demo/src/routes/Header.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 | SvelteKit 11 | 12 |
13 | 14 | 33 | 34 |
35 | 36 | GitHub 37 | 38 |
39 |
40 | 41 | 130 | -------------------------------------------------------------------------------- /.github/workflows/azure-static-web-apps-polite-desert-00b80111e.yml: -------------------------------------------------------------------------------- 1 | name: Azure Static Web Apps CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened, closed] 9 | branches: 10 | - main 11 | 12 | jobs: 13 | clean: 14 | if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.event.action != 'closed' 15 | runs-on: ubuntu-latest 16 | name: Delete old bot comment 17 | steps: 18 | - name: pr-deleter 19 | uses: maheshrayas/action-pr-comment-delete@06d7254b4aeba4491a66a7e0f755b107f7373ccd 20 | with: 21 | github_token: '${{ secrets.GITHUB_TOKEN }}' 22 | org: 'geoffrich' 23 | repo: 'svelte-adapter-azure-swa' 24 | user: 'github-actions[bot]' 25 | issue: '${{github.event.number}}' 26 | build_and_deploy_job: 27 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.event.action != 'closed') 28 | runs-on: ubuntu-latest 29 | name: Build and Deploy Job 30 | steps: 31 | - uses: actions/checkout@v2 32 | with: 33 | submodules: true 34 | - name: Build And Deploy 35 | id: builddeploy 36 | uses: Azure/static-web-apps-deploy@v1 37 | with: 38 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_POLITE_DESERT_00B80111E }} 39 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 40 | action: 'upload' 41 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### 42 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig 43 | app_location: '/demo' # App source code path 44 | api_location: 'demo/func' # Api source code path - optional 45 | output_location: 'build/static' # Built app content directory - optional 46 | ###### End of Repository/Build Configurations ###### 47 | outputs: 48 | preview_url: ${{ steps.builddeploy.outputs.static_web_app_url }} 49 | test: 50 | needs: build_and_deploy_job 51 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.event.action != 'closed') 52 | name: Run E2E Tests 53 | timeout-minutes: 10 54 | runs-on: ubuntu-latest 55 | defaults: 56 | run: 57 | working-directory: demo 58 | steps: 59 | - uses: actions/checkout@v3 60 | - uses: actions/setup-node@v2 61 | with: 62 | node-version: '20.x' 63 | - name: Install dependencies 64 | run: npm ci 65 | - name: Install Playwright 66 | run: npx playwright install chromium --with-deps 67 | - name: Run Playwright tests 68 | run: npm test 69 | env: 70 | PLAYWRIGHT_TEST_BASE_URL: ${{ needs.build_and_deploy_job.outputs.preview_url }} 71 | 72 | close_pull_request_job: 73 | if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.event.action == 'closed' 74 | runs-on: ubuntu-latest 75 | name: Close Pull Request Job 76 | steps: 77 | - name: Close Pull Request 78 | id: closepullrequest 79 | uses: Azure/static-web-apps-deploy@v1 80 | with: 81 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_POLITE_DESERT_00B80111E }} 82 | action: 'close' 83 | -------------------------------------------------------------------------------- /files/entry.js: -------------------------------------------------------------------------------- 1 | import { installPolyfills } from '@sveltejs/kit/node/polyfills'; 2 | import { Server } from 'SERVER'; 3 | import { manifest } from 'MANIFEST'; 4 | import { 5 | getClientIPFromHeaders, 6 | getClientPrincipalFromHeaders, 7 | splitCookiesFromHeaders 8 | } from './headers'; 9 | import { app, HttpResponse } from '@azure/functions'; 10 | 11 | // replaced at build time 12 | // @ts-expect-error 13 | const debug = DEBUG; 14 | 15 | installPolyfills(); 16 | 17 | const server = new Server(manifest); 18 | const initialized = server.init({ env: process.env }); 19 | 20 | /** 21 | * @typedef {import('@azure/functions').InvocationContext} InvocationContext 22 | * @typedef {import('@azure/functions').HttpRequest} HttpRequest 23 | */ 24 | 25 | app.setup({ 26 | enableHttpStream: true 27 | }); 28 | 29 | app.http('sk_render', { 30 | methods: ['HEAD', 'GET', 'POST', 'DELETE', 'PUT', 'OPTIONS', 'PATCH'], 31 | /** 32 | * 33 | * @param {HttpRequest} httpRequest 34 | * @param {InvocationContext} context 35 | */ 36 | handler: async (httpRequest, context) => { 37 | if (debug) { 38 | context.log( 39 | 'Starting request', 40 | httpRequest.method, 41 | httpRequest.headers.get('x-ms-original-url') 42 | ); 43 | context.log(`Request: ${JSON.stringify(httpRequest)}`); 44 | } 45 | 46 | const request = toRequest(httpRequest); 47 | 48 | const ipAddress = getClientIPFromHeaders(request.headers); 49 | const clientPrincipal = getClientPrincipalFromHeaders(request.headers); 50 | 51 | await initialized; 52 | const rendered = await server.respond(request, { 53 | getClientAddress() { 54 | return ipAddress; 55 | }, 56 | platform: { 57 | user: httpRequest.user, 58 | clientPrincipal, 59 | context 60 | } 61 | }); 62 | 63 | if (debug) { 64 | context.log(`SK headers: ${JSON.stringify(Object.fromEntries(rendered.headers.entries()))}`); 65 | context.log(`Response: ${JSON.stringify(rendered)}`); 66 | } 67 | 68 | return toResponse(rendered); 69 | } 70 | }); 71 | 72 | /** 73 | * @param {HttpRequest} httpRequest 74 | * @returns {Request} 75 | */ 76 | function toRequest(httpRequest) { 77 | // because we proxy all requests to the render function, the original URL in the request is /api/sk_render 78 | // this header contains the URL the user requested 79 | const originalUrl = httpRequest.headers.get('x-ms-original-url'); 80 | 81 | // SWA strips content-type headers from empty POST requests, but SK form actions require the header 82 | // https://github.com/geoffrich/svelte-adapter-azure-swa/issues/178 83 | if ( 84 | httpRequest.method === 'POST' && 85 | !httpRequest.body && 86 | !httpRequest.headers.get('content-type') 87 | ) { 88 | httpRequest.headers.set('content-type', 'application/x-www-form-urlencoded'); 89 | } 90 | 91 | /** @type {Record} */ 92 | const headers = {}; 93 | httpRequest.headers.forEach((value, key) => { 94 | if (key !== 'x-ms-original-url') { 95 | headers[key] = value; 96 | } 97 | }); 98 | 99 | return new Request(originalUrl, { 100 | method: httpRequest.method, 101 | headers: new Headers(headers), 102 | body: httpRequest.body, 103 | duplex: 'half' 104 | }); 105 | } 106 | 107 | /** 108 | * @param {Response} rendered 109 | * @returns {Promise} 110 | */ 111 | async function toResponse(rendered) { 112 | const { headers, cookies } = splitCookiesFromHeaders(rendered.headers); 113 | 114 | return new HttpResponse({ 115 | status: rendered.status, 116 | body: rendered.body, 117 | headers, 118 | cookies, 119 | enableContentNegotiation: false 120 | }); 121 | } 122 | -------------------------------------------------------------------------------- /types/swa.d.ts: -------------------------------------------------------------------------------- 1 | // types and documentation adapted from https://docs.microsoft.com/en-us/azure/static-web-apps/configuration 2 | export interface StaticWebAppConfig { 3 | routes?: Route[]; 4 | navigationFallback?: NavigationFallback; 5 | globalHeaders?: Record; 6 | responseOverrides?: Record; 7 | mimeTypes?: Record; 8 | platform?: Platform; 9 | } 10 | 11 | export type CustomStaticWebAppConfig = Omit; 12 | 13 | export interface Route { 14 | route: string; 15 | methods?: HttpMethod[]; 16 | rewrite?: string; 17 | redirect?: string; 18 | statusCode?: number; 19 | headers?: Record; 20 | allowedRoles?: string[]; 21 | } 22 | 23 | export interface NavigationFallback { 24 | rewrite: string; 25 | exclude?: string[]; 26 | } 27 | 28 | export type HttpMethod = 29 | | 'GET' 30 | | 'HEAD' 31 | | 'POST' 32 | | 'PUT' 33 | | 'DELETE' 34 | | 'CONNECT' 35 | | 'OPTIONS' 36 | | 'TRACE' 37 | | 'PATCH'; 38 | 39 | export interface ResponseOverride { 40 | rewrite?: string; 41 | statusCode?: number; 42 | redirect?: string; 43 | } 44 | 45 | export type OverridableResponseCodes = '400' | '401' | '403' | '404'; 46 | 47 | export interface Platform { 48 | apiRuntime: string; 49 | } 50 | 51 | /** 52 | * Client principal as presented to the render functions of a SWA. 53 | * 54 | * @see The official {@link https://learn.microsoft.com/en-us/azure/static-web-apps/user-information?tabs=javascript#client-principal-data documentation} 55 | * this was adapted from. 56 | */ 57 | export interface ClientPrincipal { 58 | /** 59 | * The name of the identity provider. 60 | * 61 | * @remarks 62 | * 63 | * Currently, the default providers use the following values here: 64 | * | Provider | value | 65 | * |----------|---------| 66 | * | Azure AD | aad | 67 | * | GitHub | github | 68 | * | Twitter | twitter | 69 | */ 70 | identityProvider: string; 71 | 72 | /** 73 | * An Azure Static Web Apps-specific unique identifier for the user. 74 | * 75 | * - The value is unique on a per-app basis. For instance, the same user 76 | * returns a different userId value on a different Static Web Apps 77 | * resource. 78 | * - The value persists for the lifetime of a user. If you delete and add 79 | * the same user back to the app, a new userId is generated. 80 | */ 81 | userId: string; 82 | 83 | /** 84 | * Username or email address of the user. Some providers return the user's 85 | * email address, while others send the user handle. 86 | * 87 | * @remarks 88 | * 89 | * Currently, the default providers use the following types of values here: 90 | * | Provider | value | 91 | * |----------|---------------| 92 | * | Azure AD | email address | 93 | * | GitHub | username | 94 | * | Twitter | username | 95 | */ 96 | userDetails: string; 97 | 98 | /** 99 | * An array of the user's assigned roles. 100 | * 101 | * All users (both authenticated and not) will always have the role 102 | * `anonymous` and authenticated users will always have the role 103 | * `authenticated`. Additional custom roles might be present as well. 104 | */ 105 | userRoles: string[]; 106 | } 107 | 108 | export interface ClientPrincipalWithClaims extends ClientPrincipal { 109 | claims: ClientPrincipalClaim[]; 110 | } 111 | 112 | export interface ClientPrincipalClaim { 113 | /** 114 | * The type of claim. 115 | * 116 | * Usually a standardized type like `name` or `ver`, or a schema url like 117 | * `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`. 118 | */ 119 | typ: string; 120 | 121 | /** 122 | * The value of the claim. 123 | */ 124 | val: string; 125 | } 126 | -------------------------------------------------------------------------------- /test/json.js: -------------------------------------------------------------------------------- 1 | // copied from https://github.com/duailibe/jest-json 2 | 3 | 'use strict'; 4 | 5 | const errorMarker = '__INTERNALERRORMARKER__'; 6 | 7 | /** 8 | * Jest matcher that receives a JSON string and matches to a value. 9 | * 10 | * expect(fooJson).toMatchJSON(expected) 11 | */ 12 | export function toMatchJSON(received, expected) { 13 | const { 14 | matcherErrorMessage, 15 | RECEIVED_COLOR, 16 | printDiffOrStringify, 17 | printExpected, 18 | printReceived, 19 | printWithType, 20 | stringify 21 | } = this.utils; 22 | 23 | const hint = this.utils.matcherHint('toMatchJSON', undefined, undefined, { 24 | isNot: this.isNot, 25 | promise: this.promise 26 | }); 27 | 28 | if (typeof received !== 'string') { 29 | throwError( 30 | matcherErrorMessage( 31 | hint, 32 | `${RECEIVED_COLOR('received')} value must be a valid JSON string`, 33 | printWithType('Received', received, printReceived) 34 | ) 35 | ); 36 | } 37 | 38 | try { 39 | received = JSON.parse(received); 40 | } catch (error) { 41 | const match = error.message.match(/Unexpected (\w+)(?: .)? in JSON at position (\d+)/); 42 | const index = match ? parseInt(match[2], 10) : received.length; 43 | const isEmpty = received.trim().length === 0; 44 | const message = isEmpty 45 | ? '' 46 | : match 47 | ? `Unexpected ${match[1]}: ${received[index]}` 48 | : 'Unexpected end of string'; 49 | throwError( 50 | matcherErrorMessage( 51 | hint, 52 | `${RECEIVED_COLOR('received')} value must be a valid JSON string. ${message}`, 53 | isEmpty 54 | ? 'Received: ' + RECEIVED_COLOR(stringify(received)) 55 | : printJsonError(stringify(received), RECEIVED_COLOR, index + 1) 56 | ) 57 | ); 58 | } 59 | 60 | const pass = this.equals(received, expected); 61 | const message = pass 62 | ? () => 63 | `${hint} \n\nExpected: not ${printExpected(expected)}` + 64 | (stringify(expected) !== stringify(received) 65 | ? `\nReceived: ${printReceived(received)}` 66 | : '') 67 | : () => 68 | `${hint} \n\n` + 69 | printDiffOrStringify(expected, received, 'Expected', 'Received', this.expand !== false); 70 | 71 | return { pass, message }; 72 | } 73 | 74 | /** 75 | * Asymmetric matcher to check the format of a JSON string. 76 | * 77 | * expect({ foo: fooJson }).toEqual({ 78 | * foo: expect.jsonMatching(expected), 79 | * }) 80 | */ 81 | export function jsonMatching(received, expected) { 82 | let pass = false; 83 | try { 84 | received = JSON.parse(received); 85 | pass = this.equals(received, expected); 86 | } catch (err) {} // eslint-disable-line no-empty 87 | return { pass }; 88 | } 89 | 90 | /** 91 | * Formats the JSON.parse error message 92 | */ 93 | function printJsonError(value, print, index) { 94 | let message = `Received: `; 95 | 96 | const lines = value.split('\n'); 97 | 98 | for (let i = 0, count = 0; i < lines.length; i++) { 99 | const line = lines[i]; 100 | message += print(line) + '\n'; 101 | if (index >= count && index <= count + line.length) { 102 | message += ' '.repeat(index - count + (i === 0 ? 10 : 0)) + '^\n'; 103 | } 104 | count += line.length + 1; 105 | } 106 | 107 | return message; 108 | } 109 | 110 | /** 111 | * Throws the errors removing the matcher from stack trace 112 | */ 113 | function throwError(message) { 114 | try { 115 | throw Error(errorMarker); 116 | } catch (err) { 117 | const stack = err.stack.slice(err.stack.indexOf(errorMarker) + errorMarker.length).split('\n'); 118 | 119 | for (let i = stack.length - 1; i > 0; i--) { 120 | // search for the "first" matcher call in the trace 121 | if (stack[i].includes('toMatchJSON')) { 122 | stack.splice(0, i + 1, message); 123 | break; 124 | } 125 | } 126 | 127 | const error = Error(message); 128 | error.stack = stack.join('\n'); 129 | throw error; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/headers.test.js: -------------------------------------------------------------------------------- 1 | import { installPolyfills } from '@sveltejs/kit/node/polyfills'; 2 | import { expect, describe, test } from 'vitest'; 3 | import { 4 | splitCookiesFromHeaders, 5 | getClientIPFromHeaders, 6 | getClientPrincipalFromHeaders 7 | } from '../files/headers'; 8 | 9 | installPolyfills(); 10 | 11 | describe('header processing', () => { 12 | test('no cookies', () => { 13 | const headers = new Headers(); 14 | headers.append('Location', '/'); 15 | headers.append('Content-Type', 'application/json'); 16 | 17 | const cookies = splitCookiesFromHeaders(headers); 18 | 19 | expect(cookies).toEqual({ 20 | cookies: [], 21 | headers 22 | }); 23 | }); 24 | 25 | test('multiple cookies', () => { 26 | const headers = new Headers(); 27 | 28 | // https://httpwg.org/specs/rfc7231.html#http.date 29 | const exp1 = 'Sun, 06 Nov 1994 08:49:37 GMT'; 30 | const exp2 = 'Sunday, 06-Nov-94 08:49:37 GMT'; 31 | const exp3 = 'Sun Nov 6 08:49:37 1994 GMT'; 32 | 33 | headers.append('Set-Cookie', `key1=val1; Expires=${exp1}`); 34 | headers.append('Set-Cookie', `key2=val2; Expires=${exp2}`); 35 | headers.append('Set-Cookie', `key3=val3; Expires=${exp3}`); 36 | 37 | const cookies = splitCookiesFromHeaders(headers); 38 | 39 | expect(cookies).toStrictEqual({ 40 | headers: new Headers(), 41 | cookies: [ 42 | { 43 | expires: new Date('1994-11-06T08:49:37.000Z'), 44 | name: 'key1', 45 | value: 'val1' 46 | }, 47 | { 48 | expires: new Date('1994-11-06T08:49:37.000Z'), 49 | name: 'key2', 50 | value: 'val2' 51 | }, 52 | { 53 | expires: new Date('1994-11-06T08:49:37.000Z'), 54 | name: 'key3', 55 | value: 'val3' 56 | } 57 | ] 58 | }); 59 | }); 60 | }); 61 | 62 | describe('client ip address detection', () => { 63 | test('no header', () => { 64 | const headers = new Headers(); 65 | headers.append('Location', '/'); 66 | headers.append('Content-Type', 'application/json'); 67 | 68 | const ipAddress = getClientIPFromHeaders(headers); 69 | 70 | expect(ipAddress).toBe('127.0.0.1'); 71 | }); 72 | 73 | test('regular header', () => { 74 | const headers = new Headers(); 75 | headers.append('Location', '/'); 76 | headers.append('Content-Type', 'application/json'); 77 | headers.append( 78 | 'X-Forwarded-For', 79 | '8.23.191.142:52987, [fd00::d63d:e0b8:bc35:12be:2e17:f3af]:64909' 80 | ); 81 | 82 | const ipAddress = getClientIPFromHeaders(headers); 83 | 84 | expect(ipAddress).toBe('8.23.191.142'); 85 | }); 86 | 87 | test('no port header', () => { 88 | const headers = new Headers(); 89 | headers.append('Location', '/'); 90 | headers.append('Content-Type', 'application/json'); 91 | headers.append('X-Forwarded-For', '8.23.191.142, [fd00::d63d:e0b8:bc35:12be:2e17:f3af]'); 92 | 93 | const ipAddress = getClientIPFromHeaders(headers); 94 | 95 | expect(ipAddress).toBe('8.23.191.142'); 96 | }); 97 | }); 98 | 99 | describe('client principal parsing', () => { 100 | test('parses client principal correctly', () => { 101 | const original = { 102 | identityProvider: 'aad', 103 | userId: '1234', 104 | userDetails: 'user@example.net', 105 | userRoles: ['authenticated'] 106 | }; 107 | 108 | const headers = new Headers({ 109 | 'x-ms-client-principal': Buffer.from(JSON.stringify(original)).toString('base64') 110 | }); 111 | 112 | expect(getClientPrincipalFromHeaders(headers)).toStrictEqual(original); 113 | }); 114 | 115 | test('returns undefined when there is no client principal', () => { 116 | expect(getClientPrincipalFromHeaders(new Headers())).toBeUndefined(); 117 | }); 118 | 119 | test('returns undefined if unable to parse', () => { 120 | const headers = new Headers({ 121 | 'x-ms-client-principal': 'boom' 122 | }); 123 | 124 | expect(getClientPrincipalFromHeaders(headers)).toBeUndefined(); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import { expect, describe, test, vi } from 'vitest'; 2 | import azureAdapter, { generateConfig } from '../index'; 3 | import { writeFileSync, existsSync } from 'fs'; 4 | import { jsonMatching, toMatchJSON } from './json'; 5 | import esbuild from 'esbuild'; 6 | 7 | expect.extend({ jsonMatching, toMatchJSON }); 8 | 9 | vi.mock('fs', () => ({ 10 | writeFileSync: vi.fn(), 11 | existsSync: vi.fn(() => true) 12 | })); 13 | 14 | vi.mock('esbuild', () => ({ 15 | default: { 16 | build: vi.fn(() => Promise.resolve()) 17 | } 18 | })); 19 | 20 | describe('generateConfig', () => { 21 | test('no custom config', () => { 22 | const result = generateConfig({}, 'appDir'); 23 | expect(result).toStrictEqual({ 24 | navigationFallback: { 25 | rewrite: '/api/sk_render' 26 | }, 27 | platform: { 28 | apiRuntime: 'node:20' 29 | }, 30 | routes: expect.arrayContaining([ 31 | { 32 | methods: ['POST', 'PUT', 'DELETE', 'PATCH'], 33 | rewrite: '/api/sk_render', 34 | route: '*' 35 | }, 36 | { 37 | headers: { 38 | 'cache-control': 'public, immutable, max-age=31536000' 39 | }, 40 | route: '/appDir/immutable/*' 41 | } 42 | ]) 43 | }); 44 | }); 45 | 46 | test('throws errors for invalid custom config', () => { 47 | expect(() => generateConfig({ navigationFallback: {} })).toThrowError( 48 | 'cannot override navigationFallback' 49 | ); 50 | expect(() => generateConfig({ routes: [{ route: '*' }] })).toThrowError( 51 | "cannot override '*' route" 52 | ); 53 | }); 54 | 55 | test('accepts custom config', () => { 56 | const result = generateConfig({ 57 | platform: { 58 | apiRuntime: 'node:20' 59 | }, 60 | globalHeaders: { 'X-Foo': 'bar' } 61 | }); 62 | expect(result.globalHeaders).toStrictEqual({ 'X-Foo': 'bar' }); 63 | expect(result.platform.apiRuntime).toBe('node:20'); 64 | }); 65 | }); 66 | 67 | describe('adapt', () => { 68 | test('runs', async () => { 69 | const adapter = azureAdapter(); 70 | const builder = getMockBuilder(); 71 | await adapter.adapt(builder); 72 | expect(builder.writePrerendered).toBeCalled(); 73 | expect(builder.writeClient).toBeCalled(); 74 | expect(builder.copy).toBeCalledWith(expect.stringContaining('api'), 'build/server'); 75 | }); 76 | 77 | test('writes to custom api directory', async () => { 78 | const adapter = azureAdapter({ apiDir: 'custom/api' }); 79 | const builder = getMockBuilder(); 80 | await adapter.adapt(builder); 81 | expect(esbuild.build).toBeCalledWith( 82 | expect.objectContaining({ 83 | outfile: 'custom/api/sk_render/index.js' 84 | }) 85 | ); 86 | 87 | // we don't copy the required function files to a custom API directory 88 | expect(builder.copy).not.toBeCalledWith(expect.stringContaining('api'), 'custom/api'); 89 | }); 90 | 91 | test('writes to custom static directory', async () => { 92 | vi.mocked(existsSync).mockImplementationOnce(() => false); 93 | const adapter = azureAdapter({ staticDir: 'custom/static' }); 94 | const builder = getMockBuilder(); 95 | await adapter.adapt(builder); 96 | expect(builder.writeClient).toBeCalledWith('custom/static'); 97 | expect(builder.writePrerendered).toBeCalledWith('custom/static'); 98 | }); 99 | 100 | test('logs warning when custom api directory set and required file does not exist', async () => { 101 | vi.mocked(existsSync).mockImplementationOnce(() => false); 102 | const adapter = azureAdapter({ apiDir: 'custom/api' }); 103 | const builder = getMockBuilder(); 104 | await adapter.adapt(builder); 105 | expect(builder.log.warn).toBeCalled(); 106 | }); 107 | 108 | test('adds index.html when root not prerendered', async () => { 109 | const adapter = azureAdapter(); 110 | const builder = getMockBuilder(); 111 | builder.prerendered.paths = []; 112 | await adapter.adapt(builder); 113 | 114 | expect(writeFileSync).toBeCalledWith(expect.stringContaining('index.html'), ''); 115 | expect(writeFileSync).toBeCalledWith( 116 | expect.stringContaining('staticwebapp.config.json'), 117 | expect.jsonMatching( 118 | expect.objectContaining({ 119 | routes: expect.arrayContaining([ 120 | { 121 | route: '/index.html', 122 | rewrite: '/api/sk_render' 123 | }, 124 | { 125 | route: '/', 126 | rewrite: '/api/sk_render' 127 | } 128 | ]) 129 | }) 130 | ) 131 | ); 132 | }); 133 | 134 | test.each(['/api', '/api/foo'])('throws error when the app defines %s route', async (routeId) => { 135 | const adapter = azureAdapter(); 136 | const builder = getMockBuilder(); 137 | builder.routes.push({ 138 | id: routeId 139 | }); 140 | await expect(adapter.adapt(builder)).rejects.toThrowError( 141 | 'Conflicting routes detected. Please rename the routes listed above.' 142 | ); 143 | }); 144 | 145 | test('does not throw error for /api route if allowReservedSwaRoutes is defined', async () => { 146 | const adapter = azureAdapter({ allowReservedSwaRoutes: true }); 147 | const builder = getMockBuilder(); 148 | builder.routes.push({ 149 | id: '/api' 150 | }); 151 | await expect(adapter.adapt(builder)).resolves.not.toThrow(); 152 | }); 153 | 154 | test('handles null routes', async () => { 155 | // builder.routes was added in 1.5 with route-level config 156 | const adapter = azureAdapter(); 157 | const builder = getMockBuilder(); 158 | builder.routes = null; 159 | await expect(adapter.adapt(builder)).resolves.not.toThrow(); 160 | }); 161 | }); 162 | 163 | /** @returns {import('@sveltejs/kit').Builder} */ 164 | function getMockBuilder() { 165 | return { 166 | config: { 167 | kit: { 168 | appDir: '/app' 169 | } 170 | }, 171 | log: { 172 | minor: vi.fn(), 173 | warn: vi.fn(), 174 | error: vi.fn() 175 | }, 176 | prerendered: { 177 | paths: ['/'] 178 | }, 179 | copy: vi.fn(), 180 | generateManifest: vi.fn(), 181 | getBuildDirectory: vi.fn((x) => x), 182 | getServerDirectory: vi.fn(() => 'server'), 183 | rimraf: vi.fn(), 184 | writeClient: vi.fn(), 185 | writePrerendered: vi.fn(), 186 | routes: [ 187 | { 188 | id: '/' 189 | }, 190 | { 191 | id: '/about' 192 | } 193 | ] 194 | }; 195 | } 196 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { writeFileSync, existsSync } from 'fs'; 2 | import { join, posix } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import esbuild from 'esbuild'; 5 | 6 | /** 7 | * @typedef {import('esbuild').BuildOptions} BuildOptions 8 | */ 9 | 10 | const ssrFunctionRoute = '/api/sk_render'; 11 | 12 | /** 13 | * Validate the static web app configuration does not override the minimum config for the adapter to work correctly. 14 | * @param config {import('./types/swa').CustomStaticWebAppConfig} 15 | * */ 16 | function validateCustomConfig(config) { 17 | if (config) { 18 | if ('navigationFallback' in config) { 19 | throw new Error('customStaticWebAppConfig cannot override navigationFallback.'); 20 | } 21 | if (config.routes && config.routes.find((route) => route.route === '*')) { 22 | throw new Error(`customStaticWebAppConfig cannot override '*' route.`); 23 | } 24 | } 25 | } 26 | 27 | /** @type {import('.').default} */ 28 | export default function ({ 29 | debug = false, 30 | customStaticWebAppConfig = {}, 31 | esbuildOptions = {}, 32 | apiDir: customApiDir = '', 33 | staticDir: customStaticDir = '', 34 | allowReservedSwaRoutes = false 35 | } = {}) { 36 | return { 37 | name: 'adapter-azure-swa', 38 | 39 | async adapt(builder) { 40 | // TODO: remove for 1.0 41 | if (!customApiDir && existsSync(join('api', 'render'))) { 42 | builder.log.warn( 43 | `Warning: you have an api/render folder but this adapter now uses the build/server folder for API functions. You may need to update your build configuration. Failing to do so could break your deployed site. 44 | Please see the PR for migration instructions: https://github.com/geoffrich/svelte-adapter-azure-swa/pull/92` 45 | ); 46 | } 47 | 48 | const conflictingRoutes = 49 | builder.routes?.map((route) => route.id).filter((routeId) => routeId.startsWith('/api')) ?? 50 | []; 51 | if (!allowReservedSwaRoutes && conflictingRoutes.length > 0) { 52 | builder.log.error( 53 | `Error: the following routes conflict with Azure SWA's reserved /api route: ${conflictingRoutes.join( 54 | ', ' 55 | )}. Requests to these routes in production will return 404 instead of hitting your SvelteKit app. 56 | 57 | To resolve this error, move the conflicting routes so they do not start with /api. For example, move /api/blog to /blog. 58 | If you want to suppress this error, set allowReservedSwaRoutes to true in your adapter options. 59 | ` 60 | ); 61 | 62 | throw new Error('Conflicting routes detected. Please rename the routes listed above.'); 63 | } 64 | 65 | const swaConfig = generateConfig(customStaticWebAppConfig, builder.config.kit.appDir); 66 | 67 | const tmp = builder.getBuildDirectory('azure-tmp'); 68 | const publish = 'build'; 69 | const staticDir = customStaticDir || join(publish, 'static'); 70 | const apiDir = customApiDir || join(publish, 'server'); 71 | const functionDir = join(apiDir, 'sk_render'); 72 | const entry = `${tmp}/entry.js`; 73 | builder.log.minor(`Publishing to "${publish}"`); 74 | 75 | builder.rimraf(tmp); 76 | builder.rimraf(publish); 77 | 78 | const files = fileURLToPath(new URL('./files', import.meta.url)); 79 | 80 | builder.log.minor('Generating serverless function...'); 81 | 82 | // use posix because of https://github.com/sveltejs/kit/pull/3200 83 | const relativePath = posix.relative(tmp, builder.getServerDirectory()); 84 | 85 | builder.copy(files, tmp, { 86 | replace: { 87 | SERVER: `${relativePath}/index.js`, 88 | MANIFEST: './manifest.js', 89 | DEBUG: debug.toString() 90 | } 91 | }); 92 | 93 | if (customApiDir) { 94 | checkForMissingFiles(); 95 | } else { 96 | // if the user specified a custom API directory, assume they are creating the required function files themselves 97 | builder.copy(join(files, 'api'), apiDir); 98 | } 99 | 100 | writeFileSync( 101 | `${tmp}/manifest.js`, 102 | `export const manifest = ${builder.generateManifest({ 103 | relativePath 104 | })};\n` 105 | ); 106 | 107 | // add @azure/functions to esbuildOptions.external if not already set - this is needed by the Azure Functiions v4 runtime 108 | if (!esbuildOptions.external?.includes('@azure/functions')) { 109 | esbuildOptions.external = [...(esbuildOptions.external || []), '@azure/functions']; 110 | } 111 | 112 | /** @type {BuildOptions} */ 113 | const default_options = { 114 | entryPoints: [entry], 115 | outfile: join(functionDir, 'index.js'), 116 | bundle: true, 117 | platform: 'node', 118 | target: 'node20', 119 | sourcemap: 'linked', 120 | external: esbuildOptions.external, 121 | keepNames: esbuildOptions.keepNames, 122 | loader: esbuildOptions.loader 123 | }; 124 | 125 | await esbuild.build(default_options); 126 | 127 | builder.log.minor('Copying assets...'); 128 | builder.writeClient(staticDir); 129 | builder.writePrerendered(staticDir); 130 | 131 | if (!builder.prerendered.paths.includes('/')) { 132 | // Azure SWA requires an index.html to be present 133 | // If the root was not pre-rendered, add a placeholder index.html 134 | // Route all requests for the index to the SSR function 135 | writeFileSync(`${staticDir}/index.html`, ''); 136 | swaConfig.routes.push( 137 | { 138 | route: '/index.html', 139 | rewrite: ssrFunctionRoute 140 | }, 141 | { 142 | route: '/', 143 | rewrite: ssrFunctionRoute 144 | } 145 | ); 146 | } 147 | 148 | writeFileSync(`${publish}/staticwebapp.config.json`, JSON.stringify(swaConfig, null, 2)); 149 | 150 | /** 151 | * Check for missing files when a custom API directory is provided. 152 | */ 153 | function checkForMissingFiles() { 154 | const requiredFiles = ['host.json', 'package.json']; 155 | for (const file of requiredFiles) { 156 | if (!existsSync(join(customApiDir, file))) { 157 | builder.log.warn( 158 | `Warning: apiDir set but ${file} does not exist. You will need to create this file yourself. See the docs for more information: https://github.com/geoffrich/svelte-adapter-azure-swa#apidir` 159 | ); 160 | } 161 | } 162 | } 163 | } 164 | }; 165 | } 166 | 167 | /** 168 | * @param {import('./types/swa').CustomStaticWebAppConfig} customStaticWebAppConfig 169 | * @param {string} appDir 170 | * @returns {import('./types/swa').StaticWebAppConfig} 171 | */ 172 | export function generateConfig(customStaticWebAppConfig, appDir) { 173 | validateCustomConfig(customStaticWebAppConfig); 174 | 175 | if (!customStaticWebAppConfig.routes) { 176 | customStaticWebAppConfig.routes = []; 177 | } 178 | 179 | /** @type {import('./types/swa').StaticWebAppConfig} */ 180 | const swaConfig = { 181 | ...customStaticWebAppConfig, 182 | routes: [ 183 | ...customStaticWebAppConfig.routes, 184 | { 185 | route: '/api/*' 186 | }, 187 | { 188 | route: '/data-api/*' 189 | }, 190 | { 191 | route: '*', 192 | methods: ['POST', 'PUT', 'DELETE', 'PATCH'], 193 | rewrite: ssrFunctionRoute 194 | }, 195 | { 196 | route: `/${appDir}/immutable/*`, 197 | headers: { 198 | 'cache-control': 'public, immutable, max-age=31536000' 199 | } 200 | } 201 | ], 202 | navigationFallback: { 203 | rewrite: ssrFunctionRoute 204 | }, 205 | platform: { 206 | apiRuntime: 'node:20', 207 | ...customStaticWebAppConfig.platform 208 | } 209 | }; 210 | 211 | return swaConfig; 212 | } 213 | -------------------------------------------------------------------------------- /demo/src/routes/sverdle/+page.svelte: -------------------------------------------------------------------------------- 1 | 99 | 100 | 101 | 102 | 103 | Sverdle 104 | 105 | 106 | 107 |

Sverdle

108 | 109 |
{ 113 | // prevent default callback from resetting the form 114 | return ({ update }) => { 115 | update({ reset: false }); 116 | }; 117 | }} 118 | > 119 | How to play 120 | 121 |
122 | {#each Array.from(Array(6).keys()) as row (row)} 123 | {@const current = row === i} 124 |

Row {row + 1}

125 |
126 | {#each Array.from(Array(5).keys()) as column (column)} 127 | {@const guess = current ? currentGuess : data.guesses[row]} 128 | {@const answer = data.answers[row]?.[column]} 129 | {@const value = guess?.[column] ?? ''} 130 | {@const selected = current && column === guess.length} 131 | {@const exact = answer === 'x'} 132 | {@const close = answer === 'c'} 133 | {@const missing = answer === '_'} 134 |
135 | {value} 136 | 137 | {#if exact} 138 | (correct) 139 | {:else if close} 140 | (present) 141 | {:else if missing} 142 | (absent) 143 | {:else} 144 | empty 145 | {/if} 146 | 147 | 148 |
149 | {/each} 150 |
151 | {/each} 152 |
153 | 154 |
155 | {#if won || data.answers.length >= 6} 156 | {#if !won && data.answer} 157 |

the answer was "{data.answer}"

158 | {/if} 159 | 162 | {:else} 163 |
164 | 165 | 166 | 175 | 176 | {#each ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'] as row} 177 |
178 | {#each row as letter} 179 | 191 | {/each} 192 |
193 | {/each} 194 |
195 | {/if} 196 |
197 |
198 | 199 | {#if won} 200 |
210 | {/if} 211 | 212 | 424 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### [0.22.1](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.22.0...v0.22.1) (2025-11-26) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * support PATCH verb ([#209](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/209)) ([1fa034f](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/1fa034f23b3372c5c8db135bf5faa35cd21b9d04)) 9 | 10 | ## [0.22.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.21.0...v0.22.0) (2025-05-01) 11 | 12 | 13 | ### Features 14 | 15 | * use Node 20 by default ([53cf290](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/53cf290df4ce90c349e2abc3f38966887cb23ab3)) 16 | 17 | ## [0.21.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.20.1...v0.21.0) (2025-03-04) 18 | 19 | This is a breaking change if you are deploying your own Azure functions alongside the Static Web App (`apiDir`) - see [the release notes](https://github.com/geoffrich/svelte-adapter-azure-swa/releases/tag/v0.21.0) for a migration guide. 20 | 21 | ### Features 22 | 23 | * switch to Azure Functions v4 ([#177](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/177)) ([96e6067](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/96e6067c50926c8328a5f78969ce74c304aaba75)) 24 | 25 | ### [0.20.1](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.20.0...v0.20.1) (2024-07-13) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * work around SWA issue with empty form bodies ([#179](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/179)) ([d14aae1](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/d14aae1bd35a68ed6836db6277753f2e3619de1b)) 31 | 32 | ## [0.20.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.19.1...v0.20.0) (2024-01-01) 33 | 34 | 35 | ### Features 36 | 37 | * require SvelteKit 2 and Node 18 ([c77c842](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/c77c842659297a687239b33ba52546285d7b26d3)) 38 | 39 | ### [0.19.1](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.19.0...v0.19.1) (2023-11-20) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * do not rewrite /api and /data-api requests to SvelteKit ([#162](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/162)) ([aa36771](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/aa3677133d404cf7bb396ea3f4c41ea026598ce7)) 45 | * do not throw on parsing client principal ([#160](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/160)) ([0fe3eaa](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/0fe3eaa593e3f58044b230957e190cb3011ea4d5)) 46 | 47 | ## [0.19.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.18.0...v0.19.0) (2023-08-17) 48 | 49 | 50 | ### Features 51 | 52 | * add `loader` to `esbuildOptions` ([#153](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/153)) ([f1f4ec6](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/f1f4ec6800a3d90e8002207c0609939fa1ccc049)) 53 | 54 | ## [0.18.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.17.0...v0.18.0) (2023-08-03) 55 | 56 | 57 | ### Features 58 | 59 | * add `keepNames` to `esbuildOptions` ([#150](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/150)) ([dbe44ba](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/dbe44ba4d239a4bc1db238b909fd8a5f55b5baf4)) 60 | 61 | ## [0.17.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.16.0...v0.17.0) (2023-07-02) 62 | 63 | 64 | ### Features 65 | 66 | * allow overriding `platform.apiRuntime` ([#144](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/144)) ([fd8241a](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/fd8241a7a02846d846fc291cb4c74a966d00db14)) 67 | * throw error if /api routes defined ([#142](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/142)) ([036cf64](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/036cf64fbedc3f4ec95271be03f75ada66eb7f2e)) 68 | 69 | ## [0.16.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.15.0...v0.16.0) (2023-05-12) 70 | 71 | 72 | ### Features 73 | 74 | * add `staticDir` setting ([#117](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/117)) ([4f2ff41](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/4f2ff41a8355ce070fc96f7d9c709806a74a2341)) 75 | 76 | ## [0.15.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.14.0...v0.15.0) (2023-03-18) 77 | 78 | 79 | ### Features 80 | 81 | * add context to the platform object ([#127](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/127)) ([e597d0c](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/e597d0cbd54a0e486a6bedc93d6b8071bb25f2b8)) 82 | 83 | 84 | ### Bug Fixes 85 | 86 | * await server init ([#128](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/128)) ([4900a35](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/4900a351ee78fcecf3050cf0a28696548646cd1a)) 87 | 88 | ## [0.14.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.13.0...v0.14.0) (2023-03-15) 89 | 90 | 91 | ### Features 92 | 93 | * expose client principal through platform ([#107](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/107)) ([e41f89c](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/e41f89cde57858b76df61a7ba6316f5ac0a4498d)) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * binary body is incorrectly parsed as UTF-8 ([#123](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/123)) ([4869959](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/48699595be4cabe355f070250780492bba5a1fdb)) 99 | 100 | ## [0.13.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.12.0...v0.13.0) (2023-01-23) 101 | 102 | 103 | ### Features 104 | 105 | * bump deps to SvelteKit 1.0 ([d050933](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/d050933b237530b6064351fe619c6f36bbee66d2)) 106 | 107 | ## [0.12.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.11.0...v0.12.0) (2022-11-21) 108 | 109 | 110 | ### Features 111 | 112 | * auto-create required function files ([#92](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/92)) ([c682164](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/c682164dfcc35045fbb66cc1ea31f3a8e253a411)) 113 | * implement getClientAddress ([#71](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/71)) ([56b5380](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/56b5380c8ebb29c2678bb83ea3cbf9189dd09ee6)) 114 | 115 | ## [0.11.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.10.0...v0.11.0) (2022-10-18) 116 | 117 | 118 | ### Features 119 | 120 | * generate sourcemaps ([#75](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/75)) ([90e19bd](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/90e19bdbec8dbc7833d54246bf61bbf3d9c93d89)) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * copy over necessary files ([#84](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/84)) ([f503556](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/f5035567c923f1583fd2c3c9330e7668c9334239)) 126 | * handle multiple Set-Cookie headers ([#74](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/74)) ([5a6a64f](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/5a6a64f42349aad0da2d61b11e63189130eaf1dd)) 127 | 128 | ## [0.10.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.9.0...v0.10.0) (2022-08-23) 129 | 130 | 131 | ### Features 132 | 133 | * initialize `env` ([#58](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/58)) ([88df929](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/88df929f215e588e94fbaa984e78f98e0b3a4278)) 134 | 135 | ## [0.9.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.8.0...v0.9.0) (2022-08-17) 136 | 137 | 138 | ### Features 139 | 140 | * add `esbuildOptions` to config ([#51](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/51)) ([c076f04](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/c076f0485b1d14c778111eb74ab4eea87ca8c2b2)) 141 | 142 | ## [0.8.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.7.0...v0.8.0) (2022-07-25) 143 | 144 | 145 | ### Features 146 | 147 | * remove call to writeStatic ([0d88a6f](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/0d88a6f2ee6e8a039bc3fc8ae799b40131d3d147)) 148 | 149 | ## [0.7.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.6.1...v0.7.0) (2022-06-11) 150 | 151 | 152 | ### Features 153 | 154 | * handle polyfill breaking change ([51baba9](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/51baba98416687b0a1436e644ec67dd3d18fcf92)) 155 | * target node 16 ([4b1b68f](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/4b1b68fb87f4a4fb534d69a65f8940a1f83c0de9)) 156 | * throw error if API package.json does not exist ([fd60cb2](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/fd60cb276e2c847a46fe5f02cf9f42c9be723c7b)) 157 | * update immutable directory ([b729d3b](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/b729d3bea0ba32d955fc3d1a60f3fd61ebbbbcc6)) 158 | 159 | 160 | ### Bug Fixes 161 | 162 | * update types to support ESNext ([4cd7535](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/4cd7535fb1c162f19bb5df71fea70302a6402562)) 163 | 164 | ### [0.6.1](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.6.0...v0.6.1) (2022-03-11) 165 | 166 | 167 | ### Bug Fixes 168 | 169 | * prevent file not found error when index not prerendered ([#39](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/39)) ([0e7960a](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/0e7960ac8f8dd2f4df8bb14afb627c79f7c68714)) 170 | 171 | ## [0.6.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.5.0...v0.6.0) (2022-03-09) 172 | 173 | 174 | ### Features 175 | 176 | * support SvelteKit 1.0.0-next.292 ([db1ffc6](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/db1ffc65ed362708627819cb7e627b268d007e8a)) 177 | 178 | ## [0.5.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.4.0...v0.5.0) (2022-02-26) 179 | 180 | 181 | ### Features 182 | 183 | * support SvelteKit 1.0.0-next.287 ([#33](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/33)) ([b79ab95](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/b79ab95e20f9be2c231b5492cfc975b19222f935)) 184 | 185 | 186 | ### Bug Fixes 187 | 188 | * allow returning binary responses from endpoints ([#32](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/32)) ([e0070cb](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/e0070cb802539c46235cd81edfe949a3de8e9edd)) 189 | 190 | ## [0.4.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.3.0...v0.4.0) (2022-02-03) 191 | 192 | 193 | ### Features 194 | 195 | * add customStaticWebAppConfig option ([#28](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/28)) ([70702ff](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/70702ff00842b6ddf53a6a44a1c6dc56c6e3371b)) 196 | * add immutable cache headers to hashed assets ([66e4cc4](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/66e4cc4f520dabeb57050db805073dd98b482c2e)) 197 | * support SvelteKit 1.0.0-next.234 ([#29](https://www.github.com/geoffrich/svelte-adapter-azure-swa/issues/29)) ([61c374d](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/61c374d7deb2ce0af2a503b6cdaaab9aa762b3c3)) 198 | 199 | ## [0.3.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.2.0...v0.3.0) (2022-01-07) 200 | 201 | 202 | ### Features 203 | 204 | * add debug setting to log request info ([581bdf3](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/581bdf3b2955b0906f6c18fe0c1ef0cba925c8d0)) 205 | * allow index.html to be dynamic ([1a089e1](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/1a089e1e51797ea906263bdbdf50b41a05d3fd8d)) 206 | 207 | ## [0.2.0](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.1.0...v0.2.0) (2022-01-05) 208 | 209 | 210 | ### Features 211 | 212 | * support SK next-208 ([2a8a5f9](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/2a8a5f9726dc7204050788b6c6d806636c762d18)) 213 | 214 | ## 0.1.0 (2021-12-23) 215 | 216 | 217 | ### Features 218 | 219 | * initial commit ([5f4492b](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/5f4492b9f73b2871c9a62c5f19f11a45b8bffece)) 220 | 221 | 222 | ### Bug Fixes 223 | 224 | * transpile output for Node 12 ([edb7153](https://www.github.com/geoffrich/svelte-adapter-azure-swa/commit/edb715336c7891381b0e3f90e247f398cd16692e)) 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svelte-adapter-azure-swa 2 | 3 | Adapter for Svelte apps that creates an Azure Static Web App, using an Azure function for dynamic server rendering. If your app is purely static, you may be able to use [adapter-static](https://www.npmjs.com/package/@sveltejs/adapter-static) instead. 4 | 5 | See the [demo folder](https://github.com/geoffrich/svelte-adapter-azure-swa/tree/main/demo) for an example integration with the SvelteKit demo app. The demo is automatically deployed to [Azure SWA](https://polite-desert-00b80111e.2.azurestaticapps.net/) on every commit to the main branch. 6 | 7 | ## Usage 8 | 9 | Run `npm install -D svelte-adapter-azure-swa`. 10 | 11 | Then in your `svelte.config.js`: 12 | 13 | ```js 14 | import azure from 'svelte-adapter-azure-swa'; 15 | 16 | export default { 17 | kit: { 18 | ... 19 | adapter: azure() 20 | } 21 | }; 22 | ``` 23 | 24 | And, if you use TypeScript, add this to the top of your `src/app.d.ts`: 25 | 26 | ```ts 27 | /// 28 | ``` 29 | 30 | :warning: **IMPORTANT**: you also need to configure your build so that your SvelteKit site deploys properly. Failing to do so will prevent the project from building and deploying. See the next section for instructions. 31 | 32 | ## Azure configuration 33 | 34 | When deploying to Azure, you will need to properly [configure your build](https://docs.microsoft.com/en-us/azure/static-web-apps/build-configuration?tabs=github-actions) so that both the static files and API are deployed. 35 | 36 | | property | value | 37 | | ----------------- | -------------- | 38 | | `app_location` | `./` | 39 | | `api_location` | `build/server` | 40 | | `output_location` | `build/static` | 41 | 42 | If you use a custom API directory (see [below](#apiDir)), your `api_location` will be the same as the value you pass to `apiDir`. 43 | 44 | If your `app_location` is in a subfolder (e.g. `./my_app_location`), then your `api_location` should include the path to that subfolder (e.g. `my_app_location/build/server`.) `output_location` should still be `build/static`. 45 | 46 | ### CUSTOM_BUILD_COMMAND considerations 47 | 48 | If you are setting a [`CUSTOM_BUILD_COMMAND`](https://github.com/microsoft/Oryx/blob/main/doc/configuration.md) in your build pipeline to customize how the API is built (e.g. to run `npm ci` instead of `npm install`), make sure to run `npm install` inside the API directory to install production dependencies. Otherwise, the SvelteKit render function will not be able to start up. 49 | 50 | ```yaml 51 | ... 52 | env: 53 | CUSTOM_BUILD_COMMAND: "npm ci && npm run build && npm install --prefix ./build/server --omit=dev" 54 | with: 55 | skip_api_build: true 56 | ... 57 | ``` 58 | 59 | ## Running locally with the Azure SWA CLI 60 | 61 | You can debug using the [Azure Static Web Apps CLI](https://github.com/Azure/static-web-apps-cli). Note that the CLI is currently in preview and you may encounter issues. 62 | 63 | To run the CLI, install `@azure/static-web-apps-cli` and the [Azure Functions Core Tools](https://github.com/Azure/static-web-apps-cli#serve-both-the-static-app-and-api) and add a `swa-cli.config.json` to your project (see sample below). Run `npm run build` to build your project and `swa start` to start the emulator. See the [CLI docs](https://github.com/Azure/static-web-apps-cli) for more information on usage. 64 | 65 | ### Sample `swa-cli.config.json` 66 | 67 | ```json 68 | { 69 | "configurations": { 70 | "app": { 71 | "outputLocation": "./build/static", 72 | "apiLocation": "./build/server", 73 | "host": "127.0.0.1" 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | ## Options 80 | 81 | ### apiDir 82 | 83 | The directory where the `sk_render` Azure function for SSR will be placed. Most of the time, you shouldn't need to set this. 84 | 85 | By default, the adapter will output the `sk_render` Azure function for SSR in the `build/server` folder. If you want to output it to a different directory instead (e.g. if you have additional Azure functions to deploy), you can set this option. 86 | 87 | **Note:** since the `sk_render` function is written using the [v4 Node.js programming model](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4), any other Azure functions you deploy with your Static Web App [need to also be written using v4](https://learn.microsoft.com/en-us/azure/azure-functions/functions-node-upgrade-v4?tabs=v4&pivots=programming-language-javascript). 88 | 89 | ```js 90 | import azure from 'svelte-adapter-azure-swa'; 91 | 92 | export default { 93 | kit: { 94 | ... 95 | adapter: azure({ 96 | apiDir: 'custom/api' 97 | }) 98 | } 99 | }; 100 | ``` 101 | 102 | If you set this option, you will also need to create a `host.json` and `package.json` in your API directory. The adapter normally generates these files by default, but skips them when a custom API directory is provided to prevent overwriting any existing files. You can see the default files the adapter generates in [this directory](https://github.com/geoffrich/svelte-adapter-azure-swa/tree/main/files/api). 103 | 104 | For instance, by default the adapter outputs these files... 105 | 106 | ``` 107 | build/ 108 | └── server/ 109 | ├── sk_render/ 110 | │ └── index.js 111 | ├── host.json 112 | ├── local.settings.json 113 | └── package.json 114 | ``` 115 | 116 | ... but only outputs these files when a custom API directory is provided: 117 | 118 | ``` 119 | custom/ 120 | └── api/ 121 | └── sk_render/ 122 | └── index.js 123 | ``` 124 | 125 | The `main` field in your `package.json` [needs to use a glob pattern](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#registering-a-function) that includes both the `sk_render/index.js` entrypoint as well as the entrypoints for your other Azure functions. Example `package.json`: 126 | 127 | ```json 128 | { 129 | "main": "**/index.js", 130 | "dependencies": { 131 | "@azure/functions": "^4" 132 | } 133 | } 134 | ``` 135 | 136 | Also note that the adapter reserves the folder prefix `sk_render` and API route prefix `sk_render` for Azure functions generated by the adapter. So, if you use a custom API directory, you cannot have any other folder starting with `sk_render` or functions available at the `sk_render` route, since these will conflict with the adapter's Azure functions. 137 | 138 | ### staticDir 139 | 140 | The directory where the static assets will be placed. Most of the time, you shouldn't need to set this. 141 | 142 | By default, the adapter will output the static JS, CSS and HTML files to the `build/static` folder. If you want to output it to a different directory instead you can set this option. 143 | 144 | ```js 145 | import azure from 'svelte-adapter-azure-swa'; 146 | 147 | export default { 148 | kit: { 149 | ... 150 | adapter: azure({ 151 | staticDir: 'custom/static' 152 | }) 153 | } 154 | }; 155 | ``` 156 | 157 | ### customStaticWebAppConfig 158 | 159 | An object containing additional Azure SWA [configuration options](https://docs.microsoft.com/en-us/azure/static-web-apps/configuration). This will be merged with the `staticwebapp.config.json` generated by the adapter. 160 | 161 | Attempting to override the default catch-all route (`route: '*'`) or the `navigationFallback` options will throw an error, since they are critical for server-side rendering. 162 | 163 | **Note:** customizing this config (especially `routes`) has the potential to break how SvelteKit handles the request. Make sure to test any modifications thoroughly. 164 | 165 | ```js 166 | import azure from 'svelte-adapter-azure-swa'; 167 | 168 | export default { 169 | kit: { 170 | ... 171 | adapter: azure({ 172 | customStaticWebAppConfig: { 173 | routes: [ 174 | { 175 | route: '/login', 176 | allowedRoles: ['admin'] 177 | } 178 | ], 179 | globalHeaders: { 180 | 'X-Content-Type-Options': 'nosniff', 181 | 'X-Frame-Options': 'DENY', 182 | 'Content-Security-Policy': "default-src https: 'unsafe-eval' 'unsafe-inline'; object-src 'none'", 183 | }, 184 | mimeTypes: { 185 | '.json': 'text/json' 186 | }, 187 | responseOverrides: { 188 | '401': { 189 | 'redirect': '/login', 190 | 'statusCode': 302 191 | } 192 | }, 193 | platform: { 194 | apiRuntime: 'node:20' 195 | } 196 | } 197 | }) 198 | } 199 | }; 200 | ``` 201 | 202 | ### allowReservedSwaRoutes 203 | 204 | In production, Azure SWA will route any requests to `/api` or `/api/*` to the SWA [API backend](https://learn.microsoft.com/en-us/azure/static-web-apps/apis-overview). If you also define SvelteKit routes beginning with `/api`, those requests will work in dev, but return a 404 in production since the request will be routed to the SWA API. Because of this, the adapter will throw an error at build time if it detects any routes beginning with `/api`. 205 | 206 | If you want to disable this check, you can set `allowReservedSwaRoutes` to true. However, this will not start routing `/api` requests to your SvelteKit app. SWA does not allow configuring the `/api` route. 207 | 208 | ```js 209 | import azure from 'svelte-adapter-azure-swa'; 210 | 211 | export default { 212 | kit: { 213 | ... 214 | adapter: azure({ 215 | allowReservedSwaRoutes: true 216 | }) 217 | } 218 | }; 219 | ``` 220 | 221 | ### esbuildOptions 222 | 223 | An object containing additional [esbuild options](https://esbuild.github.io/api/#build-api). Currently only supports [external](https://esbuild.github.io/api/#external), [keepNames](https://esbuild.github.io/api/#keep-names), and [loader](https://esbuild.github.io/api/#loader). If you require additional options to be exposed, please [open an issue](https://github.com/geoffrich/svelte-adapter-azure-swa/issues). 224 | 225 | ```js 226 | import azure from 'svelte-adapter-azure-swa'; 227 | 228 | export default { 229 | kit: { 230 | ... 231 | adapter: azure({ 232 | esbuildOptions: { 233 | external: ['fsevents'], 234 | keepNames: true 235 | } 236 | }) 237 | } 238 | }; 239 | ``` 240 | 241 | ## Platform-specific context 242 | 243 | SWA provides some information to the backend functions that this adapter makes available as [platform-specific context](https://kit.svelte.dev/docs/adapters#platform-specific-context). This is available in hooks and server routes through the `platform` property on the `RequestEvent`. 244 | 245 | To get typings for the `platform` property, reference this adapter in your `src/app.d.ts` as described in the [usage section](#usage). 246 | 247 | ### `clientPrincipal` 248 | 249 | This contains the client principal as parsed from the `x-ms-client-principal` request header. See the [official SWA documentation](https://learn.microsoft.com/en-us/azure/static-web-apps/user-information?tabs=javascript#api-functions) or [the types](index.d.ts) for further details. 250 | 251 | This is currently only available when running in production on SWA. In addition, it is only available in certain circumstances in production - see [this adapter issue](https://github.com/geoffrich/svelte-adapter-azure-swa/issues/102) for more details. Please report any issues you encounter. 252 | 253 | ### `context` 254 | 255 | All server requests to your SvelteKit app are handled by an Azure function. This property contains that Azure function's [request context](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node#context-object). 256 | 257 | ### `user` 258 | 259 | The `user` property of the Azure function's [HTTP request](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#http-request). 260 | 261 | ## Monorepo support 262 | 263 | If you're deploying your app from a monorepo, here's what you need to know. 264 | 265 | The build currently fails if you use `pnpm` as a package manager. You can track [this issue](https://github.com/geoffrich/svelte-adapter-azure-swa/issues/135) for updates. For now, you can work around the issue by using `npm` instead. 266 | 267 | Also, since your SvelteKit app is in a subfolder of the monorepo, you will need to update your deployment workflow. 268 | 269 | For instance, if you have the following folder structure: 270 | 271 | ``` 272 | apps/ 273 | ├── sveltekit-app 274 | └── other-app 275 | ``` 276 | 277 | The `app_location` and `api_location` in your deployment configuration need to point to the `apps/sveltekit-app` subfolder. `output_location` should remain the same. Here's how that would look for an Azure SWA GitHub workflow: 278 | 279 | ```diff 280 | steps: 281 | - uses: actions/checkout@v2 282 | with: 283 | submodules: true 284 | - name: Build And Deploy 285 | id: builddeploy 286 | uses: Azure/static-web-apps-deploy@v1 287 | with: 288 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ORANGE_GRASS_0778C6300 }} 289 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 290 | action: "upload" 291 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### 292 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig 293 | - app_location: "./" # App source code path 294 | - api_location: "build/server" # Api source code path - optional 295 | + app_location: "./apps/sveltekit-app" # App source code path 296 | + api_location: "apps/sveltekit-app/build/server" # Api source code path - optional 297 | output_location: "build/static" # Built app content directory - optional 298 | ###### End of Repository/Build Configurations ###### 299 | ``` 300 | 301 | ## Gotchas 302 | 303 | Azure has its share of surprising or quirky behaviors. Here is an evolving list of things to look out for: 304 | 305 | > [!CAUTION] 306 | > Azure silently strips the `content-type` header from requests that have no body. 307 | > 308 | > [SvelteKit form actions](https://kit.svelte.dev/docs/form-actions) are valid with no parameters, which can lead to `POST` requests that have an empty body. Unfortunately, [Azure deletes the `content-type` header when the request has an empty body](https://github.com/geoffrich/svelte-adapter-azure-swa/issues/178), which breaks SvelteKit's logic for handling form actions. Until [this is addressed by Azure](https://github.com/Azure/static-web-apps/issues/1512), update to [verson 0.20.1](https://github.com/geoffrich/svelte-adapter-azure-swa/releases/tag/v0.20.1) which contains a workaround for this behavior. 309 | -------------------------------------------------------------------------------- /demo/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit-azure-swa-demo", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "sveltekit-azure-swa-demo", 9 | "version": "0.1.0", 10 | "devDependencies": { 11 | "@fontsource/fira-mono": "^5.0.0", 12 | "@neoconfetti/svelte": "^2.0.0", 13 | "@playwright/test": "^1.49.1", 14 | "@sveltejs/adapter-auto": "^4.0.0", 15 | "@sveltejs/kit": "^2.49.0", 16 | "@sveltejs/vite-plugin-svelte": "^6.2.1", 17 | "svelte": "^5.45.1", 18 | "svelte-adapter-azure-swa": "file:..", 19 | "svelte-check": "^4.0.0", 20 | "typescript": "^5.0.0", 21 | "vite": "^7.2.4" 22 | } 23 | }, 24 | "..": { 25 | "version": "0.22.0", 26 | "dev": true, 27 | "license": "MIT", 28 | "dependencies": { 29 | "esbuild": "^0.19.9", 30 | "set-cookie-parser": "^2.6.0" 31 | }, 32 | "devDependencies": { 33 | "@azure/functions": "^4", 34 | "@sveltejs/kit": "^2.17.3", 35 | "@types/node": "^22.0.0", 36 | "@types/set-cookie-parser": "^2.4.7", 37 | "prettier": "^3.1.1", 38 | "typescript": "^5.0.0", 39 | "vitest": "^3.0.0" 40 | }, 41 | "peerDependencies": { 42 | "@sveltejs/kit": "^2.0.0" 43 | } 44 | }, 45 | "node_modules/@esbuild/aix-ppc64": { 46 | "version": "0.25.0", 47 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", 48 | "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", 49 | "cpu": [ 50 | "ppc64" 51 | ], 52 | "dev": true, 53 | "optional": true, 54 | "os": [ 55 | "aix" 56 | ], 57 | "engines": { 58 | "node": ">=18" 59 | } 60 | }, 61 | "node_modules/@esbuild/android-arm": { 62 | "version": "0.25.0", 63 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", 64 | "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", 65 | "cpu": [ 66 | "arm" 67 | ], 68 | "dev": true, 69 | "optional": true, 70 | "os": [ 71 | "android" 72 | ], 73 | "engines": { 74 | "node": ">=18" 75 | } 76 | }, 77 | "node_modules/@esbuild/android-arm64": { 78 | "version": "0.25.0", 79 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", 80 | "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", 81 | "cpu": [ 82 | "arm64" 83 | ], 84 | "dev": true, 85 | "optional": true, 86 | "os": [ 87 | "android" 88 | ], 89 | "engines": { 90 | "node": ">=18" 91 | } 92 | }, 93 | "node_modules/@esbuild/android-x64": { 94 | "version": "0.25.0", 95 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", 96 | "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", 97 | "cpu": [ 98 | "x64" 99 | ], 100 | "dev": true, 101 | "optional": true, 102 | "os": [ 103 | "android" 104 | ], 105 | "engines": { 106 | "node": ">=18" 107 | } 108 | }, 109 | "node_modules/@esbuild/darwin-arm64": { 110 | "version": "0.25.0", 111 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", 112 | "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", 113 | "cpu": [ 114 | "arm64" 115 | ], 116 | "dev": true, 117 | "optional": true, 118 | "os": [ 119 | "darwin" 120 | ], 121 | "engines": { 122 | "node": ">=18" 123 | } 124 | }, 125 | "node_modules/@esbuild/darwin-x64": { 126 | "version": "0.25.0", 127 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", 128 | "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", 129 | "cpu": [ 130 | "x64" 131 | ], 132 | "dev": true, 133 | "optional": true, 134 | "os": [ 135 | "darwin" 136 | ], 137 | "engines": { 138 | "node": ">=18" 139 | } 140 | }, 141 | "node_modules/@esbuild/freebsd-arm64": { 142 | "version": "0.25.0", 143 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", 144 | "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", 145 | "cpu": [ 146 | "arm64" 147 | ], 148 | "dev": true, 149 | "optional": true, 150 | "os": [ 151 | "freebsd" 152 | ], 153 | "engines": { 154 | "node": ">=18" 155 | } 156 | }, 157 | "node_modules/@esbuild/freebsd-x64": { 158 | "version": "0.25.0", 159 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", 160 | "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", 161 | "cpu": [ 162 | "x64" 163 | ], 164 | "dev": true, 165 | "optional": true, 166 | "os": [ 167 | "freebsd" 168 | ], 169 | "engines": { 170 | "node": ">=18" 171 | } 172 | }, 173 | "node_modules/@esbuild/linux-arm": { 174 | "version": "0.25.0", 175 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", 176 | "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", 177 | "cpu": [ 178 | "arm" 179 | ], 180 | "dev": true, 181 | "optional": true, 182 | "os": [ 183 | "linux" 184 | ], 185 | "engines": { 186 | "node": ">=18" 187 | } 188 | }, 189 | "node_modules/@esbuild/linux-arm64": { 190 | "version": "0.25.0", 191 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", 192 | "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", 193 | "cpu": [ 194 | "arm64" 195 | ], 196 | "dev": true, 197 | "optional": true, 198 | "os": [ 199 | "linux" 200 | ], 201 | "engines": { 202 | "node": ">=18" 203 | } 204 | }, 205 | "node_modules/@esbuild/linux-ia32": { 206 | "version": "0.25.0", 207 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", 208 | "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", 209 | "cpu": [ 210 | "ia32" 211 | ], 212 | "dev": true, 213 | "optional": true, 214 | "os": [ 215 | "linux" 216 | ], 217 | "engines": { 218 | "node": ">=18" 219 | } 220 | }, 221 | "node_modules/@esbuild/linux-loong64": { 222 | "version": "0.25.0", 223 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", 224 | "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", 225 | "cpu": [ 226 | "loong64" 227 | ], 228 | "dev": true, 229 | "optional": true, 230 | "os": [ 231 | "linux" 232 | ], 233 | "engines": { 234 | "node": ">=18" 235 | } 236 | }, 237 | "node_modules/@esbuild/linux-mips64el": { 238 | "version": "0.25.0", 239 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", 240 | "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", 241 | "cpu": [ 242 | "mips64el" 243 | ], 244 | "dev": true, 245 | "optional": true, 246 | "os": [ 247 | "linux" 248 | ], 249 | "engines": { 250 | "node": ">=18" 251 | } 252 | }, 253 | "node_modules/@esbuild/linux-ppc64": { 254 | "version": "0.25.0", 255 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", 256 | "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", 257 | "cpu": [ 258 | "ppc64" 259 | ], 260 | "dev": true, 261 | "optional": true, 262 | "os": [ 263 | "linux" 264 | ], 265 | "engines": { 266 | "node": ">=18" 267 | } 268 | }, 269 | "node_modules/@esbuild/linux-riscv64": { 270 | "version": "0.25.0", 271 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", 272 | "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", 273 | "cpu": [ 274 | "riscv64" 275 | ], 276 | "dev": true, 277 | "optional": true, 278 | "os": [ 279 | "linux" 280 | ], 281 | "engines": { 282 | "node": ">=18" 283 | } 284 | }, 285 | "node_modules/@esbuild/linux-s390x": { 286 | "version": "0.25.0", 287 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", 288 | "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", 289 | "cpu": [ 290 | "s390x" 291 | ], 292 | "dev": true, 293 | "optional": true, 294 | "os": [ 295 | "linux" 296 | ], 297 | "engines": { 298 | "node": ">=18" 299 | } 300 | }, 301 | "node_modules/@esbuild/linux-x64": { 302 | "version": "0.25.0", 303 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", 304 | "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", 305 | "cpu": [ 306 | "x64" 307 | ], 308 | "dev": true, 309 | "optional": true, 310 | "os": [ 311 | "linux" 312 | ], 313 | "engines": { 314 | "node": ">=18" 315 | } 316 | }, 317 | "node_modules/@esbuild/netbsd-arm64": { 318 | "version": "0.25.0", 319 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", 320 | "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", 321 | "cpu": [ 322 | "arm64" 323 | ], 324 | "dev": true, 325 | "optional": true, 326 | "os": [ 327 | "netbsd" 328 | ], 329 | "engines": { 330 | "node": ">=18" 331 | } 332 | }, 333 | "node_modules/@esbuild/netbsd-x64": { 334 | "version": "0.25.0", 335 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", 336 | "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", 337 | "cpu": [ 338 | "x64" 339 | ], 340 | "dev": true, 341 | "optional": true, 342 | "os": [ 343 | "netbsd" 344 | ], 345 | "engines": { 346 | "node": ">=18" 347 | } 348 | }, 349 | "node_modules/@esbuild/openbsd-arm64": { 350 | "version": "0.25.0", 351 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", 352 | "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", 353 | "cpu": [ 354 | "arm64" 355 | ], 356 | "dev": true, 357 | "optional": true, 358 | "os": [ 359 | "openbsd" 360 | ], 361 | "engines": { 362 | "node": ">=18" 363 | } 364 | }, 365 | "node_modules/@esbuild/openbsd-x64": { 366 | "version": "0.25.0", 367 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", 368 | "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", 369 | "cpu": [ 370 | "x64" 371 | ], 372 | "dev": true, 373 | "optional": true, 374 | "os": [ 375 | "openbsd" 376 | ], 377 | "engines": { 378 | "node": ">=18" 379 | } 380 | }, 381 | "node_modules/@esbuild/sunos-x64": { 382 | "version": "0.25.0", 383 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", 384 | "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", 385 | "cpu": [ 386 | "x64" 387 | ], 388 | "dev": true, 389 | "optional": true, 390 | "os": [ 391 | "sunos" 392 | ], 393 | "engines": { 394 | "node": ">=18" 395 | } 396 | }, 397 | "node_modules/@esbuild/win32-arm64": { 398 | "version": "0.25.0", 399 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", 400 | "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", 401 | "cpu": [ 402 | "arm64" 403 | ], 404 | "dev": true, 405 | "optional": true, 406 | "os": [ 407 | "win32" 408 | ], 409 | "engines": { 410 | "node": ">=18" 411 | } 412 | }, 413 | "node_modules/@esbuild/win32-ia32": { 414 | "version": "0.25.0", 415 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", 416 | "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", 417 | "cpu": [ 418 | "ia32" 419 | ], 420 | "dev": true, 421 | "optional": true, 422 | "os": [ 423 | "win32" 424 | ], 425 | "engines": { 426 | "node": ">=18" 427 | } 428 | }, 429 | "node_modules/@esbuild/win32-x64": { 430 | "version": "0.25.0", 431 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", 432 | "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", 433 | "cpu": [ 434 | "x64" 435 | ], 436 | "dev": true, 437 | "optional": true, 438 | "os": [ 439 | "win32" 440 | ], 441 | "engines": { 442 | "node": ">=18" 443 | } 444 | }, 445 | "node_modules/@fontsource/fira-mono": { 446 | "version": "5.2.5", 447 | "resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-5.2.5.tgz", 448 | "integrity": "sha512-rujrs+J+w2Nmqd6zsNQTzT7eYLKrSQWdF7SuAdjjXVs+Si06Ag6etOYFmF3Mzb0NufmEIPCDUS2ppt6hxX+SLg==", 449 | "dev": true, 450 | "funding": { 451 | "url": "https://github.com/sponsors/ayuhito" 452 | } 453 | }, 454 | "node_modules/@jridgewell/gen-mapping": { 455 | "version": "0.3.13", 456 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 457 | "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 458 | "dev": true, 459 | "license": "MIT", 460 | "dependencies": { 461 | "@jridgewell/sourcemap-codec": "^1.5.0", 462 | "@jridgewell/trace-mapping": "^0.3.24" 463 | } 464 | }, 465 | "node_modules/@jridgewell/remapping": { 466 | "version": "2.3.5", 467 | "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 468 | "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 469 | "dev": true, 470 | "license": "MIT", 471 | "dependencies": { 472 | "@jridgewell/gen-mapping": "^0.3.5", 473 | "@jridgewell/trace-mapping": "^0.3.24" 474 | } 475 | }, 476 | "node_modules/@jridgewell/resolve-uri": { 477 | "version": "3.1.2", 478 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 479 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 480 | "dev": true, 481 | "engines": { 482 | "node": ">=6.0.0" 483 | } 484 | }, 485 | "node_modules/@jridgewell/sourcemap-codec": { 486 | "version": "1.5.0", 487 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 488 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 489 | "dev": true 490 | }, 491 | "node_modules/@jridgewell/trace-mapping": { 492 | "version": "0.3.25", 493 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 494 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 495 | "dev": true, 496 | "dependencies": { 497 | "@jridgewell/resolve-uri": "^3.1.0", 498 | "@jridgewell/sourcemap-codec": "^1.4.14" 499 | } 500 | }, 501 | "node_modules/@neoconfetti/svelte": { 502 | "version": "2.2.1", 503 | "resolved": "https://registry.npmjs.org/@neoconfetti/svelte/-/svelte-2.2.1.tgz", 504 | "integrity": "sha512-2Ts0Rxaf6clW2qG+AKhTwpl01AAyZEAe03XeuQ7Vp+qYIaM3ycuI94j546fmEkUxwACwGI3Zl2cZ/pncTuNcJg==", 505 | "dev": true 506 | }, 507 | "node_modules/@playwright/test": { 508 | "version": "1.57.0", 509 | "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", 510 | "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", 511 | "dev": true, 512 | "license": "Apache-2.0", 513 | "dependencies": { 514 | "playwright": "1.57.0" 515 | }, 516 | "bin": { 517 | "playwright": "cli.js" 518 | }, 519 | "engines": { 520 | "node": ">=18" 521 | } 522 | }, 523 | "node_modules/@polka/url": { 524 | "version": "1.0.0-next.28", 525 | "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", 526 | "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", 527 | "dev": true, 528 | "license": "MIT" 529 | }, 530 | "node_modules/@rollup/rollup-android-arm-eabi": { 531 | "version": "4.53.3", 532 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", 533 | "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", 534 | "cpu": [ 535 | "arm" 536 | ], 537 | "dev": true, 538 | "license": "MIT", 539 | "optional": true, 540 | "os": [ 541 | "android" 542 | ] 543 | }, 544 | "node_modules/@rollup/rollup-android-arm64": { 545 | "version": "4.53.3", 546 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", 547 | "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", 548 | "cpu": [ 549 | "arm64" 550 | ], 551 | "dev": true, 552 | "license": "MIT", 553 | "optional": true, 554 | "os": [ 555 | "android" 556 | ] 557 | }, 558 | "node_modules/@rollup/rollup-darwin-arm64": { 559 | "version": "4.53.3", 560 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", 561 | "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", 562 | "cpu": [ 563 | "arm64" 564 | ], 565 | "dev": true, 566 | "license": "MIT", 567 | "optional": true, 568 | "os": [ 569 | "darwin" 570 | ] 571 | }, 572 | "node_modules/@rollup/rollup-darwin-x64": { 573 | "version": "4.53.3", 574 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", 575 | "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", 576 | "cpu": [ 577 | "x64" 578 | ], 579 | "dev": true, 580 | "license": "MIT", 581 | "optional": true, 582 | "os": [ 583 | "darwin" 584 | ] 585 | }, 586 | "node_modules/@rollup/rollup-freebsd-arm64": { 587 | "version": "4.53.3", 588 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", 589 | "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", 590 | "cpu": [ 591 | "arm64" 592 | ], 593 | "dev": true, 594 | "license": "MIT", 595 | "optional": true, 596 | "os": [ 597 | "freebsd" 598 | ] 599 | }, 600 | "node_modules/@rollup/rollup-freebsd-x64": { 601 | "version": "4.53.3", 602 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", 603 | "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", 604 | "cpu": [ 605 | "x64" 606 | ], 607 | "dev": true, 608 | "license": "MIT", 609 | "optional": true, 610 | "os": [ 611 | "freebsd" 612 | ] 613 | }, 614 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 615 | "version": "4.53.3", 616 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", 617 | "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", 618 | "cpu": [ 619 | "arm" 620 | ], 621 | "dev": true, 622 | "license": "MIT", 623 | "optional": true, 624 | "os": [ 625 | "linux" 626 | ] 627 | }, 628 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 629 | "version": "4.53.3", 630 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", 631 | "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", 632 | "cpu": [ 633 | "arm" 634 | ], 635 | "dev": true, 636 | "license": "MIT", 637 | "optional": true, 638 | "os": [ 639 | "linux" 640 | ] 641 | }, 642 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 643 | "version": "4.53.3", 644 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", 645 | "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", 646 | "cpu": [ 647 | "arm64" 648 | ], 649 | "dev": true, 650 | "license": "MIT", 651 | "optional": true, 652 | "os": [ 653 | "linux" 654 | ] 655 | }, 656 | "node_modules/@rollup/rollup-linux-arm64-musl": { 657 | "version": "4.53.3", 658 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", 659 | "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", 660 | "cpu": [ 661 | "arm64" 662 | ], 663 | "dev": true, 664 | "license": "MIT", 665 | "optional": true, 666 | "os": [ 667 | "linux" 668 | ] 669 | }, 670 | "node_modules/@rollup/rollup-linux-loong64-gnu": { 671 | "version": "4.53.3", 672 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", 673 | "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", 674 | "cpu": [ 675 | "loong64" 676 | ], 677 | "dev": true, 678 | "license": "MIT", 679 | "optional": true, 680 | "os": [ 681 | "linux" 682 | ] 683 | }, 684 | "node_modules/@rollup/rollup-linux-ppc64-gnu": { 685 | "version": "4.53.3", 686 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", 687 | "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", 688 | "cpu": [ 689 | "ppc64" 690 | ], 691 | "dev": true, 692 | "license": "MIT", 693 | "optional": true, 694 | "os": [ 695 | "linux" 696 | ] 697 | }, 698 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 699 | "version": "4.53.3", 700 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", 701 | "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", 702 | "cpu": [ 703 | "riscv64" 704 | ], 705 | "dev": true, 706 | "license": "MIT", 707 | "optional": true, 708 | "os": [ 709 | "linux" 710 | ] 711 | }, 712 | "node_modules/@rollup/rollup-linux-riscv64-musl": { 713 | "version": "4.53.3", 714 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", 715 | "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", 716 | "cpu": [ 717 | "riscv64" 718 | ], 719 | "dev": true, 720 | "license": "MIT", 721 | "optional": true, 722 | "os": [ 723 | "linux" 724 | ] 725 | }, 726 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 727 | "version": "4.53.3", 728 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", 729 | "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", 730 | "cpu": [ 731 | "s390x" 732 | ], 733 | "dev": true, 734 | "license": "MIT", 735 | "optional": true, 736 | "os": [ 737 | "linux" 738 | ] 739 | }, 740 | "node_modules/@rollup/rollup-linux-x64-gnu": { 741 | "version": "4.53.3", 742 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", 743 | "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", 744 | "cpu": [ 745 | "x64" 746 | ], 747 | "dev": true, 748 | "license": "MIT", 749 | "optional": true, 750 | "os": [ 751 | "linux" 752 | ] 753 | }, 754 | "node_modules/@rollup/rollup-linux-x64-musl": { 755 | "version": "4.53.3", 756 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", 757 | "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", 758 | "cpu": [ 759 | "x64" 760 | ], 761 | "dev": true, 762 | "license": "MIT", 763 | "optional": true, 764 | "os": [ 765 | "linux" 766 | ] 767 | }, 768 | "node_modules/@rollup/rollup-openharmony-arm64": { 769 | "version": "4.53.3", 770 | "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", 771 | "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", 772 | "cpu": [ 773 | "arm64" 774 | ], 775 | "dev": true, 776 | "license": "MIT", 777 | "optional": true, 778 | "os": [ 779 | "openharmony" 780 | ] 781 | }, 782 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 783 | "version": "4.53.3", 784 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", 785 | "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", 786 | "cpu": [ 787 | "arm64" 788 | ], 789 | "dev": true, 790 | "license": "MIT", 791 | "optional": true, 792 | "os": [ 793 | "win32" 794 | ] 795 | }, 796 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 797 | "version": "4.53.3", 798 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", 799 | "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", 800 | "cpu": [ 801 | "ia32" 802 | ], 803 | "dev": true, 804 | "license": "MIT", 805 | "optional": true, 806 | "os": [ 807 | "win32" 808 | ] 809 | }, 810 | "node_modules/@rollup/rollup-win32-x64-gnu": { 811 | "version": "4.53.3", 812 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", 813 | "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", 814 | "cpu": [ 815 | "x64" 816 | ], 817 | "dev": true, 818 | "license": "MIT", 819 | "optional": true, 820 | "os": [ 821 | "win32" 822 | ] 823 | }, 824 | "node_modules/@rollup/rollup-win32-x64-msvc": { 825 | "version": "4.53.3", 826 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", 827 | "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", 828 | "cpu": [ 829 | "x64" 830 | ], 831 | "dev": true, 832 | "license": "MIT", 833 | "optional": true, 834 | "os": [ 835 | "win32" 836 | ] 837 | }, 838 | "node_modules/@standard-schema/spec": { 839 | "version": "1.0.0", 840 | "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", 841 | "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", 842 | "dev": true, 843 | "license": "MIT" 844 | }, 845 | "node_modules/@sveltejs/acorn-typescript": { 846 | "version": "1.0.7", 847 | "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.7.tgz", 848 | "integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==", 849 | "dev": true, 850 | "license": "MIT", 851 | "peerDependencies": { 852 | "acorn": "^8.9.0" 853 | } 854 | }, 855 | "node_modules/@sveltejs/adapter-auto": { 856 | "version": "4.0.0", 857 | "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-4.0.0.tgz", 858 | "integrity": "sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ==", 859 | "dev": true, 860 | "dependencies": { 861 | "import-meta-resolve": "^4.1.0" 862 | }, 863 | "peerDependencies": { 864 | "@sveltejs/kit": "^2.0.0" 865 | } 866 | }, 867 | "node_modules/@sveltejs/kit": { 868 | "version": "2.49.0", 869 | "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.0.tgz", 870 | "integrity": "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==", 871 | "dev": true, 872 | "license": "MIT", 873 | "dependencies": { 874 | "@standard-schema/spec": "^1.0.0", 875 | "@sveltejs/acorn-typescript": "^1.0.5", 876 | "@types/cookie": "^0.6.0", 877 | "acorn": "^8.14.1", 878 | "cookie": "^0.6.0", 879 | "devalue": "^5.3.2", 880 | "esm-env": "^1.2.2", 881 | "kleur": "^4.1.5", 882 | "magic-string": "^0.30.5", 883 | "mrmime": "^2.0.0", 884 | "sade": "^1.8.1", 885 | "set-cookie-parser": "^2.6.0", 886 | "sirv": "^3.0.0" 887 | }, 888 | "bin": { 889 | "svelte-kit": "svelte-kit.js" 890 | }, 891 | "engines": { 892 | "node": ">=18.13" 893 | }, 894 | "peerDependencies": { 895 | "@opentelemetry/api": "^1.0.0", 896 | "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", 897 | "svelte": "^4.0.0 || ^5.0.0-next.0", 898 | "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" 899 | }, 900 | "peerDependenciesMeta": { 901 | "@opentelemetry/api": { 902 | "optional": true 903 | } 904 | } 905 | }, 906 | "node_modules/@sveltejs/vite-plugin-svelte": { 907 | "version": "6.2.1", 908 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", 909 | "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", 910 | "dev": true, 911 | "license": "MIT", 912 | "dependencies": { 913 | "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", 914 | "debug": "^4.4.1", 915 | "deepmerge": "^4.3.1", 916 | "magic-string": "^0.30.17", 917 | "vitefu": "^1.1.1" 918 | }, 919 | "engines": { 920 | "node": "^20.19 || ^22.12 || >=24" 921 | }, 922 | "peerDependencies": { 923 | "svelte": "^5.0.0", 924 | "vite": "^6.3.0 || ^7.0.0" 925 | } 926 | }, 927 | "node_modules/@sveltejs/vite-plugin-svelte-inspector": { 928 | "version": "5.0.1", 929 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", 930 | "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", 931 | "dev": true, 932 | "license": "MIT", 933 | "dependencies": { 934 | "debug": "^4.4.1" 935 | }, 936 | "engines": { 937 | "node": "^20.19 || ^22.12 || >=24" 938 | }, 939 | "peerDependencies": { 940 | "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", 941 | "svelte": "^5.0.0", 942 | "vite": "^6.3.0 || ^7.0.0" 943 | } 944 | }, 945 | "node_modules/@types/cookie": { 946 | "version": "0.6.0", 947 | "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", 948 | "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", 949 | "dev": true 950 | }, 951 | "node_modules/@types/estree": { 952 | "version": "1.0.8", 953 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 954 | "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 955 | "dev": true, 956 | "license": "MIT" 957 | }, 958 | "node_modules/acorn": { 959 | "version": "8.15.0", 960 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 961 | "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 962 | "dev": true, 963 | "license": "MIT", 964 | "bin": { 965 | "acorn": "bin/acorn" 966 | }, 967 | "engines": { 968 | "node": ">=0.4.0" 969 | } 970 | }, 971 | "node_modules/aria-query": { 972 | "version": "5.3.2", 973 | "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", 974 | "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", 975 | "dev": true, 976 | "engines": { 977 | "node": ">= 0.4" 978 | } 979 | }, 980 | "node_modules/axobject-query": { 981 | "version": "4.1.0", 982 | "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", 983 | "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", 984 | "dev": true, 985 | "license": "Apache-2.0", 986 | "engines": { 987 | "node": ">= 0.4" 988 | } 989 | }, 990 | "node_modules/chokidar": { 991 | "version": "4.0.3", 992 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", 993 | "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", 994 | "dev": true, 995 | "dependencies": { 996 | "readdirp": "^4.0.1" 997 | }, 998 | "engines": { 999 | "node": ">= 14.16.0" 1000 | }, 1001 | "funding": { 1002 | "url": "https://paulmillr.com/funding/" 1003 | } 1004 | }, 1005 | "node_modules/clsx": { 1006 | "version": "2.1.1", 1007 | "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 1008 | "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 1009 | "dev": true, 1010 | "engines": { 1011 | "node": ">=6" 1012 | } 1013 | }, 1014 | "node_modules/cookie": { 1015 | "version": "0.6.0", 1016 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 1017 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 1018 | "dev": true, 1019 | "engines": { 1020 | "node": ">= 0.6" 1021 | } 1022 | }, 1023 | "node_modules/debug": { 1024 | "version": "4.4.3", 1025 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 1026 | "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 1027 | "dev": true, 1028 | "license": "MIT", 1029 | "dependencies": { 1030 | "ms": "^2.1.3" 1031 | }, 1032 | "engines": { 1033 | "node": ">=6.0" 1034 | }, 1035 | "peerDependenciesMeta": { 1036 | "supports-color": { 1037 | "optional": true 1038 | } 1039 | } 1040 | }, 1041 | "node_modules/deepmerge": { 1042 | "version": "4.3.1", 1043 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 1044 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 1045 | "dev": true, 1046 | "engines": { 1047 | "node": ">=0.10.0" 1048 | } 1049 | }, 1050 | "node_modules/devalue": { 1051 | "version": "5.5.0", 1052 | "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", 1053 | "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", 1054 | "dev": true, 1055 | "license": "MIT" 1056 | }, 1057 | "node_modules/esbuild": { 1058 | "version": "0.25.0", 1059 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", 1060 | "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", 1061 | "dev": true, 1062 | "hasInstallScript": true, 1063 | "bin": { 1064 | "esbuild": "bin/esbuild" 1065 | }, 1066 | "engines": { 1067 | "node": ">=18" 1068 | }, 1069 | "optionalDependencies": { 1070 | "@esbuild/aix-ppc64": "0.25.0", 1071 | "@esbuild/android-arm": "0.25.0", 1072 | "@esbuild/android-arm64": "0.25.0", 1073 | "@esbuild/android-x64": "0.25.0", 1074 | "@esbuild/darwin-arm64": "0.25.0", 1075 | "@esbuild/darwin-x64": "0.25.0", 1076 | "@esbuild/freebsd-arm64": "0.25.0", 1077 | "@esbuild/freebsd-x64": "0.25.0", 1078 | "@esbuild/linux-arm": "0.25.0", 1079 | "@esbuild/linux-arm64": "0.25.0", 1080 | "@esbuild/linux-ia32": "0.25.0", 1081 | "@esbuild/linux-loong64": "0.25.0", 1082 | "@esbuild/linux-mips64el": "0.25.0", 1083 | "@esbuild/linux-ppc64": "0.25.0", 1084 | "@esbuild/linux-riscv64": "0.25.0", 1085 | "@esbuild/linux-s390x": "0.25.0", 1086 | "@esbuild/linux-x64": "0.25.0", 1087 | "@esbuild/netbsd-arm64": "0.25.0", 1088 | "@esbuild/netbsd-x64": "0.25.0", 1089 | "@esbuild/openbsd-arm64": "0.25.0", 1090 | "@esbuild/openbsd-x64": "0.25.0", 1091 | "@esbuild/sunos-x64": "0.25.0", 1092 | "@esbuild/win32-arm64": "0.25.0", 1093 | "@esbuild/win32-ia32": "0.25.0", 1094 | "@esbuild/win32-x64": "0.25.0" 1095 | } 1096 | }, 1097 | "node_modules/esm-env": { 1098 | "version": "1.2.2", 1099 | "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 1100 | "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 1101 | "dev": true, 1102 | "license": "MIT" 1103 | }, 1104 | "node_modules/esrap": { 1105 | "version": "2.2.0", 1106 | "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.0.tgz", 1107 | "integrity": "sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==", 1108 | "dev": true, 1109 | "license": "MIT", 1110 | "dependencies": { 1111 | "@jridgewell/sourcemap-codec": "^1.4.15" 1112 | } 1113 | }, 1114 | "node_modules/fdir": { 1115 | "version": "6.5.0", 1116 | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 1117 | "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 1118 | "dev": true, 1119 | "license": "MIT", 1120 | "engines": { 1121 | "node": ">=12.0.0" 1122 | }, 1123 | "peerDependencies": { 1124 | "picomatch": "^3 || ^4" 1125 | }, 1126 | "peerDependenciesMeta": { 1127 | "picomatch": { 1128 | "optional": true 1129 | } 1130 | } 1131 | }, 1132 | "node_modules/fsevents": { 1133 | "version": "2.3.2", 1134 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 1135 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 1136 | "dev": true, 1137 | "hasInstallScript": true, 1138 | "optional": true, 1139 | "os": [ 1140 | "darwin" 1141 | ], 1142 | "engines": { 1143 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1144 | } 1145 | }, 1146 | "node_modules/import-meta-resolve": { 1147 | "version": "4.1.0", 1148 | "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", 1149 | "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", 1150 | "dev": true, 1151 | "license": "MIT", 1152 | "funding": { 1153 | "type": "github", 1154 | "url": "https://github.com/sponsors/wooorm" 1155 | } 1156 | }, 1157 | "node_modules/is-reference": { 1158 | "version": "3.0.3", 1159 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", 1160 | "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", 1161 | "dev": true, 1162 | "dependencies": { 1163 | "@types/estree": "^1.0.6" 1164 | } 1165 | }, 1166 | "node_modules/kleur": { 1167 | "version": "4.1.5", 1168 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1169 | "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1170 | "dev": true, 1171 | "engines": { 1172 | "node": ">=6" 1173 | } 1174 | }, 1175 | "node_modules/locate-character": { 1176 | "version": "3.0.0", 1177 | "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 1178 | "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", 1179 | "dev": true 1180 | }, 1181 | "node_modules/magic-string": { 1182 | "version": "0.30.17", 1183 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 1184 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1185 | "dev": true, 1186 | "dependencies": { 1187 | "@jridgewell/sourcemap-codec": "^1.5.0" 1188 | } 1189 | }, 1190 | "node_modules/mri": { 1191 | "version": "1.2.0", 1192 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", 1193 | "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", 1194 | "dev": true, 1195 | "engines": { 1196 | "node": ">=4" 1197 | } 1198 | }, 1199 | "node_modules/mrmime": { 1200 | "version": "2.0.1", 1201 | "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", 1202 | "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", 1203 | "dev": true, 1204 | "license": "MIT", 1205 | "engines": { 1206 | "node": ">=10" 1207 | } 1208 | }, 1209 | "node_modules/ms": { 1210 | "version": "2.1.3", 1211 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1212 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1213 | "dev": true, 1214 | "license": "MIT" 1215 | }, 1216 | "node_modules/nanoid": { 1217 | "version": "3.3.11", 1218 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 1219 | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1220 | "dev": true, 1221 | "funding": [ 1222 | { 1223 | "type": "github", 1224 | "url": "https://github.com/sponsors/ai" 1225 | } 1226 | ], 1227 | "license": "MIT", 1228 | "bin": { 1229 | "nanoid": "bin/nanoid.cjs" 1230 | }, 1231 | "engines": { 1232 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1233 | } 1234 | }, 1235 | "node_modules/picocolors": { 1236 | "version": "1.1.1", 1237 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1238 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1239 | "dev": true 1240 | }, 1241 | "node_modules/picomatch": { 1242 | "version": "4.0.3", 1243 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 1244 | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 1245 | "dev": true, 1246 | "license": "MIT", 1247 | "engines": { 1248 | "node": ">=12" 1249 | }, 1250 | "funding": { 1251 | "url": "https://github.com/sponsors/jonschlinkert" 1252 | } 1253 | }, 1254 | "node_modules/playwright": { 1255 | "version": "1.57.0", 1256 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", 1257 | "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", 1258 | "dev": true, 1259 | "license": "Apache-2.0", 1260 | "dependencies": { 1261 | "playwright-core": "1.57.0" 1262 | }, 1263 | "bin": { 1264 | "playwright": "cli.js" 1265 | }, 1266 | "engines": { 1267 | "node": ">=18" 1268 | }, 1269 | "optionalDependencies": { 1270 | "fsevents": "2.3.2" 1271 | } 1272 | }, 1273 | "node_modules/playwright-core": { 1274 | "version": "1.57.0", 1275 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", 1276 | "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", 1277 | "dev": true, 1278 | "license": "Apache-2.0", 1279 | "bin": { 1280 | "playwright-core": "cli.js" 1281 | }, 1282 | "engines": { 1283 | "node": ">=18" 1284 | } 1285 | }, 1286 | "node_modules/postcss": { 1287 | "version": "8.5.6", 1288 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 1289 | "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 1290 | "dev": true, 1291 | "funding": [ 1292 | { 1293 | "type": "opencollective", 1294 | "url": "https://opencollective.com/postcss/" 1295 | }, 1296 | { 1297 | "type": "tidelift", 1298 | "url": "https://tidelift.com/funding/github/npm/postcss" 1299 | }, 1300 | { 1301 | "type": "github", 1302 | "url": "https://github.com/sponsors/ai" 1303 | } 1304 | ], 1305 | "license": "MIT", 1306 | "dependencies": { 1307 | "nanoid": "^3.3.11", 1308 | "picocolors": "^1.1.1", 1309 | "source-map-js": "^1.2.1" 1310 | }, 1311 | "engines": { 1312 | "node": "^10 || ^12 || >=14" 1313 | } 1314 | }, 1315 | "node_modules/readdirp": { 1316 | "version": "4.1.2", 1317 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", 1318 | "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", 1319 | "dev": true, 1320 | "engines": { 1321 | "node": ">= 14.18.0" 1322 | }, 1323 | "funding": { 1324 | "type": "individual", 1325 | "url": "https://paulmillr.com/funding/" 1326 | } 1327 | }, 1328 | "node_modules/rollup": { 1329 | "version": "4.53.3", 1330 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", 1331 | "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", 1332 | "dev": true, 1333 | "license": "MIT", 1334 | "dependencies": { 1335 | "@types/estree": "1.0.8" 1336 | }, 1337 | "bin": { 1338 | "rollup": "dist/bin/rollup" 1339 | }, 1340 | "engines": { 1341 | "node": ">=18.0.0", 1342 | "npm": ">=8.0.0" 1343 | }, 1344 | "optionalDependencies": { 1345 | "@rollup/rollup-android-arm-eabi": "4.53.3", 1346 | "@rollup/rollup-android-arm64": "4.53.3", 1347 | "@rollup/rollup-darwin-arm64": "4.53.3", 1348 | "@rollup/rollup-darwin-x64": "4.53.3", 1349 | "@rollup/rollup-freebsd-arm64": "4.53.3", 1350 | "@rollup/rollup-freebsd-x64": "4.53.3", 1351 | "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", 1352 | "@rollup/rollup-linux-arm-musleabihf": "4.53.3", 1353 | "@rollup/rollup-linux-arm64-gnu": "4.53.3", 1354 | "@rollup/rollup-linux-arm64-musl": "4.53.3", 1355 | "@rollup/rollup-linux-loong64-gnu": "4.53.3", 1356 | "@rollup/rollup-linux-ppc64-gnu": "4.53.3", 1357 | "@rollup/rollup-linux-riscv64-gnu": "4.53.3", 1358 | "@rollup/rollup-linux-riscv64-musl": "4.53.3", 1359 | "@rollup/rollup-linux-s390x-gnu": "4.53.3", 1360 | "@rollup/rollup-linux-x64-gnu": "4.53.3", 1361 | "@rollup/rollup-linux-x64-musl": "4.53.3", 1362 | "@rollup/rollup-openharmony-arm64": "4.53.3", 1363 | "@rollup/rollup-win32-arm64-msvc": "4.53.3", 1364 | "@rollup/rollup-win32-ia32-msvc": "4.53.3", 1365 | "@rollup/rollup-win32-x64-gnu": "4.53.3", 1366 | "@rollup/rollup-win32-x64-msvc": "4.53.3", 1367 | "fsevents": "~2.3.2" 1368 | } 1369 | }, 1370 | "node_modules/sade": { 1371 | "version": "1.8.1", 1372 | "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", 1373 | "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", 1374 | "dev": true, 1375 | "dependencies": { 1376 | "mri": "^1.1.0" 1377 | }, 1378 | "engines": { 1379 | "node": ">=6" 1380 | } 1381 | }, 1382 | "node_modules/set-cookie-parser": { 1383 | "version": "2.6.0", 1384 | "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", 1385 | "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", 1386 | "dev": true 1387 | }, 1388 | "node_modules/sirv": { 1389 | "version": "3.0.1", 1390 | "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", 1391 | "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", 1392 | "dev": true, 1393 | "license": "MIT", 1394 | "dependencies": { 1395 | "@polka/url": "^1.0.0-next.24", 1396 | "mrmime": "^2.0.0", 1397 | "totalist": "^3.0.0" 1398 | }, 1399 | "engines": { 1400 | "node": ">=18" 1401 | } 1402 | }, 1403 | "node_modules/source-map-js": { 1404 | "version": "1.2.1", 1405 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1406 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1407 | "dev": true, 1408 | "license": "BSD-3-Clause", 1409 | "engines": { 1410 | "node": ">=0.10.0" 1411 | } 1412 | }, 1413 | "node_modules/svelte": { 1414 | "version": "5.45.1", 1415 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.1.tgz", 1416 | "integrity": "sha512-arf+zybnG7sfOnQ0Xj+sld8+RDBnE9Uw2ofD4cNbIwlVSLbxfZrkdA9Gd6NfWdC101moFP58/l3ZG9dSNfrrEQ==", 1417 | "dev": true, 1418 | "license": "MIT", 1419 | "dependencies": { 1420 | "@jridgewell/remapping": "^2.3.4", 1421 | "@jridgewell/sourcemap-codec": "^1.5.0", 1422 | "@sveltejs/acorn-typescript": "^1.0.5", 1423 | "@types/estree": "^1.0.5", 1424 | "acorn": "^8.12.1", 1425 | "aria-query": "^5.3.1", 1426 | "axobject-query": "^4.1.0", 1427 | "clsx": "^2.1.1", 1428 | "devalue": "^5.5.0", 1429 | "esm-env": "^1.2.1", 1430 | "esrap": "^2.2.0", 1431 | "is-reference": "^3.0.3", 1432 | "locate-character": "^3.0.0", 1433 | "magic-string": "^0.30.11", 1434 | "zimmerframe": "^1.1.2" 1435 | }, 1436 | "engines": { 1437 | "node": ">=18" 1438 | } 1439 | }, 1440 | "node_modules/svelte-adapter-azure-swa": { 1441 | "resolved": "..", 1442 | "link": true 1443 | }, 1444 | "node_modules/svelte-check": { 1445 | "version": "4.1.4", 1446 | "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.4.tgz", 1447 | "integrity": "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==", 1448 | "dev": true, 1449 | "dependencies": { 1450 | "@jridgewell/trace-mapping": "^0.3.25", 1451 | "chokidar": "^4.0.1", 1452 | "fdir": "^6.2.0", 1453 | "picocolors": "^1.0.0", 1454 | "sade": "^1.7.4" 1455 | }, 1456 | "bin": { 1457 | "svelte-check": "bin/svelte-check" 1458 | }, 1459 | "engines": { 1460 | "node": ">= 18.0.0" 1461 | }, 1462 | "peerDependencies": { 1463 | "svelte": "^4.0.0 || ^5.0.0-next.0", 1464 | "typescript": ">=5.0.0" 1465 | } 1466 | }, 1467 | "node_modules/tinyglobby": { 1468 | "version": "0.2.15", 1469 | "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 1470 | "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 1471 | "dev": true, 1472 | "license": "MIT", 1473 | "dependencies": { 1474 | "fdir": "^6.5.0", 1475 | "picomatch": "^4.0.3" 1476 | }, 1477 | "engines": { 1478 | "node": ">=12.0.0" 1479 | }, 1480 | "funding": { 1481 | "url": "https://github.com/sponsors/SuperchupuDev" 1482 | } 1483 | }, 1484 | "node_modules/totalist": { 1485 | "version": "3.0.1", 1486 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", 1487 | "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", 1488 | "dev": true, 1489 | "license": "MIT", 1490 | "engines": { 1491 | "node": ">=6" 1492 | } 1493 | }, 1494 | "node_modules/typescript": { 1495 | "version": "5.3.3", 1496 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 1497 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 1498 | "dev": true, 1499 | "bin": { 1500 | "tsc": "bin/tsc", 1501 | "tsserver": "bin/tsserver" 1502 | }, 1503 | "engines": { 1504 | "node": ">=14.17" 1505 | } 1506 | }, 1507 | "node_modules/vite": { 1508 | "version": "7.2.4", 1509 | "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", 1510 | "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", 1511 | "dev": true, 1512 | "license": "MIT", 1513 | "dependencies": { 1514 | "esbuild": "^0.25.0", 1515 | "fdir": "^6.5.0", 1516 | "picomatch": "^4.0.3", 1517 | "postcss": "^8.5.6", 1518 | "rollup": "^4.43.0", 1519 | "tinyglobby": "^0.2.15" 1520 | }, 1521 | "bin": { 1522 | "vite": "bin/vite.js" 1523 | }, 1524 | "engines": { 1525 | "node": "^20.19.0 || >=22.12.0" 1526 | }, 1527 | "funding": { 1528 | "url": "https://github.com/vitejs/vite?sponsor=1" 1529 | }, 1530 | "optionalDependencies": { 1531 | "fsevents": "~2.3.3" 1532 | }, 1533 | "peerDependencies": { 1534 | "@types/node": "^20.19.0 || >=22.12.0", 1535 | "jiti": ">=1.21.0", 1536 | "less": "^4.0.0", 1537 | "lightningcss": "^1.21.0", 1538 | "sass": "^1.70.0", 1539 | "sass-embedded": "^1.70.0", 1540 | "stylus": ">=0.54.8", 1541 | "sugarss": "^5.0.0", 1542 | "terser": "^5.16.0", 1543 | "tsx": "^4.8.1", 1544 | "yaml": "^2.4.2" 1545 | }, 1546 | "peerDependenciesMeta": { 1547 | "@types/node": { 1548 | "optional": true 1549 | }, 1550 | "jiti": { 1551 | "optional": true 1552 | }, 1553 | "less": { 1554 | "optional": true 1555 | }, 1556 | "lightningcss": { 1557 | "optional": true 1558 | }, 1559 | "sass": { 1560 | "optional": true 1561 | }, 1562 | "sass-embedded": { 1563 | "optional": true 1564 | }, 1565 | "stylus": { 1566 | "optional": true 1567 | }, 1568 | "sugarss": { 1569 | "optional": true 1570 | }, 1571 | "terser": { 1572 | "optional": true 1573 | }, 1574 | "tsx": { 1575 | "optional": true 1576 | }, 1577 | "yaml": { 1578 | "optional": true 1579 | } 1580 | } 1581 | }, 1582 | "node_modules/vite/node_modules/fsevents": { 1583 | "version": "2.3.3", 1584 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1585 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1586 | "dev": true, 1587 | "hasInstallScript": true, 1588 | "optional": true, 1589 | "os": [ 1590 | "darwin" 1591 | ], 1592 | "engines": { 1593 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1594 | } 1595 | }, 1596 | "node_modules/vitefu": { 1597 | "version": "1.1.1", 1598 | "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", 1599 | "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", 1600 | "dev": true, 1601 | "license": "MIT", 1602 | "workspaces": [ 1603 | "tests/deps/*", 1604 | "tests/projects/*", 1605 | "tests/projects/workspace/packages/*" 1606 | ], 1607 | "peerDependencies": { 1608 | "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" 1609 | }, 1610 | "peerDependenciesMeta": { 1611 | "vite": { 1612 | "optional": true 1613 | } 1614 | } 1615 | }, 1616 | "node_modules/zimmerframe": { 1617 | "version": "1.1.2", 1618 | "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", 1619 | "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", 1620 | "dev": true 1621 | } 1622 | } 1623 | } 1624 | --------------------------------------------------------------------------------