diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d5abae752 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +*node_modules* +**/target +.git +.idea +*.iml +test-output \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 9c96b1a4b..eacd81c10 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -20,6 +20,10 @@ jobs: with: java-version: 22 distribution: temurin + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 # Use preinstalled PostgreSQL - name: Start PostgreSQL on Ubuntu diff --git a/.slugignore b/.slugignore new file mode 100644 index 000000000..b0cd42701 --- /dev/null +++ b/.slugignore @@ -0,0 +1,5 @@ +webapp/frontend/node_modules +webapp/frontend/.svelte-kit +webapp/frontend/README.md +webapp/frontend/components.json +webapp/frontend/eslint.config.json diff --git a/Dockerfile b/Dockerfile index b63f16c3c..693af19a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ FROM eclipse-temurin:22 WORKDIR /srv +ARG SKIP_FRONTEND=false + RUN apt update && apt -y install maven COPY pom.xml pom.xml @@ -17,13 +19,13 @@ RUN mvn dependency:go-offline COPY . . -ENV SPRING_DATASOURCE_URL jdbc:postgresql://mindcode-db/mindcode_development +ENV SPRING_DATASOURCE_URL jdbc:postgresql://mindcode-db:5432/mindcode_development ENV SPRING_DATASOURCE_USERNAME postgres ENV SPRING_DATASOURCE_PASSWORD pg_password # Skip tests because postgres is only available at runtime -RUN mvn install -Dmaven.test.skip +RUN mvn clean package -Dmaven.test.skip -DskipFrontend=${SKIP_FRONTEND} EXPOSE 8080 -CMD java -classpath `find webapp -type f -name '*.jar' | tr '\n' ':'` info.teksol.mindcode.webapp.WebappApplication +CMD java -jar webapp/target/mindcode-webapp.jar diff --git a/Procfile b/Procfile index 4a40e8ff0..a9cdaafe7 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: java -Dserver.port=${PORT} -classpath $( find webapp -type f -name '*.jar' | tr '\n' ':' ) info.teksol.mindcode.webapp.WebappApplication +web: java -Dserver.port=${PORT} -jar webapp/target/mindcode-webapp.jar diff --git a/docker-compose.yaml b/docker-compose.yaml index b4174ec7c..e63750d55 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,4 @@ services: - mindcode-db: image: postgres:17 restart: on-failure @@ -11,16 +10,26 @@ services: - mindcode-postgres-data:/var/lib/postgresql/data ports: - "5432:5432" + networks: + - mindcode-net mindcode-web: build: context: . + args: + SKIP_FRONTEND: ${SKIP_FRONTEND:-false} depends_on: - mindcode-db ports: - "127.0.0.1:8080:8080" + networks: + - mindcode-net volumes: mindcode-postgres-data: driver: local + +networks: + mindcode-net: + driver: bridge diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 000000000..47736bb96 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,5 @@ +Contains the samples used by the webapp. + +The json files contain additional information about the samples, such as +whether they should be executed by default and the order in which they should +appear to the user. \ No newline at end of file diff --git a/samples/src/main/java/info/teksol/mindcode/samples/Samples.java b/samples/src/main/java/info/teksol/mindcode/samples/Samples.java index 53dea3634..a4768cdc8 100644 --- a/samples/src/main/java/info/teksol/mindcode/samples/Samples.java +++ b/samples/src/main/java/info/teksol/mindcode/samples/Samples.java @@ -18,6 +18,7 @@ public static Map loadMindcodeSamples() { "relaxed:many-thorium", "heal-damaged-building", "mining-drone", + "item-transport", "upgrade-conveyors", "run:sum-of-primes" ); diff --git a/samples/src/main/resources/samples/mindcode.json b/samples/src/main/resources/samples/mindcode.json new file mode 100644 index 000000000..f4d649729 --- /dev/null +++ b/samples/src/main/resources/samples/mindcode.json @@ -0,0 +1,42 @@ +[ + { + "filename": "control-multiple-units.mnd", + "title": "Controlling units", + "runnable": false + }, + { + "filename": "one-thorium.mnd", + "title": "Controlling a block", + "runnable": false + }, + { + "filename": "many-thorium.mnd", + "title": "Controlling many blocks", + "runnable": false + }, + { + "filename": "heal-damaged-building.mnd", + "title": "Healing buildings", + "runnable": false + }, + { + "filename": "mining-drone.mnd", + "title": "Mining drone", + "runnable": false + }, + { + "filename": "item-transport.mnd", + "title": "Item Transport", + "runnable": false + }, + { + "filename": "upgrade-conveyors.mnd", + "title": "Upgrade conveyors", + "runnable": false + }, + { + "filename": "sum-of-primes.mnd", + "title": "Sum of primes", + "runnable": true + } +] \ No newline at end of file diff --git a/samples/src/main/resources/samples/schematics.json b/samples/src/main/resources/samples/schematics.json new file mode 100644 index 000000000..90dcae0e6 --- /dev/null +++ b/samples/src/main/resources/samples/schematics.json @@ -0,0 +1,52 @@ +[ + { + "filename": "detector.sdf", + "title": "Property Detector", + "runnable": false + }, + { + "filename": "healing-center.sdf", + "title": "Healing Center", + "runnable": false + }, + { + "filename": "on-off-switch.sdf", + "title": "On/Off Switch", + "runnable": false + }, + { + "filename": "regulator.sdf", + "title": "Regulator", + "runnable": false + }, + { + "filename": "overdrive-dome-supply.sdf", + "title": "Overdrive Dome Supply", + "runnable": false + }, + { + "filename": "worker-recall-station.sdf", + "title": "Unit Recall Station", + "runnable": false + }, + { + "filename": "scrap-to-metaglass-2.sdf", + "title": "Scrap to Metaglass", + "runnable": false + }, + { + "filename": "item-transport.sdf", + "title": "Item Transport", + "runnable": false + }, + { + "filename": "payload-hub.sdf", + "title": "Payload Hub", + "runnable": false + }, + { + "filename": "mandelbrot-generator.sdf", + "title": "Mandelbrot Generator", + "runnable": false + } +] \ No newline at end of file diff --git a/webapp/.gitignore b/webapp/.gitignore index 549e00a2a..ab36447a7 100644 --- a/webapp/.gitignore +++ b/webapp/.gitignore @@ -31,3 +31,5 @@ build/ ### VS Code ### .vscode/ + +src/main/resources/static diff --git a/webapp/README.md b/webapp/README.md new file mode 100644 index 000000000..a91b4b649 --- /dev/null +++ b/webapp/README.md @@ -0,0 +1,34 @@ +# Mindcode webapp + +The frontend for mindcode is built using [svelte kit](https://svelte.dev/docs/kit/introduction). + +## Development (backend) + +Look at the [contribution guide](../CONTRIBUTING.markdown). + +As you will see in [deployment](#deployment), building the backend will also build the frontend, so you don't need to setup node if you only want to edit backend code. + +To prevent the frontend from being built unecessarily, you may use `-DskipFrontend=true` on the maven command, or set the `SKIP_FRONTEND` environment variable when running the backend through docker. + +## Development (frontend) + +If you need to make changes to the frontend, run + +```sh +cd webapp/frontend +npm run dev +``` + +This will open a development server on `http://localhost:5173`, which provides hot reload for the pages, and proxies request to `/api/**` to `http://localhost:8080/api/**` (the url where the mindcode webapp server runs during development, the mindcode server needs to be run separatedly). + +## Deployment + +When building the webapp, [`frontend-maven-plugin`](https://github.com/eirslett/frontend-maven-plugin) will be invoked to build the frontend. + +`webapp/frontend/svelte.config.js` contains the frontend configuration, it is configured so that: +- The frontend is built as a collection of html pages with client-side routing +- The build output is placed on `webapp/src/main/resources/static` + +> [!WARNING] +> If the `HEROKU` environment variable is present, most folders and files in the project will be deleted, +> preserving only `webapp/target/mindcode-webapp.jar` and a few configuration files. \ No newline at end of file diff --git a/webapp/frontend/.gitignore b/webapp/frontend/.gitignore new file mode 100644 index 000000000..3b462cb0c --- /dev/null +++ b/webapp/frontend/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/webapp/frontend/.npmrc b/webapp/frontend/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/webapp/frontend/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/webapp/frontend/.prettierignore b/webapp/frontend/.prettierignore new file mode 100644 index 000000000..7d74fe246 --- /dev/null +++ b/webapp/frontend/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/webapp/frontend/.prettierrc b/webapp/frontend/.prettierrc new file mode 100644 index 000000000..819fa5766 --- /dev/null +++ b/webapp/frontend/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/routes/layout.css" +} diff --git a/webapp/frontend/components.json b/webapp/frontend/components.json new file mode 100644 index 000000000..e786169db --- /dev/null +++ b/webapp/frontend/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src\\routes\\layout.css", + "baseColor": "neutral" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} diff --git a/webapp/frontend/eslint.config.js b/webapp/frontend/eslint.config.js new file mode 100644 index 000000000..7df665dd9 --- /dev/null +++ b/webapp/frontend/eslint.config.js @@ -0,0 +1,41 @@ +import prettier from 'eslint-config-prettier'; +import { fileURLToPath } from 'node:url'; +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { globals: { ...globals.browser, ...globals.node } }, + + rules: { + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off' + } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/webapp/frontend/package-lock.json b/webapp/frontend/package-lock.json new file mode 100644 index 000000000..aa2f9ff2c --- /dev/null +++ b/webapp/frontend/package-lock.json @@ -0,0 +1,4972 @@ +{ + "name": "frontend", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.1", + "devDependencies": { + "@codemirror/autocomplete": "^6.20.0", + "@codemirror/commands": "^6.10.2", + "@codemirror/language": "^6.12.1", + "@codemirror/lint": "^6.9.2", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.11", + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.39.1", + "@fsegurai/codemirror-theme-forest": "^6.2.3", + "@fsegurai/codemirror-theme-vscode-light": "^6.2.4", + "@internationalized/date": "^3.10.1", + "@lezer/common": "^1.5.0", + "@lezer/generator": "^1.8.0", + "@lezer/lr": "^1.4.8", + "@lucide/svelte": "^0.577.0", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.49.1", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/vite": "^4.1.17", + "@types/node": "~22.19.13", + "bits-ui": "^2.15.4", + "clsx": "^2.1.1", + "codemirror": "^6.0.2", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.13.1", + "globals": "^16.5.0", + "mdsvex": "^0.12.6", + "prettier": "^3.7.4", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "svelte": "^5.45.6", + "svelte-check": "^4.3.4", + "svelte-sonner": "^1.0.7", + "tailwind-merge": "^3.4.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.1.17", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.48.1", + "vaul-svelte": "^1.0.0-next.7", + "vite": "^7.2.6", + "vitest": "^4.0.18" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", + "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", + "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.39.16", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.16.tgz", + "integrity": "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz", + "integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.40 || 9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@fsegurai/codemirror-theme-forest": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/@fsegurai/codemirror-theme-forest/-/codemirror-theme-forest-6.2.3.tgz", + "integrity": "sha512-cn+GMBkHtp70qLEHfvjXyR1nWdA1KDeC/hJOWWv/FmpUrFfq61oM2GWZL+yqDINrTO709lfcDu+gcAjnpOtfSg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@fsegurai/codemirror-theme-vscode-light": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@fsegurai/codemirror-theme-vscode-light/-/codemirror-theme-vscode-light-6.2.4.tgz", + "integrity": "sha512-to840rPueVX6UV4YLEymGhUzzyuunY15LfGgm4Vq6hY2NmCfwYhQ/KfTcJ3XrZXiE80MrHjOq1FDcXl17UgvIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.0.tgz", + "integrity": "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/generator": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.8.0.tgz", + "integrity": "sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.1.0", + "@lezer/lr": "^1.3.0" + }, + "bin": { + "lezer-generator": "src/lezer-generator.cjs" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lucide/svelte": { + "version": "0.577.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.577.0.tgz", + "integrity": "sha512-0P6mkySd2MapIEgq08tADPmcN4DHndC/02PWwaLkOerXlx5Sv9aT4BxyXLIY+eccr0g/nEyCYiJesqS61YdBZQ==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.3.tgz", + "integrity": "sha512-qyX8+93kK/7R5BEXPC2PjUt0+fS/VO2BVHjEHyIEWiYn88rcRBHmdLgoJjktBltgAf+NY7RfCGB1SoyKS/p9kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.3.tgz", + "integrity": "sha512-6sHrL42bjt5dHQzJ12Q4vMKfN+kUnZ0atHHnv4V0Wd9JMTk7FDzSY35+7qbz3ypQYMBPANbpGK7JpnWNnhGt8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.3.tgz", + "integrity": "sha512-1ht2SpGIjEl2igJ9AbNpPIKzb1B5goXOcmtD0RFxnwNuMxqkR6AUaaErZz+4o+FKmzxcSNBOLrzsICZVNYa1Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.3.tgz", + "integrity": "sha512-FYZ4iVunXxtT+CZqQoPVwPhH7549e/Gy7PIRRtq4t5f/vt54pX6eG9ebttRH6QSH7r/zxAFA4EZGlQ0h0FvXiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.3.tgz", + "integrity": "sha512-M/mwDCJ4wLsIgyxv2Lj7Len+UMHd4zAXu4GQ2UaCdksStglWhP61U3uowkaYBQBhVoNpwx5Hputo8eSqM7K82Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.3.tgz", + "integrity": "sha512-5jZT2c7jBCrMegKYTYTpni8mg8y3uY8gzeq2ndFOANwNuC/xJbVAoGKR9LhMDA0H3nIhvaqUoBEuJoICBudFrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.3.tgz", + "integrity": "sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.3.tgz", + "integrity": "sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.3.tgz", + "integrity": "sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.3.tgz", + "integrity": "sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.3.tgz", + "integrity": "sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.3.tgz", + "integrity": "sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.3.tgz", + "integrity": "sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.3.tgz", + "integrity": "sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.3.tgz", + "integrity": "sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.3.tgz", + "integrity": "sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.3.tgz", + "integrity": "sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.3.tgz", + "integrity": "sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.3.tgz", + "integrity": "sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.3.tgz", + "integrity": "sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.3.tgz", + "integrity": "sha512-vo54aXwjpTtsAnb3ca7Yxs9t2INZg7QdXN/7yaoG7nPGbOBXYXQY41Km+S1Ov26vzOAzLcAjmMdjyEqS1JkVhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.3.tgz", + "integrity": "sha512-HI+PIVZ+m+9AgpnY3pt6rinUdRYrGHvmVdsNQ4odNqQ/eRF78DVpMR7mOq7nW06QxpczibwBmeQzB68wJ+4W4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.3.tgz", + "integrity": "sha512-vRByotbdMo3Wdi+8oC2nVxtc3RkkFKrGaok+a62AT8lz/YBuQjaVYAS5Zcs3tPzW43Vsf9J0wehJbUY5xRSekA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.3.tgz", + "integrity": "sha512-POZHq7UeuzMJljC5NjKi8vKMFN6/5EOqcX1yGntNLp7rUTpBAXQ1hW8kWPFxYLv07QMcNM75xqVLGPWQq6TKFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.3.tgz", + "integrity": "sha512-aPFONczE4fUFKNXszdvnd2GqKEYQdV5oEsIbKPujJmWlCI9zEsv1Otig8RKK+X9bed9gFUN6LAeN4ZcNuu4zjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", + "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.53.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.53.4.tgz", + "integrity": "sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.6.3", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "obug": "^2.1.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "22.19.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", + "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bits-ui": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.16.3.tgz", + "integrity": "sha512-5hJ5dEhf5yPzkRFcxzgQHScGodeo0gK0MUUXrdLlRHWaBOBGZiacWLG96j/wwFatKwZvouw7q+sn14i0fx3RIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/dom": "^1.7.1", + "esm-env": "^1.1.2", + "runed": "^0.35.1", + "svelte-toolbelt": "^0.10.6", + "tabbable": "^6.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "@internationalized/date": "^3.8.1", + "svelte": "^5.33.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", + "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.0.tgz", + "integrity": "sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "esutils": "^2.0.3", + "globals": "^16.0.0", + "known-css-properties": "^0.37.0", + "postcss": "^8.4.49", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.2.tgz", + "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdsvex": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.12.6.tgz", + "integrity": "sha512-pupx2gzWh3hDtm/iDW4WuCpljmyHbHi34r7ktOqpPGvyiM4MyfNgdJ3qMizXdgCErmvYC9Nn/qyjePy+4ss9Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.4", + "@types/unist": "^2.0.3", + "prism-svelte": "^0.4.7", + "prismjs": "^1.17.1", + "unist-util-visit": "^2.0.1", + "vfile-message": "^2.0.4" + }, + "peerDependencies": { + "svelte": "^3.56.0 || ^4.0.0 || ^5.0.0-next.120" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.1.tgz", + "integrity": "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz", + "integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/prism-svelte": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/prism-svelte/-/prism-svelte-0.4.7.tgz", + "integrity": "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.55.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.3.tgz", + "integrity": "sha512-y9yUpfQvetAjiDLtNMf1hL9NXchIJgWt6zIKeoB+tCd3npX08Eqfzg60V9DhIGVMtQ0AlMkFw5xa+AQ37zxnAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.3", + "@rollup/rollup-android-arm64": "4.55.3", + "@rollup/rollup-darwin-arm64": "4.55.3", + "@rollup/rollup-darwin-x64": "4.55.3", + "@rollup/rollup-freebsd-arm64": "4.55.3", + "@rollup/rollup-freebsd-x64": "4.55.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.3", + "@rollup/rollup-linux-arm-musleabihf": "4.55.3", + "@rollup/rollup-linux-arm64-gnu": "4.55.3", + "@rollup/rollup-linux-arm64-musl": "4.55.3", + "@rollup/rollup-linux-loong64-gnu": "4.55.3", + "@rollup/rollup-linux-loong64-musl": "4.55.3", + "@rollup/rollup-linux-ppc64-gnu": "4.55.3", + "@rollup/rollup-linux-ppc64-musl": "4.55.3", + "@rollup/rollup-linux-riscv64-gnu": "4.55.3", + "@rollup/rollup-linux-riscv64-musl": "4.55.3", + "@rollup/rollup-linux-s390x-gnu": "4.55.3", + "@rollup/rollup-linux-x64-gnu": "4.55.3", + "@rollup/rollup-linux-x64-musl": "4.55.3", + "@rollup/rollup-openbsd-x64": "4.55.3", + "@rollup/rollup-openharmony-arm64": "4.55.3", + "@rollup/rollup-win32-arm64-msvc": "4.55.3", + "@rollup/rollup-win32-ia32-msvc": "4.55.3", + "@rollup/rollup-win32-x64-gnu": "4.55.3", + "@rollup/rollup-win32-x64-msvc": "4.55.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/runed": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz", + "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + } + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", + "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte": { + "version": "5.53.7", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.7.tgz", + "integrity": "sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.6.3", + "esm-env": "^1.2.1", + "esrap": "^2.2.2", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.4.tgz", + "integrity": "sha512-F1pGqXc710Oi/wTI4d/x7d6lgPwwfx1U6w3Q35n4xsC2e8C/yN2sM1+mWxjlMcpAfWucjlq4vPi+P4FZ8a14sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz", + "integrity": "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0", + "pnpm": "10.24.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-sonner": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.8.tgz", + "integrity": "sha512-TjF+MY8RpBSQfker9+mBHbdx8szrXzhLuqFE89WGgTRv1uYFFc/no10kRvSvSQ7zaAv90APq7qpP145ZcsvUwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "runed": "^0.28.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-sonner/node_modules/runed": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz", + "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/svelte-toolbelt": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz", + "integrity": "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.35.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.30.2" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz", + "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwind-merge": ">=3.0.0", + "tailwindcss": "*" + }, + "peerDependenciesMeta": { + "tailwind-merge": { + "optional": true + } + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vaul-svelte": { + "version": "1.0.0-next.7", + "resolved": "https://registry.npmjs.org/vaul-svelte/-/vaul-svelte-1.0.0-next.7.tgz", + "integrity": "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "runed": "^0.23.2", + "svelte-toolbelt": "^0.7.1" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/vaul-svelte/node_modules/runed": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/vaul-svelte/node_modules/svelte-toolbelt": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz", + "integrity": "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.23.2", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/webapp/frontend/package.json b/webapp/frontend/package.json new file mode 100644 index 000000000..ca17451dc --- /dev/null +++ b/webapp/frontend/package.json @@ -0,0 +1,67 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "prettier --write .", + "test:unit": "vitest", + "test": "npm run test:unit -- --run" + }, + "engines": { + "node": ">=22" + }, + "devDependencies": { + "@codemirror/autocomplete": "^6.20.0", + "@codemirror/commands": "^6.10.2", + "@codemirror/language": "^6.12.1", + "@codemirror/lint": "^6.9.2", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.11", + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.39.1", + "@fsegurai/codemirror-theme-forest": "^6.2.3", + "@fsegurai/codemirror-theme-vscode-light": "^6.2.4", + "@internationalized/date": "^3.10.1", + "@lezer/common": "^1.5.0", + "@lezer/generator": "^1.8.0", + "@lezer/lr": "^1.4.8", + "@lucide/svelte": "^0.577.0", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.49.1", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/vite": "^4.1.17", + "@types/node": "~22.19.13", + "bits-ui": "^2.15.4", + "clsx": "^2.1.1", + "codemirror": "^6.0.2", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.13.1", + "globals": "^16.5.0", + "mdsvex": "^0.12.6", + "prettier": "^3.7.4", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "svelte": "^5.45.6", + "svelte-check": "^4.3.4", + "svelte-sonner": "^1.0.7", + "tailwind-merge": "^3.4.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.1.17", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.48.1", + "vaul-svelte": "^1.0.0-next.7", + "vite": "^7.2.6", + "vitest": "^4.0.18" + } +} \ No newline at end of file diff --git a/webapp/frontend/src/app.d.ts b/webapp/frontend/src/app.d.ts new file mode 100644 index 000000000..d0f152034 --- /dev/null +++ b/webapp/frontend/src/app.d.ts @@ -0,0 +1,22 @@ +import { HTMLInputAttributes } from 'svelte/elements'; + +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +declare module 'svelte/elements' { + interface HTMLInputAttributes { + /** Used by Codemirror to find the main input field in a search panel */ + 'main-field'?: string; + } +} + +export {}; diff --git a/webapp/frontend/src/app.html b/webapp/frontend/src/app.html new file mode 100644 index 000000000..f273cc58f --- /dev/null +++ b/webapp/frontend/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/webapp/frontend/src/lib/api.ts b/webapp/frontend/src/lib/api.ts new file mode 100644 index 000000000..59a597c5b --- /dev/null +++ b/webapp/frontend/src/lib/api.ts @@ -0,0 +1,173 @@ +export interface SourceRange { + path: string; + startLine: number; + startColumn: number; + endLine: number; + endColumn: number; +} + +export interface RunResult { + processorId: string; + output: string; + steps: number; +} + +export interface CompileRequest { + sourceId: string | null; + source: string; + target: string; + run: boolean; +} + +export interface CompileResponseMessage { + message: string; + prefix: string; + range?: SourceRange; +} + +export interface CompileResponse { + sourceId: string; + compiled: string; + runResult: RunResult | null; + errors: CompileResponseMessage[]; + warnings: CompileResponseMessage[]; + infos: CompileResponseMessage[]; + isPlainText: boolean; +} + +export interface SchemacodeCompileRequest { + sourceId: string | null; + source: string; + target: string; + run: boolean; +} + +export interface SchemacodeCompileResponse { + sourceId: string; + compiled: string; + runResults: RunResult[]; + errors: CompileResponseMessage[]; + warnings: CompileResponseMessage[]; + infos: CompileResponseMessage[]; +} + +export interface DecompileRequest { + sourceId: string | null; + source: string; + target: string; + run: boolean; +} + +export interface DecompileResponse { + sourceId: string; + source: string; + errors: CompileResponseMessage[]; + warnings: CompileResponseMessage[]; + infos: CompileResponseMessage[]; + runResults: RunResult[]; +} + +export interface ServerSource { + id: string; + source: string; + /** ISO 8601 formatted date string */ + createdAt: string; +} + +export interface Sample { + id: string; + title: string; + source: string; + runnable: boolean; +} + +export class ApiHandler { + private fetch: typeof fetch; + + constructor(fetchImpl = fetch) { + this.fetch = fetchImpl; + } + + async compileMindcode(request: CompileRequest): Promise { + const response = await this.fetch('/api/compile', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }); + + if (!response.ok) { + throw new ApiError('/api/compile', response.status); + } + + return await response.json(); + } + + async compileSchemacode(request: SchemacodeCompileRequest): Promise { + const response = await this.fetch('/api/schemacode/compile', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }); + + if (!response.ok) { + throw new ApiError('/api/schemacode/compile', response.status); + } + + return await response.json(); + } + + async decompileSchematic(request: DecompileRequest): Promise { + const response = await this.fetch('/api/schemacode/decompile', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }); + + if (!response.ok) { + throw new ApiError('/api/schemacode/decompile', response.status); + } + return await response.json(); + } + + async decompileMlog(request: DecompileRequest): Promise { + const response = await this.fetch('/api/decompile', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }); + + if (!response.ok) { + throw new ApiError('/api/decompile', response.status); + } + + return await response.json(); + } + + async loadSource(id: string): Promise { + const response = await this.fetch(`/api/source/${encodeURIComponent(id)}`); + + if (!response.ok) { + throw new ApiError(response.url, response.status); + } + + return await response.json(); + } +} + +export class ApiError extends Error { + constructor( + public path: string, + public status: number + ) { + super(`API request to ${path} failed with status ${status}`); + this.name = 'ApiError'; + } +} diff --git a/webapp/frontend/src/lib/codemirror.ts b/webapp/frontend/src/lib/codemirror.ts new file mode 100644 index 000000000..beee90b15 --- /dev/null +++ b/webapp/frontend/src/lib/codemirror.ts @@ -0,0 +1,129 @@ +import type { Diagnostic } from '@codemirror/lint'; +import { EditorView } from 'codemirror'; +import type { CompileResponseMessage, SourceRange } from './api'; +import { + EditorSelection, + Text, + Compartment, + StateField, + Facet, + StateEffect, + type TransactionSpec, + type Extension +} from '@codemirror/state'; +import { forest } from '@fsegurai/codemirror-theme-forest'; +import { vsCodeLight } from '@fsegurai/codemirror-theme-vscode-light'; +import { invertedEffects } from '@codemirror/commands'; +import { foldGutter } from '@codemirror/language'; + +export const foldChevronDownId = 'fold-chevron-down'; +export const foldChevronRightId = 'fold-chevron-right'; + +// Compartment for dynamically switching themes +export const themeCompartment = new Compartment(); +export const lineWrappingCompartment = new Compartment(); + +export const defaultDocId = Facet.define({ + combine: (values) => values[values.length - 1] +}); + +export const updateDocId = StateEffect.define(); +export const invertUpdateDocId = invertedEffects.of((tr) => { + for (const effect of tr.effects) { + if (effect.is(updateDocId)) { + const currentId = tr.startState.field(currentDocId); + return [updateDocId.of(currentId)]; + } + } + + return []; +}); + +export const currentDocId = StateField.define({ + create(state) { + return state.facet(defaultDocId); + }, + update(value, tr) { + for (const effect of tr.effects) { + if (effect.is(updateDocId)) { + value = effect.value; + } + } + + return value; + } +}); + +export function getTheme(dark: boolean) { + return dark ? forest : vsCodeLight; +} + +export function updateEditor(editor: EditorView | undefined, text: string, spec?: TransactionSpec) { + if (editor) { + const transaction = editor.state.update({ + changes: { from: 0, to: editor.state.doc.length, insert: text }, + ...spec + }); + editor.dispatch(transaction); + } +} + +export function compileMessagesToDiagnostics( + doc: Text, + errors: CompileResponseMessage[], + warnings: CompileResponseMessage[] +): Diagnostic[] { + const diagnostics: Diagnostic[] = []; + + for (const err of errors) { + diagnostics.push({ + from: err.range ? posToOffset(doc, err.range.startLine, err.range.startColumn) : 0, + to: err.range ? posToOffset(doc, err.range.endLine, err.range.endColumn) : 0, + message: err.message, + severity: 'error' + }); + } + for (const warn of warnings) { + diagnostics.push({ + from: warn.range ? posToOffset(doc, warn.range.startLine, warn.range.startColumn) : 0, + to: warn.range ? posToOffset(doc, warn.range.endLine, warn.range.endColumn) : 0, + message: warn.message, + severity: 'warning' + }); + } + + return diagnostics; +} + +export function posToOffset(doc: Text, line: number, column: number): number { + const lineInfo = doc.line(line); + return lineInfo.from + column - 1; +} + +export function jumpToRange(editor: EditorView, range: SourceRange) { + const start = posToOffset(editor.state.doc, range.startLine, range.startColumn); + const end = posToOffset(editor.state.doc, range.endLine, range.endColumn); + + editor.dispatch({ + selection: EditorSelection.range(start, end), + scrollIntoView: true + }); + editor.focus(); +} + +export function styledFoldGutter(): Extension { + return foldGutter({ + markerDOM(open) { + const div = document.createElement('div'); + const templateId = open ? foldChevronDownId : foldChevronRightId; + const template = document.getElementById(templateId) as HTMLTemplateElement | null; + if (template) { + div.appendChild(template.content.cloneNode(true)); + } else { + // fallback to a simple marker if the template is missing (shouldn't happen) + div.textContent = open ? '⌄' : '›'; + } + return div; + } + }); +} diff --git a/webapp/frontend/src/lib/components/BottomActionBar.svelte b/webapp/frontend/src/lib/components/BottomActionBar.svelte new file mode 100644 index 000000000..b057b8f15 --- /dev/null +++ b/webapp/frontend/src/lib/components/BottomActionBar.svelte @@ -0,0 +1,85 @@ + + +{#if isVisible} +
+
+ + + + + {#if secondaryAction} + + {/if} + + + {#if onDismiss} + + {/if} +
+
+{/if} + + +{#if isVisible} +
+{/if} diff --git a/webapp/frontend/src/lib/components/CompilerMessages.svelte b/webapp/frontend/src/lib/components/CompilerMessages.svelte new file mode 100644 index 000000000..27a640a0f --- /dev/null +++ b/webapp/frontend/src/lib/components/CompilerMessages.svelte @@ -0,0 +1,87 @@ + + +{#snippet messageItem(label: string, range: SourceRange)} + {#if onJumpToPosition} + { + e.preventDefault(); + onJumpToPosition(range); + }}>{label} at line {range.startLine}, column {range.startColumn}: + {:else} + {label} at line {range.startLine}, column {range.startColumn}: + {/if} +{/snippet} + +{#if errors.length > 0 || warnings.length > 0 || infos.length > 0} +
+

+ {errors.length > 0 ? 'Errors:' : warnings.length > 0 ? 'Warnings:' : title} +

+ + {#if errors.length > 0} +
    + {#each errors as error} +
  • + {#if error.range} + {@render messageItem('Error', error.range)} + {/if} + {error.message} +
  • + {/each} +
+
+ Errors were encountered. See + Mindcode Troubleshooting + for some tips, or + ask for help. +
+ {/if} + + {#if warnings.length > 0} +
    + {#each warnings as warning} +
  • + {#if warning.range} + {@render messageItem('Warning', warning.range)} + {/if} + {warning.message} +
  • + {/each} +
+ {/if} + + {#if infos.length > 0} +
    + {#each infos as info} +
  • {info.message}
  • + {/each} +
+ {/if} +
+{/if} diff --git a/webapp/frontend/src/lib/components/ControlBar.svelte b/webapp/frontend/src/lib/components/ControlBar.svelte new file mode 100644 index 000000000..ab62b7296 --- /dev/null +++ b/webapp/frontend/src/lib/components/ControlBar.svelte @@ -0,0 +1,48 @@ + + +
+ {#each primaryActions as action} + + {/each} + + + {#if children} +
+ {@render children()} +
+ {/if} +
diff --git a/webapp/frontend/src/lib/components/CopyButton.svelte b/webapp/frontend/src/lib/components/CopyButton.svelte new file mode 100644 index 000000000..ddc2d25ac --- /dev/null +++ b/webapp/frontend/src/lib/components/CopyButton.svelte @@ -0,0 +1,43 @@ + + + + {#if copied} + + {:else} + + {/if} + diff --git a/webapp/frontend/src/lib/components/EditorActionButton.svelte b/webapp/frontend/src/lib/components/EditorActionButton.svelte new file mode 100644 index 000000000..b5155636e --- /dev/null +++ b/webapp/frontend/src/lib/components/EditorActionButton.svelte @@ -0,0 +1,42 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + {tooltip} + + diff --git a/webapp/frontend/src/lib/components/EditorLayout.svelte b/webapp/frontend/src/lib/components/EditorLayout.svelte new file mode 100644 index 000000000..097283a15 --- /dev/null +++ b/webapp/frontend/src/lib/components/EditorLayout.svelte @@ -0,0 +1,139 @@ + + +
+
+ +
+ { + if (mode === 'maximized') { + outputMode = 'normal'; + } + }} + tabActions={inputActions} + > + {#snippet tabTriggers()} + {inputLabel} + {/snippet} + + +
+
+
+
+
+ + +
+ + { + if (mode === 'maximized') { + inputMode = 'normal'; + } + }} + > + {#snippet editor()} +
+
+
+ {/snippet} +
+
+
diff --git a/webapp/frontend/src/lib/components/EditorLayoutTabs.svelte b/webapp/frontend/src/lib/components/EditorLayoutTabs.svelte new file mode 100644 index 000000000..a50fd8bd9 --- /dev/null +++ b/webapp/frontend/src/lib/components/EditorLayoutTabs.svelte @@ -0,0 +1,79 @@ + + + + + {@render tabTriggers()} + + {@render tabActions?.(value)} + updateMode(mode === 'minimized' ? 'normal' : 'minimized')} + > + {#if mode === 'minimized'} + + {:else} + + {/if} + {mode === 'minimized' ? restoreLabel : minimizeLabel} + + + + + +
+ {@render children?.()} +
+
diff --git a/webapp/frontend/src/lib/components/EditorSearchPanel.svelte b/webapp/frontend/src/lib/components/EditorSearchPanel.svelte new file mode 100644 index 000000000..4b87c4bfe --- /dev/null +++ b/webapp/frontend/src/lib/components/EditorSearchPanel.svelte @@ -0,0 +1,236 @@ + + + + + diff --git a/webapp/frontend/src/lib/components/EraseButton.svelte b/webapp/frontend/src/lib/components/EraseButton.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/webapp/frontend/src/lib/components/MlogWatcherButton.svelte b/webapp/frontend/src/lib/components/MlogWatcherButton.svelte new file mode 100644 index 000000000..bd14c461f --- /dev/null +++ b/webapp/frontend/src/lib/components/MlogWatcherButton.svelte @@ -0,0 +1,45 @@ + + + + {#if status === 'sent'} + + {:else if status === 'loading'} + + {:else if status === 'error'} + + {:else} + + {/if} + diff --git a/webapp/frontend/src/lib/components/PageInfoCard.svelte b/webapp/frontend/src/lib/components/PageInfoCard.svelte new file mode 100644 index 000000000..16c6c6d77 --- /dev/null +++ b/webapp/frontend/src/lib/components/PageInfoCard.svelte @@ -0,0 +1,51 @@ + + + + + {heading} + + + + {@render children?.()} + + diff --git a/webapp/frontend/src/lib/components/ProjectLinks.svelte b/webapp/frontend/src/lib/components/ProjectLinks.svelte new file mode 100644 index 000000000..8de23a368 --- /dev/null +++ b/webapp/frontend/src/lib/components/ProjectLinks.svelte @@ -0,0 +1,40 @@ + + +
+ {#if variant === 'mindcode'} + Mindcode syntax + | + System library + | + {:else} + Schemacode syntax + | + {/if} + Readme + | + Changelog +
+ +
+ Bug reports, suggestions and questions are welcome at the project page. +
diff --git a/webapp/frontend/src/lib/components/SamplePicker.svelte b/webapp/frontend/src/lib/components/SamplePicker.svelte new file mode 100644 index 000000000..0a25945b7 --- /dev/null +++ b/webapp/frontend/src/lib/components/SamplePicker.svelte @@ -0,0 +1,98 @@ + + +{#snippet button({ props }: ButtonArgs)} + +{/snippet} + +{#if isDesktop} + + + + + + + No results found. + + {#each samples as sample (sample.title)} + handleSampleSelect(sample)}> + {sample.title} + + {/each} + + + + + +{:else} + + + +
+ + + + No results found. + + {#each samples as sample (sample.title)} + handleSampleSelect(sample)}> + {sample.title} + + {/each} + + + +
+
+
+{/if} diff --git a/webapp/frontend/src/lib/components/SettingsButton.svelte b/webapp/frontend/src/lib/components/SettingsButton.svelte new file mode 100644 index 000000000..f6b4ad6ff --- /dev/null +++ b/webapp/frontend/src/lib/components/SettingsButton.svelte @@ -0,0 +1,81 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + +
+ + Settings + Configure editor and integration options. + + +
+
+ +
+
+ MlogWatcher Integration +
+
+
+ + +
+

+ WebSocket port for the Mindustry mod integration. +

+
+
+ + + + +
+
+ Editor Options +
+
+
+ +

Wrap long lines in the editor

+
+ +
+
+
+
+ + + + {#snippet child({ props })} + + {/snippet} + + +
+
+
diff --git a/webapp/frontend/src/lib/components/TabsOutput.svelte b/webapp/frontend/src/lib/components/TabsOutput.svelte new file mode 100644 index 000000000..30eccab84 --- /dev/null +++ b/webapp/frontend/src/lib/components/TabsOutput.svelte @@ -0,0 +1,151 @@ + + + selectedTab, (newTab) => (tab = newTab)} + bind:mode + {maximizeLabel} + {minimizeLabel} + {restoreLabel} + {onModeChange} + class={className} +> + {#snippet tabTriggers()} + {codeTitle} + {#if hasOutput} + Output + {/if} + {/snippet} + + {#snippet tabActions(tab)} + {@render outputActions?.(tab === 'code')} + + {/snippet} + + + {@render editor()} + + + {#if hasOutput} + + + + + + {#if selectedOutput === 'compiler-messages'} + Compiler messages + {:else} + {selectedProcessor?.label ?? 'Select an output...'} + {/if} + + + {#if hasCompilerMessages} + Compiler messages + {/if} + {#each processorTabs as tab} + {tab.label} + {/each} + + + {#if selectedProcessor} + diff --git a/webapp/frontend/src/lib/components/ui/toggle/index.ts b/webapp/frontend/src/lib/components/ui/toggle/index.ts new file mode 100644 index 000000000..8cb2936fc --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/toggle/index.ts @@ -0,0 +1,13 @@ +import Root from "./toggle.svelte"; +export { + toggleVariants, + type ToggleSize, + type ToggleVariant, + type ToggleVariants, +} from "./toggle.svelte"; + +export { + Root, + // + Root as Toggle, +}; diff --git a/webapp/frontend/src/lib/components/ui/toggle/toggle.svelte b/webapp/frontend/src/lib/components/ui/toggle/toggle.svelte new file mode 100644 index 000000000..56eb86b14 --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/toggle/toggle.svelte @@ -0,0 +1,52 @@ + + + + + diff --git a/webapp/frontend/src/lib/components/ui/tooltip/index.ts b/webapp/frontend/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 000000000..171860421 --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,19 @@ +import Root from "./tooltip.svelte"; +import Trigger from "./tooltip-trigger.svelte"; +import Content from "./tooltip-content.svelte"; +import Provider from "./tooltip-provider.svelte"; +import Portal from "./tooltip-portal.svelte"; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal, +}; diff --git a/webapp/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 000000000..266252287 --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,52 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
+ {/snippet} +
+
+
diff --git a/webapp/frontend/src/lib/components/ui/tooltip/tooltip-portal.svelte b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-portal.svelte new file mode 100644 index 000000000..d234f7d7d --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/webapp/frontend/src/lib/components/ui/tooltip/tooltip-provider.svelte b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-provider.svelte new file mode 100644 index 000000000..8150bef2e --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-provider.svelte @@ -0,0 +1,7 @@ + + + diff --git a/webapp/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 000000000..1acdaa47b --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/webapp/frontend/src/lib/components/ui/tooltip/tooltip.svelte b/webapp/frontend/src/lib/components/ui/tooltip/tooltip.svelte new file mode 100644 index 000000000..0b0f9cef2 --- /dev/null +++ b/webapp/frontend/src/lib/components/ui/tooltip/tooltip.svelte @@ -0,0 +1,7 @@ + + + diff --git a/webapp/frontend/src/lib/editors.svelte.ts b/webapp/frontend/src/lib/editors.svelte.ts new file mode 100644 index 000000000..47115b10a --- /dev/null +++ b/webapp/frontend/src/lib/editors.svelte.ts @@ -0,0 +1,392 @@ +import { + currentDocId, + defaultDocId, + getTheme, + invertUpdateDocId, + lineWrappingCompartment, + styledFoldGutter, + themeCompartment, + updateDocId +} from './codemirror'; +import { sourceIdKey, syncUrl, type ThemeStore } from './stores.svelte'; +import { defaultKeymap, history, historyKeymap, insertTab } from '@codemirror/commands'; +import { + crosshairCursor, + drawSelection, + dropCursor, + EditorView, + highlightActiveLine, + highlightActiveLineGutter, + highlightSpecialChars, + keymap, + lineNumbers, + rectangularSelection +} from '@codemirror/view'; +import type { Attachment } from 'svelte/attachments'; +import { mount, unmount, untrack } from 'svelte'; +import { Annotation, EditorState, Transaction, type Extension } from '@codemirror/state'; +import { browser } from '$app/environment'; +import { page } from '$app/state'; +import type { ApiHandler, Sample } from './api'; +import { + bracketMatching, + defaultHighlightStyle, + foldKeymap, + indentOnInput, + syntaxHighlighting +} from '@codemirror/language'; +import { lintKeymap } from '@codemirror/lint'; +import { + autocompletion, + closeBrackets, + closeBracketsKeymap, + completionKeymap +} from '@codemirror/autocomplete'; +import { highlightSelectionMatches, search, searchKeymap } from '@codemirror/search'; +import EditorSearchPanel from './components/EditorSearchPanel.svelte'; +import type { Settings } from './settings.svelte'; +import { toast } from 'svelte-sonner'; + +export type EditorStoreType = 'input' | 'output'; + +export interface InputEditorStoreOptions { + api: ApiHandler; + theme: ThemeStore; + samples?: Sample[]; + extensions?: Extension[]; + settings: Settings; +} + +const keepCurrentUrl = Annotation.define(); +const internalUpdate = Annotation.define(); + +export class InputEditorStore { + view = $state(); + isLoading = $state(true); + #id = $derived(browser ? page.url.searchParams.get(sourceIdKey) : null); + /** Reactive value kept in sync with the id stored in the editor's state */ + sourceId = $state(this.#id); + + constructor({ api, theme, samples = [], extensions = [], settings }: InputEditorStoreOptions) { + const sampleIds = new Set(samples.map((s) => s.id)); + // handle editor creation and destruction + $effect.pre(() => { + untrack(() => { + this.view = new EditorView({ + extensions: [ + commonExtensions(theme, settings), + defaultDocId.of(null), + currentDocId, + invertUpdateDocId, + EditorView.updateListener.of((update) => { + this.sourceId = update.state.field(currentDocId); + }), + EditorView.updateListener.of((update) => { + if (this.isLoading) return; + + const docId = update.state.field(currentDocId); + if (this.#id === docId) return; + if (update.transactions.some((tr) => tr.annotation(keepCurrentUrl))) return; + + this.#id = docId; + syncUrl({ sourceId: docId, replaceState: false }); + }), + extensions + ], + // Intercepts transactions on the input editor to reset the + // source id when a sample is modified. This allows the server to not have to + // save the source code if the sourceId is a sample id. + dispatchTransactions(trs, view) { + const docId = view.state.field(currentDocId); + + if (shouldResetDocId(trs, docId, sampleIds)) { + const tr = view.state.update( + { + effects: updateDocId.of(null) + }, + ...trs + ); + view.update([tr]); + return; + } + view.update(trs); + } + }); + }); + + return () => { + this.view?.destroy(); + this.view = undefined; + }; + }); + + syncEditorTheme(() => this.view, theme); + syncEditorLineWrapping(() => this.view, settings); + + // sync editor content with sourceId from URL + $effect(() => { + const view = this.view; + if (!view) return; + + const id = this.#id; + const docId = view.state.field(currentDocId); + + untrack(() => { + // don't update if the ids are the same + if (docId === id) { + this.isLoading = false; + return; + } + + if (!id) { + view.dispatch({ + effects: updateDocId.of(null), + changes: { from: 0, to: view.state.doc.length, insert: '' }, + annotations: internalUpdate.of(true) + }); + this.isLoading = false; + return; + } + + const sample = samples.find((s) => s.id === id); + + if (sample) { + view.dispatch({ + effects: updateDocId.of(sample.id), + changes: { from: 0, to: view.state.doc.length, insert: sample.source }, + annotations: internalUpdate.of(true) + }); + this.isLoading = false; + return; + } + + this.isLoading = true; + api + .loadSource(id) + .then((serverSource) => { + view.dispatch({ + effects: updateDocId.of(serverSource.id), + changes: { from: 0, to: view.state.doc.length, insert: serverSource.source }, + annotations: internalUpdate.of(true) + }); + this.isLoading = false; + }) + .catch((error) => { + toast.error('Failed to load the source. Please check the URL and try again.'); + console.error('Error loading source:', error); + + view.dispatch({ + // preserve the id in case the user wants to reload + effects: updateDocId.of(id), + changes: { from: 0, to: view.state.doc.length, insert: '' }, + annotations: internalUpdate.of(true) + }); + this.isLoading = false; + return; + }); + }); + }); + } + + selectSample(sample: Sample, { preserveUrl = true } = {}) { + if (!this.view) return; + + this.isLoading = false; + + // avoid triggering an id reset if the same sample is selected again + if (this.sourceId === sample.id) return; + + this.view.dispatch({ + effects: updateDocId.of(sample.id), + changes: { from: 0, to: this.view.state.doc.length, insert: sample.source }, + annotations: [keepCurrentUrl.of(preserveUrl), internalUpdate.of(true)] + }); + } + + clear({ preserveUrl = true } = {}) { + if (!this.view) return; + this.isLoading = false; + this.view.dispatch({ + effects: updateDocId.of(null), + changes: { from: 0, to: this.view.state.doc.length, insert: '' }, + annotations: [keepCurrentUrl.of(preserveUrl), internalUpdate.of(true)] + }); + } + + setEditorId(newId: string | null, { addToHistory = false, preserveUrl = false } = {}) { + if (!this.view) return; + const id = this.view.state.field(currentDocId); + if (newId === id) return; + + this.view.dispatch({ + effects: updateDocId.of(newId), + annotations: [ + Transaction.addToHistory.of(addToHistory), + keepCurrentUrl.of(preserveUrl), + internalUpdate.of(true) + ] + }); + } + + attach: Attachment = (element) => { + const view = this.view; + if (!view) return; + element.appendChild(view.dom); + }; +} + +export class OutputEditorStore { + view = $state(); + + constructor( + public theme: ThemeStore, + public extensions: Extension[] = [], + public settings: Settings + ) { + $effect.pre(() => { + untrack(() => { + this.view = new EditorView({ + extensions: [commonExtensions(theme, settings), ...extensions] + }); + }); + + return () => { + this.view?.destroy(); + this.view = undefined; + }; + }); + + syncEditorTheme(() => this.view, theme); + syncEditorLineWrapping(() => this.view, settings); + } + + attach: Attachment = (element) => { + const view = this.view; + if (!view) return; + element.appendChild(view.dom); + }; + + get state(): EditorState | undefined { + return this.view?.state; + } +} + +function syncEditorTheme(getView: () => EditorView | undefined, themeStore: ThemeStore) { + $effect(() => { + const view = getView(); + if (!view) return; + const editor = view; + const newTheme = getTheme(themeStore.isDark); + if (themeCompartment.get(editor.state) === newTheme) return; + + untrack(() => { + view.dispatch({ + effects: themeCompartment.reconfigure(newTheme) + }); + }); + }); +} + +function syncEditorLineWrapping(getView: () => EditorView | undefined, settings: Settings) { + $effect(() => { + const view = getView(); + if (!view) return; + const { lineWrapping } = settings; + + untrack(() => { + view.dispatch({ + effects: lineWrappingCompartment.reconfigure(lineWrapping ? EditorView.lineWrapping : []) + }); + }); + }); +} + +function commonExtensions(themeStore: ThemeStore, settings: Settings): Extension[] { + return [ + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + drawSelection(), + dropCursor(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + crosshairCursor(), + highlightActiveLine(), + highlightSelectionMatches(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + ...completionKeymap, + ...lintKeymap, + { key: 'Tab', run: insertTab } + ]), + styledFoldGutter(), + search({ + createPanel(view) { + const dom = document.createElement('div'); + const component = mount(EditorSearchPanel, { + target: dom, + props: { + view + } + }); + + return { + dom, + mount() { + component.selectInput(); + }, + update(update) { + component.update(update); + }, + destroy() { + unmount(component); + } + }; + } + }), + EditorView.theme({ + '&': { + height: '100%', + width: '100%', + fontSize: '14px' + }, + '.cm-scroller': { + fontFamily: 'monospace' + }, + '.cm-gutters': { + // prevent the theme libraries from adding padding + // to the gutter because it breaks active line highlighting + // by leaving a between the highlighted gutter and the highlighted line + paddingRight: 0 + } + }), + themeCompartment.of(getTheme(themeStore.isDark)), + lineWrappingCompartment.of(settings.lineWrapping ? EditorView.lineWrapping : []) + ]; +} + +export function shouldResetDocId( + transactions: readonly Transaction[], + docId: string | null, + sampleIds: Set +): boolean { + const isSample = docId && sampleIds.has(docId); + + if (!isSample) return false; + + const hasUserEdit = transactions.some( + (transaction) => transaction.docChanged && !transaction.annotation(internalUpdate) + ); + + return hasUserEdit; +} diff --git a/webapp/frontend/src/lib/grammars/mindcode.grammar b/webapp/frontend/src/lib/grammars/mindcode.grammar new file mode 100644 index 000000000..3f6a24d59 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mindcode.grammar @@ -0,0 +1,389 @@ +@precedence { + dot, + declModifierBracket, + stat, + call, + prefix, + + exp @left, + times @left, + plus @left, + shift @left, + bitAnd @left, + bitOr @left, + dots @left, + inList @left, + rel @left, + equal @left, + and @left, + or @left, + ternary @right, + assign @right, + case, + formatIdentifier +} +@top Program { statementList? } + +@skip { space | LineComment | BlockComment | doubleLineComment } + +commaSep { term ("," term)* } +semiColonSep { term (";" term)* } +kw { @specialize[@name={term}] } +dir { @specialize[@name={term}] } + +ArithOp { expr } +LogicOp { expr } +CompareOp { expr } +UpdateOp { expr } +BitOp { expr } + +end { kw<"end"> } +statementList { (statement? ";" | EnhancedComment | MlogBlock)* } + +statement { + expression | + Directive | + VariableDeclaration | + kw<"module"> Identifier | + kw<"allocate"> commaSep | + kw<"param"> Identifier "=" expression | + kw<"require"> (String | Identifier) (kw<"remote"> commaSep)? | + FunctionDefinition | + LabeledStatement | + Loop | + ForLoop | + WhileLoop | + DoWhileLoop | + BreakStatement { kw<"break"> (Identifier | kw<"do"> | kw<"loop"> | kw<"for"> | kw<"while"> | kw<"atomic"> | kw<"begin"> | kw<"debug">)? } | + ContinueStatement { kw<"continue"> (Identifier | kw<"do"> | kw<"loop"> | kw<"for"> | kw<"while">)? } | + ReturnStatement { kw<"return"> expression? } | + AtomicBlock | + Block | + DebugBlock +} + +AtomicBlock { kw<"atomic"> ~sharedModifier statementList end } +Block { kw<"begin"> statementList end } +DebugBlock { kw<"debug"> ~sharedModifier statementList end } + +expression { + lvalue | + Range | + KeywordLiteral | + CallExpression | + AtomicExpression { kw<"atomic"> ~sharedModifier !call "(" expression ")" } | + CaseExpression | + IfExpression | + FormatString | + String | + ColorLiteral | + number | + CharLiteral | + kw<"null"> | + kw<"true"> | + kw<"false"> | + IncDec { lvalue ("++" | "--") } | + // yes this is wrong, however this parser + // doesn't need to be correct, just enough to have + // syntax highlighting + // "--a.b" is parsed as (--a).b + IncDecPrefix { ("++" | "--") !prefix lvalue } | + BinaryExpression | + UnaryExpression | + TernaryExpression | + AssignmentExpression | + "(" expression ")" +} + +lvalue { + Identifier | MemberExpression +} + +MemberExpression { + expression !dot "." MemberProperty { Identifier } | + expression !dot "[" expression "]" +} + +Range { + expression !dots (".." | "...") expression +} + +Identifier { + SimpleIdentifier | + BuiltinIdentifier | + ExternalIdentifier +} + +CallExpression { + (expression | Identifier { EndIdentifier }) !call ArgumentList { "(" commaSep? ")" } +} + +callArgument { + (kw<"in"> | kw<"out"> | kw<"ref">)+ Identifier | expression +} + +IfExpression { + kw<"if"> expression kw<"then"> statementList + (kw<"elsif"> expression kw<"then"> statementList)* + (kw<"else"> statementList)? end +} + +CaseExpression { + kw<"case"> expression CaseAlternative* (kw<"else"> statementList)? end +} + +CaseAlternative { + !case kw<"when"> commaSep kw<"then"> statementList? +} + +Directive { + (dir<"#set"> | dir<"#setlocal">) DirectiveValue ("=" commaSep)? | + dir<"#declare"> SimpleIdentifier commaSep<(KeywordLiteral | Identifier)> +} + +VariableDeclaration { + declarationModifier* kw<"var"> commaSep | + declarationModifier+ commaSep +} + +VariableDeclarationItem { + Identifier ("=" expression)? | + Identifier "[" expression? "]" ("=" ValueList)? +} + +ValueList { + "(" commaSep ")" +} + +declarationModifier { + kw<"const"> | + kw<"cached"> | + kw<"export"> ~sharedModifier | + kw<"external"> ("(" Identifier ("[" expression "]")? ")")? | + kw<"guarded"> | + kw<"linked"> | + kw<"mlog"> "(" commaSep ")" | + kw<"noinit"> | + kw<"remote"> ~sharedModifier ("(" Identifier? ")")? | + kw<"volatile"> +} + + +allocation { + (kw<"heap"> | kw<"stack">) kw<"in"> Identifier ("[" Range "]")? +} + +BinaryExpression { + expression !exp ArithOp<"**"> expression | + expression !times ArithOp<"/" | "%" | "*" | "\\" | "%%"> expression | + expression !plus ArithOp<"+" | "-"> expression | + expression !shift BitOp<">>>" | ">>" | "<<"> expression | + expression !rel CompareOp<"<" | "<=" | ">" | ">="> expression | + expression !equal CompareOp<"==" | "===" | "!=" | "!=="> expression | + expression !bitOr BitOp<"|" | "^"> expression | + expression !bitAnd BitOp<"&"> expression | + expression !inList (kw<"not">? kw<"in">) expression | + expression !and LogicOp<"&&" | kw<"and">> expression | + expression !or LogicOp<"||" | kw<"or">> expression +} + +UnaryExpression { + !prefix (ArithOp<"+" | "-" | "~" | "!"> | kw<"not">) expression +} + +TernaryExpression { + expression !ternary "?" expression ":" expression +} + +AssignmentExpression { + expression !assign + UpdateOp<"=" | "**=" | + "*=" | "/=" | "\\=" | "%=" | "%%=" | + "+=" | "-=" | "<<=" | ">>=" | ">>>=" | + "&=" | "|=" | "^=" | "&&=" | "||="> + expression +} + +FunctionDefinition { + functionModifier* (kw<"void"> | kw<"def">) Identifier ParameterList { "(" commaSep? ")" } + FunctionBody { statementList } + end +} + +functionParameter { + (kw<"in"> | kw<"out"> | kw<"ref">)* Identifier "..."? +} + +functionModifier { + kw<"atomic"> ~sharedModifier | kw<"debug"> ~sharedModifier | kw<"inline"> | kw<"noinline"> | kw<"export"> ~sharedModifier | kw<"remote"> ~sharedModifier +} + +LabeledStatement { + Label (ForLoop | WhileLoop | DoWhileLoop | Loop | AtomicBlock | Block | DebugBlock) +} + +Loop { + kw<"loop"> statementList end +} + +ForLoop { + kw<"for"> semiColonSep kw<"do"> statementList end | + kw<"for"> (VariableDeclaration ~forAmbig | commaSep) ";" expression? ";" commaSep? kw<"do"> statementList end +} + +iterationGroup { + kw<"var">? commaSep kw<"in"> commaSep kw<"descending">? +} + +iteratorVar { + kw<"out">? Identifier ~forAmbig +} + +WhileLoop { + ~while kw<"while"> expression kw<"do"> statementList end +} + +DoWhileLoop { + kw<"do"> statementList ~while kw<"while"> expression +} + +MlogBlock { + kw<"mlog"> ("(" commaSep? ")")? mlogBody +} + +mlogParamVar { + (kw<"in"> | kw<"out">)* Identifier ~mlogAmbig +} + +@skip {} { + mlogBody { + MlogBodyStart MlogBlockContent { (mlogString | rawMlogToken | mlogToken)* } MlogBodyEnd + } +} + + +Label { + Identifier ":" +} + +@skip {} { + FormatString { + formatStart (formatEscape | formatContent | formatMaybeIdentifier | formatPlaceholder | formatExpression)* formatEnd + } + + formatPlaceholder { + FormatPlaceholderStart Identifier { SimpleIdentifier { !formatIdentifier formatMaybeIdentifier } }? + } + + EnhancedComment { + enhancedCommentStart (enhancedCommentInterpolation | enhancedCommentContent | enhancedCommentPlaceholder)* enhancedCommentEnd + } + + enhancedCommentPlaceholder { + enhancedCommentFormatPlaceholderStart Identifier + } + + String { + stringStart (stringEscape | stringContent)* stringEnd + } +} + +formatExpression[@name=Interpolation] { + InterpolationStart expression InterpolationEnd +} + +enhancedCommentInterpolation[@name=Interpolation] { + enhancedCommentInterpolationStart expression InterpolationEnd +} + +number { + (BinaryLiteral | HexLiteral | IntLiteral | FloatLiteral) +} + +@external tokens endCall from './mindcode_tokens' { EndIdentifier } + +@local tokens { + InterpolationStart[closedBy=InterpolationEnd] { "${" } + FormatPlaceholderStart { "$" } + formatMaybeIdentifier { $[a-zA-Z_] $[a-zA-Z0-9_]* } + @precedence { + InterpolationStart, + FormatPlaceholderStart + } + formatEscape[@name=Escape] { "\\" $[$\\n] } + formatEnd { '"' } + @else formatContent +} + +@local tokens { + enhancedCommentInterpolationStart[@name=InterpolationStart,closedBy=InterpolationEnd] { "${" } + enhancedCommentFormatPlaceholderStart[@name=FormatPlaceholderStart] { "$" } + @precedence { + enhancedCommentInterpolationStart, + enhancedCommentFormatPlaceholderStart + } + enhancedCommentEnd { "\n" | @eof } + @else enhancedCommentContent +} + +@local tokens { + stringEnd { '"' } + stringEscape[@name=Escape] { "\\" $[\\n] } + @else stringContent +} + +@local tokens { + mlogString { '"' !["]* '"' } + MlogBodyEnd[openedBy="MlogBodyStart"] { "}" } + rawMlogToken { ':' !["#; \t\r\n] ![#; \t\r\n]* } + @else mlogToken +} + +@tokens { + "..." ".." "?" ":" "," ";" + space { @whitespace+ } + enhancedCommentStart { "///" } + LineComment { "//" ![\n]* } + doubleLineComment[@name=LineComment] { "////" ![\n]* } + BlockComment { "/*" blockCommentRest } + blockCommentRest { ![*] blockCommentRest | "*" blockCommentAfterStar } + blockCommentAfterStar { "/" | "*" blockCommentAfterStar | ![/*] blockCommentRest } + @precedence { + doubleLineComment, + enhancedCommentStart, + LineComment, + "/" + } + + SimpleIdentifier { "@@"? $[a-zA-Z_] $[a-zA-Z0-9_]* } + ExternalIdentifier { "$" $[a-zA-Z_] $[a-zA-Z0-9_]* } + directiveName { "#" $[a-zA-Z]+ } + BuiltinIdentifier { "@" $[a-zA-Z_] ($[-a-zA-Z0-9_]* $[a-zA-Z0-9_])? } + @precedence { + SimpleIdentifier, + ExternalIdentifier, + BuiltinIdentifier + } + + KeywordLiteral { ":" $[a-zA-Z_] ($[-a-zA-Z0-9_]* $[a-zA-Z0-9_])? } + DirectiveValue { $[-a-zA-Z0-9_.]+ } + formatStart { '$"' } + stringStart { '"' } + InterpolationEnd[openedBy=InterpolationStart] { "}" } + ColorLiteral { "%" ($[0-9a-fA-F]+ | "[" $[a-zA-Z_]+ "]") } + BinaryLiteral { "0b" $[01]+ } + HexLiteral { "0x" $[0-9a-fA-F]+ } + IntLiteral { $[0-9]+ } + floatExponent { $[eE]$[+-]? $[0-9]+ } + FloatLiteral { $[0-9]+ floatExponent | $[0-9]* "." $[0-9]+ floatExponent? } + CharEscape { "\\" $[\\n] } + CharLiteral { "'" (![\n\r'] | CharEscape) "'" } + MlogBodyStart[closedBy="MlogBodyEnd"] { "{" } + + @precedence { + BinaryLiteral, + HexLiteral, + FloatLiteral, + IntLiteral + } +} \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/mindcode.grammar.d.ts b/webapp/frontend/src/lib/grammars/mindcode.grammar.d.ts new file mode 100644 index 000000000..ff74c6d63 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mindcode.grammar.d.ts @@ -0,0 +1,3 @@ +import { LRParser } from "@lezer/lr"; + +export const parser: LRParser; diff --git a/webapp/frontend/src/lib/grammars/mindcode.grammar.terms.d.ts b/webapp/frontend/src/lib/grammars/mindcode.grammar.terms.d.ts new file mode 100644 index 000000000..b5af63907 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mindcode.grammar.terms.d.ts @@ -0,0 +1 @@ +export const EndIdentifier: number; diff --git a/webapp/frontend/src/lib/grammars/mindcode.grammar.test.ts b/webapp/frontend/src/lib/grammars/mindcode.grammar.test.ts new file mode 100644 index 000000000..ac3e96949 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mindcode.grammar.test.ts @@ -0,0 +1,22 @@ +import { parser } from './mindcode.grammar'; +import { fileTests } from '@lezer/generator/test'; +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { test, describe, expect } from 'vitest'; + +describe('mindcode grammar', async () => { + const directory = join(import.meta.dirname, 'test/mindcode'); + const fileNames = await readdir(directory); + const fileContents = await Promise.all( + fileNames.map((file) => readFile(join(directory, file), 'utf-8')) + ); + + const testFiles = fileContents.flatMap((file, index) => fileTests(file, fileNames[index])); + + for (const { name, run } of testFiles) { + test.concurrent(name, () => { + run(parser); + expect(true).toBe(true); // prevent "no assertions" error + }); + } +}); diff --git a/webapp/frontend/src/lib/grammars/mindcode_language.ts b/webapp/frontend/src/lib/grammars/mindcode_language.ts new file mode 100644 index 000000000..ef1b1da72 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mindcode_language.ts @@ -0,0 +1,113 @@ +import { parser } from './mindcode.grammar'; +import { + LRLanguage, + LanguageSupport, + indentNodeProp, + foldNodeProp, + foldInside, + delimitedIndent +} from '@codemirror/language'; +import { parseMixed } from '@lezer/common'; +import { styleTags, tags as t } from '@lezer/highlight'; +import { mlogLanguage } from './mlog_language'; + +const mlogParser = mlogLanguage.parser.configure({ + dialect: 'mindcodeEmbed' +}); + +export const mindcodeLanguage = LRLanguage.define({ + name: 'mindcode', + parser: parser.configure({ + wrap: parseMixed((node) => { + if (node.name !== 'MlogBlockContent') return null; + return { parser: mlogParser }; + }), + props: [ + styleTags({ + 'var const param': t.definitionKeyword, + 'cached export external guarded linked noinit remote volatile in out ref inline noinline': + t.keyword, + 'if then else elsif loop for do while break continue return case when begin end': + t.controlKeyword, + 'def void': t.definitionKeyword, + 'allocate heap stack require module mlog atomic debug and or not': t.keyword, + 'CallExpression/Identifier!': t.function(t.variableName), + 'FunctionDefinition/Identifier!': t.function(t.definition(t.variableName)), + 'MemberExpression/MemberProperty/Identifier/SimpleIdentifier': t.propertyName, + 'MemberExpression/MemberProperty/Identifier/ExternalIdentifier': t.propertyName, + 'Label/Identifier!': t.labelName, + 'BreakStatement/Identifier! ContinueStatement/Identifier!': t.labelName, + SimpleIdentifier: t.variableName, + ExternalIdentifier: t.variableName, + BuiltinIdentifier: t.special(t.variableName), + 'String FormatString': t.string, + CharLiteral: t.character, + Escape: t.escape, + 'BinaryLiteral HexLiteral FloatLiteral IntLiteral': t.number, + 'true false': t.bool, + null: t.null, + KeywordLiteral: t.atom, + ColorLiteral: t.color, + 'LineComment BlockComment': t.comment, + EnhancedComment: t.special(t.comment), + '#declare #set #setlocal': t.processingInstruction, + DirectiveValue: t.attributeValue, + '( ) [ ]': t.paren, + 'MlogBodyStart MlogBodyEnd': t.brace, + 'InterpolationStart InterpolationEnd FormatPlaceholderStart': t.special(t.brace), + ArithOp: t.arithmeticOperator, + UpdateOp: t.updateOperator, + CompareOp: t.compareOperator, + LogicOp: t.logicOperator, + BitOp: t.bitwiseOperator, + '? :': t.punctuation, + '.. ...': t.punctuation, + ', ;': t.separator, + '.': t.derefOperator + }), + indentNodeProp.add({ + 'Block IfExpression FunctionDefinition CaseExpression AtomicBlock DebugBlock': + delimitedIndent({ + closing: 'end', + align: false + }), + 'Loop ForLoop WhileLoop': delimitedIndent({ + closing: 'end', + align: false + }), + DoWhileLoop: delimitedIndent({ closing: 'while', align: false }), + MlogBlock: delimitedIndent({ closing: '}', align: false }) + }), + foldNodeProp.add({ + 'Block IfExpression CaseExpression AtomicBlock DebugBlock ForLoop Loop WhileLoop': + foldInside, + DoWhileLoop(node) { + const firstChild = node.getChild('do'); + const lastChild = node.getChild('while') || node.lastChild; + if (!lastChild || !firstChild || lastChild.to <= firstChild.to) return null; + + return { from: firstChild.to, to: lastChild.from }; + }, + FunctionDefinition(node) { + const name = node.getChild('Identifier'); + const end = node.getChild('end'); + if (!name || !end) return null; + return { from: name.to, to: end.from }; + }, + MlogBlock(node) { + const start = node.getChild('MlogBodyStart'); + const end = node.getChild('MlogBodyEnd'); + if (!start || !end) return null; + return { from: start.to, to: end.from }; + } + }) + ] + }), + languageData: { + commentTokens: { line: '//', block: { open: '/*', close: '*/' } } + } +}); + +export function mindcode() { + return new LanguageSupport(mindcodeLanguage); +} diff --git a/webapp/frontend/src/lib/grammars/mindcode_tokens.ts b/webapp/frontend/src/lib/grammars/mindcode_tokens.ts new file mode 100644 index 000000000..86639354e --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mindcode_tokens.ts @@ -0,0 +1,25 @@ +import { ExternalTokenizer } from '@lezer/lr'; +import { EndIdentifier } from './mindcode.grammar.terms'; + +const e = 101, + n = 110, + d = 100, + leftParen = 40, + space = 32, + tab = 9, + newline = 10, + carriageReturn = 13; + +export const endCall = new ExternalTokenizer((input, stack) => { + if (input.next == e && input.peek(1) == n && input.peek(2) == d) { + let pos = 3; + let ch = input.peek(pos); + while (ch == space || ch == tab || ch == newline || ch == carriageReturn) { + pos++; + ch = input.peek(pos); + } + + if (ch !== leftParen) return; + input.acceptToken(EndIdentifier, 3); + } +}); diff --git a/webapp/frontend/src/lib/grammars/mlog.grammar b/webapp/frontend/src/lib/grammars/mlog.grammar new file mode 100644 index 000000000..477272a2c --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mlog.grammar @@ -0,0 +1,102 @@ +// modified from https://github.com/JeanJPNM/mlogls/blob/aaa80957e5a313e35e61173cce4979c6d576fe3b/packages/site/src/codemirror/syntax.grammar +@dialects { mindcodeEmbed } +@top Program { statements? } + +@skip { whitespace | Comment | MindcodeComment } + + +statements { + instructionSeparator* simpleStatement (instructionSeparator simpleStatement?)* +} + +simpleStatement { + LabelDeclaration | Instruction +} + +LabelDeclaration { + maybeLabelName value* +} + +Instruction { + instructionName value* +} + +instructionName { + ControlInstructionName { controlKeyword } | + InstructionName { identifier } +} + +controlKeyword { @specialize } + +value { + Boolean | + NullLiteral | + NumberLiteral | + String | + Identifier { identifier | maybeLabelName | controlKeyword } | + Color | + GlobalIdentifier +} + +@local tokens { + stringEnd { '"' } + StringFormatPlaceholder { "{" $[0-9] "}"} + StringEscapedBracket { "[[" } + StringNewLineSequence { "\\n" } + StringColorTag { "[" $[#a-zA-Z0-9]* "]"} + stringContent { ![\n] } + + @precedence { + stringEnd, + StringFormatPlaceholder, + StringEscapedBracket, + StringColorTag, + StringNewLineSequence, + stringContent + } +} + +@skip {} { + String { + '"' ( + stringContent | + StringFormatPlaceholder | + StringEscapedBracket | + StringColorTag | + StringNewLineSequence + )* stringEnd + } +} + +NullLiteral { + @specialize +} + +Boolean { + @specialize +} + +@external specialize { identifier } specializeIdentifier from "./mlog_tokens" { + NumberLiteral, + Color, + GlobalIdentifier +} + +@tokens { + ";" + instructionSeparator { "\n" | ";" } + whitespace { @whitespace } + Comment { "#" ![\n]* } + MindcodeComment[@dialect=mindcodeEmbed] { "//" ![\n]* } + identifier { ![ \t\n\r#;"] ![ \t\n\r#;]* } + maybeLabelName { ![ \t\n\r#;"] ![ \t\n\r#;]* ":" } + + @precedence { + instructionSeparator, + whitespace, + Comment, + MindcodeComment, + maybeLabelName, + identifier + } +} diff --git a/webapp/frontend/src/lib/grammars/mlog.grammar.d.ts b/webapp/frontend/src/lib/grammars/mlog.grammar.d.ts new file mode 100644 index 000000000..ff74c6d63 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mlog.grammar.d.ts @@ -0,0 +1,3 @@ +import { LRParser } from "@lezer/lr"; + +export const parser: LRParser; diff --git a/webapp/frontend/src/lib/grammars/mlog.grammar.terms.d.ts b/webapp/frontend/src/lib/grammars/mlog.grammar.terms.d.ts new file mode 100644 index 000000000..efbe6b628 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mlog.grammar.terms.d.ts @@ -0,0 +1,4 @@ +export const Color: number; +export const NumberLiteral: number; +export const GlobalIdentifier: number; +export const Dialect_mindcodeEmbed: number; diff --git a/webapp/frontend/src/lib/grammars/mlog.grammar.test.ts b/webapp/frontend/src/lib/grammars/mlog.grammar.test.ts new file mode 100644 index 000000000..923e45648 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mlog.grammar.test.ts @@ -0,0 +1,22 @@ +import { parser } from './mlog.grammar'; +import { fileTests } from '@lezer/generator/test'; +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { test, describe, expect } from 'vitest'; + +describe('mlog grammar', async () => { + const directory = join(import.meta.dirname, 'test/mlog'); + const fileNames = await readdir(directory); + const fileContents = await Promise.all( + fileNames.map((file) => readFile(join(directory, file), 'utf-8')) + ); + + const testFiles = fileContents.flatMap((file, index) => fileTests(file, fileNames[index])); + + for (const { name, run } of testFiles) { + test.concurrent(name, () => { + run(parser); + expect(true).toBe(true); // prevent "no assertions" error + }); + } +}); diff --git a/webapp/frontend/src/lib/grammars/mlog_language.ts b/webapp/frontend/src/lib/grammars/mlog_language.ts new file mode 100644 index 000000000..95943feaa --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mlog_language.ts @@ -0,0 +1,80 @@ +import { indentNodeProp, LanguageSupport, LRLanguage } from '@codemirror/language'; +import { parser } from './mlog.grammar'; +import { styleTags, tags as t } from '@lezer/highlight'; +import { lineNumbers } from '@codemirror/view'; + +// use this https://lezer-playground.vercel.app/ +// playground to test the grammar +export const mlogLanguage = LRLanguage.define({ + name: 'mlog', + parser: parser.configure({ + props: [ + indentNodeProp.add(() => { + return (context) => { + let parent = context.node; + + while (parent.parent) { + parent = parent.parent; + } + + const { doc } = context.state; + + const nextChild = parent.childAfter(context.pos); + const posLine = doc.lineAt(context.pos).number; + + if ( + nextChild?.name === 'LabelDeclaration' && + doc.lineAt(nextChild.from).number === posLine + ) + return null; + + let previousChild = parent.childBefore(context.pos); + + if (!previousChild) return null; + + if (previousChild.type.name === 'Comment') { + const prev = previousChild.prevSibling; + + if (prev?.type.name !== 'LabelDeclaration') return null; + if (doc.lineAt(prev.from).number !== posLine) return null; + previousChild = prev; + } + + if (previousChild.type.name !== 'LabelDeclaration') return null; + + return context.column(previousChild.from) + context.unit; + }; + }), + styleTags({ + Identifier: t.variableName, + GlobalIdentifier: t.special(t.variableName), + Comment: t.lineComment, + MindcodeComment: t.lineComment, + Boolean: t.bool, + Color: t.number, + LabelDeclaration: t.function(t.name), + InstructionName: t.keyword, + NullLiteral: t.null, + NumberLiteral: t.number, + String: t.string, + ControlInstructionName: t.controlKeyword, + StringFormatPlaceholder: t.tagName, + StringEscapedBracket: t.escape, + StringColorTag: t.tagName, + StringNewLineSequence: t.escape, + ';': t.punctuation + }) + ] + }), + languageData: { + commentTokens: { line: '#' } + } +}); + +export const mlogLanguageExtension = new LanguageSupport(mlogLanguage, [ + lineNumbers({ + formatNumber(lineNo, state) { + return (lineNo - 1).toString(); + } + }) +]); diff --git a/webapp/frontend/src/lib/grammars/mlog_tokens.ts b/webapp/frontend/src/lib/grammars/mlog_tokens.ts new file mode 100644 index 000000000..e8263622a --- /dev/null +++ b/webapp/frontend/src/lib/grammars/mlog_tokens.ts @@ -0,0 +1,38 @@ +import type { Stack } from '@lezer/lr'; +import { + Color, + NumberLiteral, + GlobalIdentifier, + Dialect_mindcodeEmbed +} from './mlog.grammar.terms'; + +const binaryRegex = /^[-+]?0b[01]+$/; +const hexRegex = /^[-+]?0x[0-9a-fA-F]+$/; +// mindcode's float regex is a bit different from mlog's, +// as it allows for things like 1.2e10, which mlog doesn't parse, +// conversely, mlog allows suffixes like 'f' or '.', which mindcode doesn't parse. +const mlogFloatRegex = /^[+-]?(\.\d+|\d+(\.\d+)?|\d+[eE][-+]?\d+)[fF.]?$/; +// Since this is used for highlighting purposes, +// letting mindcode floats end with [fF.] is not a big deal, +// and it allows us to only need to check one regex based on the dialect. +const mindcodeFloatRegex = /^[+-]?(\d+|\d*\.\d+)([eE][-+]?\d+)?[fF.]?$/; + +export function specializeIdentifier(symbol: string, stack: Stack) { + if (symbol.startsWith('@') && symbol.length > 1) return GlobalIdentifier; + + // color literals + if (symbol.startsWith('%') && (symbol.length === 7 || symbol.length === 9)) return Color; + + // color tag literals + if (symbol.startsWith('%[') && symbol.endsWith(']') && symbol.length > 3) return Color; + + if (binaryRegex.test(symbol)) return NumberLiteral; + if (hexRegex.test(symbol)) return NumberLiteral; + + const floatRegex = stack.dialectEnabled(Dialect_mindcodeEmbed) + ? mindcodeFloatRegex + : mlogFloatRegex; + if (floatRegex.test(symbol)) return NumberLiteral; + + return -1; +} diff --git a/webapp/frontend/src/lib/grammars/schemacode.grammar b/webapp/frontend/src/lib/grammars/schemacode.grammar new file mode 100644 index 000000000..439e0da01 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/schemacode.grammar @@ -0,0 +1,168 @@ +@top Program { definition* } + +@skip { space | LineComment | BlockComment } + +commaSep { term ("," term)* } +kw { @specialize[@name={term}] } + +definition { + SchematicDefinition | + StringAssignment +} + +SchematicDefinition { + (Identifier ":")? kw<"schematic"> schematicItem* kw<"end"> +} + +schematicItem { + Attribute | + BlockItem +} + +StringAssignment { + Identifier "=" (String | TextBlock | Identifier) +} + +Attribute { + (kw<"name"> | kw<"description"> | kw<"tag">) "=" (String | TextBlock | Identifier) | + kw<"filename"> "=" String | + kw<"dimensions"> "=" Coordinates | + kw<"target"> "=" (Int | Version) +} + +BlockItem { + (commaSep ":")? Ref kw<"at"> position Direction? Configuration? +} + +position { + Coordinates | + RelativeCoordinates | + CoordinatesRelativeTo +} + +Coordinates { + "(" Int "," Int ")" +} + +RelativeCoordinates { + ("+" | "-") Coordinates +} + +CoordinatesRelativeTo { + Identifier RelativeCoordinates +} + +Direction { + kw<"facing"> (kw<"north"> | kw<"south"> | kw<"east"> | kw<"west">) +} + +Configuration { + kw<"virtual"> | + kw<"color"> kw<"rgba"> "(" commaSep ")" | + kw<"connected"> kw<"to"> ConnectionList | + kw<"block"> Ref | + kw<"command"> Ref | + kw<"item"> Ref | + kw<"liquid"> Ref | + kw<"unit"> Ref | + kw<"text"> (String | TextBlock | Identifier) | + kw<"enabled"> | + kw<"disabled"> | + ProcessorConfiguration +} + +ConnectionList { + commaSep +} + +connection { + Coordinates | + RelativeCoordinates | + Identifier +} + +ProcessorConfiguration { + kw<"processor"> ProcessorLinks? ProcessorSourceAssignment? kw<"end"> +} + +ProcessorSourceAssignment { + (kw<"mindcode"> | kw<"mlog">) "=" ProcessorSource +} + +ProcessorLinks { + kw<"links"> LinkDef* kw<"end"> +} + +ProcessorSource { + processorSourceSnippet ("+" processorSourceSnippet)* +} + +processorSourceSnippet { + (String | TextBlock | Identifier) | FileReference { kw<"file"> (String | TextBlock | Identifier) } +} + +LinkDef { + LinkPattern | + connection (kw<"as"> Identifier kw<"virtual">?)? +} + + + +@skip {} { + String { '"' StringContent { singleLineStringContent* } singleLineStringEnd } +} + +@skip {} { + multilineSingleQuoteString { "'''" TextBlockContent { multilineSingleQuoteStringContent* } multilineSingleQuoteStringEnd } +} + +@skip {} { + multilineDoubleQuoteString { '"""' TextBlockContent { multilineDoubleQuoteStringContent* } multilineDoubleQuoteStringEnd } +} + +TextBlock { multilineSingleQuoteString | multilineDoubleQuoteString } + +@local tokens { + singleLineStringEnd { '"' } + @else singleLineStringContent +} + +@local tokens { + multilineSingleQuoteStringEnd { "'''" } + @else multilineSingleQuoteStringContent +} + +@local tokens { + multilineDoubleQuoteStringEnd { '"""' } + @else multilineDoubleQuoteStringContent +} + +@tokens { + space { @whitespace+ } + LineComment { "//" ![\n]* } + BlockComment { "/*" blockCommentRest } + blockCommentRest { ![*] blockCommentRest | "*" blockCommentAfterStar } + blockCommentAfterStar { "/" | "*" blockCommentAfterStar | ![/*] blockCommentRest } + + // the only difference between an Identifier and a LinkPattern is that + // LinkPattern can contain an asterisk, so the external specializer + // handles that for us + Identifier { $[a-zA-Z_*] $[a-zA-Z0-9_\-*]* } + Ref { "@" $[a-zA-Z_] $[a-zA-Z0-9_\-]* } + + Int { ("+" | "-")? $[0-9]+ } + // Still preserving processor specification support to not break + // syntax highlighting on old code + Version { $[0-9]+ "." $[0-9]+ $[a-zA-Z]? | $[0-9]+ $[a-zA-Z] } + + @precedence { + Version, + Int + } + + "(" ")" "," ":" "." ".." "..." "+" "-" "=" +} + +@external specialize { Identifier } specializeIdentifier from "./schemacode_tokens" { + LinkPattern +} \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/schemacode.grammar.d.ts b/webapp/frontend/src/lib/grammars/schemacode.grammar.d.ts new file mode 100644 index 000000000..ff74c6d63 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/schemacode.grammar.d.ts @@ -0,0 +1,3 @@ +import { LRParser } from "@lezer/lr"; + +export const parser: LRParser; diff --git a/webapp/frontend/src/lib/grammars/schemacode.grammar.terms.d.ts b/webapp/frontend/src/lib/grammars/schemacode.grammar.terms.d.ts new file mode 100644 index 000000000..cd845a880 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/schemacode.grammar.terms.d.ts @@ -0,0 +1 @@ +export const LinkPattern: number; diff --git a/webapp/frontend/src/lib/grammars/schemacode.grammar.test.ts b/webapp/frontend/src/lib/grammars/schemacode.grammar.test.ts new file mode 100644 index 000000000..d7f635c0a --- /dev/null +++ b/webapp/frontend/src/lib/grammars/schemacode.grammar.test.ts @@ -0,0 +1,22 @@ +import { parser } from './schemacode.grammar'; +import { fileTests } from '@lezer/generator/test'; +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { test, describe, expect } from 'vitest'; + +describe('schemacode grammar', async () => { + const directory = join(import.meta.dirname, 'test/schemacode'); + const fileNames = await readdir(directory); + const fileContents = await Promise.all( + fileNames.map((file) => readFile(join(directory, file), 'utf-8')) + ); + + const testFiles = fileContents.flatMap((file, index) => fileTests(file, fileNames[index])); + + for (const { name, run } of testFiles) { + test.concurrent(name, () => { + run(parser); + expect(true).toBe(true); // prevent "no assertions" error + }); + } +}); diff --git a/webapp/frontend/src/lib/grammars/schemacode_language.ts b/webapp/frontend/src/lib/grammars/schemacode_language.ts new file mode 100644 index 000000000..3a9f880d6 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/schemacode_language.ts @@ -0,0 +1,169 @@ +import { parser } from './schemacode.grammar'; +import { + LRLanguage, + LanguageSupport, + indentNodeProp, + foldNodeProp, + foldInside, + delimitedIndent +} from '@codemirror/language'; +import { styleTags, tags as t } from '@lezer/highlight'; +import { parseMixed, type Input, type SyntaxNode } from '@lezer/common'; +import { mlogLanguage } from './mlog_language'; +import { mindcodeLanguage } from './mindcode_language'; + +interface LookupData { + mlog: Set; + mindcode: Set; +} + +const lookupCache = new WeakMap(); + +export const schemacodeLanguage = LRLanguage.define({ + name: 'schemacode', + parser: parser.configure({ + wrap: parseMixed((ref, input) => { + if (ref.name !== 'TextBlockContent' && ref.name !== 'StringContent') { + return null; + } + + // the first parent is TextBlock or String, the second is the "true" parent + const parent = ref.node.parent?.parent; + if (parent?.name === 'ProcessorSource') { + // parent.parent is ProcessorSourceAssignment, which + // should contain either a mindcode or mlog keyword + if (parent.parent?.getChild('mindcode')) return { parser: mindcodeLanguage.parser }; + return { parser: mlogLanguage.parser }; + } + if (parent?.name === 'StringAssignment') { + let root = ref.node; + while (root.parent) { + root = root.parent; + } + + const data = getLookupData(root, input); + const identifier = parent.getChild('Identifier'); + if (!identifier) return null; + + const name = input.read(identifier.from, identifier.to); + if (data.mlog.has(name)) return { parser: mlogLanguage.parser }; + if (data.mindcode.has(name)) return { parser: mindcodeLanguage.parser }; + } + return null; + }), + props: [ + indentNodeProp.add({ + SchematicDefinition: delimitedIndent({ closing: 'end', align: false }), + BlockItem: delimitedIndent({ closing: 'end', align: false }), + ProcessorConfiguration: delimitedIndent({ closing: 'end', align: false }), + ProcessorLinks: delimitedIndent({ closing: 'end', align: false }) + }), + foldNodeProp.add({ + 'SchematicDefinition ProcessorConfiguration ProcessorLinks': foldInside, + TextBlock(node, state) { + const len = state.doc.length; + // fold everything between the """ delimiters + const from = Math.min(node.from + 3, len); + const to = Math.min(node.to - 3, len); + return { from, to }; + } + }), + styleTags({ + Identifier: t.variableName, + LinkPattern: t.regexp, + Ref: t.string, + String: t.string, + StringContent: t.string, + TextBlock: t.string, + TextBlockContent: t.string, + Int: t.integer, + Version: t.number, + + 'schematic end': t.keyword, + 'name description tag filename dimensions target': t.propertyName, + 'at facing virtual color connected to block command item liquid unit text enabled disabled processor mindcode mlog file links as': + t.keyword, + 'north south east west rgba': t.atom, + + LineComment: t.lineComment, + BlockComment: t.blockComment, + '( )': t.paren, + ', : . .. ...': t.punctuation, + '=': t.operator + }) + ] + }), + languageData: { + commentTokens: { line: '//', block: { open: '/*', close: '*/' } }, + indentOnInput: /^\s*(?:end)$/ + } +}); + +export function schemacode() { + return new LanguageSupport(schemacodeLanguage); +} + +function getLookupData(root: SyntaxNode, input: Input): LookupData { + if (lookupCache.has(root)) { + const cached = lookupCache.get(root)!; + return cached; + } + + const consumers = new Map>(); + + const cursor = root.cursor(); + + cursor.iterate((ref) => { + const node = ref.node; + if (node.name === 'ProcessorSourceAssignment') { + const sourceNode = node.getChild('ProcessorSource'); + if (!sourceNode) return; + + const children = sourceNode.getChildren('Identifier'); + // using angle brackets to avoid name collisions + const target = node.getChild('mindcode') ? '' : ''; + + for (const child of children) { + let set = consumers.get(target); + if (!set) { + set = new Set(); + consumers.set(target, set); + } + set.add(input.read(child.from, child.to)); + } + } + if (node.name === 'StringAssignment') { + const [variable, value] = node.getChildren('Identifier'); + if (!variable || !value) return; + + const consumer = input.read(variable.from, variable.to); + const consumed = input.read(value.from, value.to); + let set = consumers.get(consumer); + if (!set) { + set = new Set(); + consumers.set(consumer, set); + } + set.add(consumed); + } + }); + + function traverse(consumer: string, visited: Set, root = false) { + if (visited.has(consumer)) return; + if (!root) { + visited.add(consumer); + } + + for (const proc of consumers.get(consumer) || []) { + traverse(proc, visited); + } + } + + const mindcode = new Set(); + const mlog = new Set(); + traverse('', mindcode, true); + traverse('', mlog, true); + + const data = { mindcode, mlog }; + lookupCache.set(root, data); + return data; +} diff --git a/webapp/frontend/src/lib/grammars/schemacode_tokens.ts b/webapp/frontend/src/lib/grammars/schemacode_tokens.ts new file mode 100644 index 000000000..e7d36570c --- /dev/null +++ b/webapp/frontend/src/lib/grammars/schemacode_tokens.ts @@ -0,0 +1,7 @@ +import { LinkPattern } from './schemacode.grammar.terms'; + +export function specializeIdentifier(symbol: string) { + if (symbol.includes('*')) return LinkPattern; + + return -1; +} diff --git a/webapp/frontend/src/lib/grammars/test/mindcode/blocks.txt b/webapp/frontend/src/lib/grammars/test/mindcode/blocks.txt new file mode 100644 index 000000000..10918ef9c --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mindcode/blocks.txt @@ -0,0 +1,25 @@ +# Mlog Block + +mlog(in switch, out time) { + # here starts the mlog block + print $message # accessing an undeclared variable + printflush message1 # accessing a linked block using its implicit name + control enabled switch false # accessing a linked block using its Mindcode name + set start @second # setting an mlog variable + // Here we loop till the button is pressed. (This comment is ignored.) + loop: # a label + sensor enabled switch @enabled + jump loop notEqual enabled false + op sub time @second start # setting the output variable + # here ends the mlog block +} + +==> + +Program( + MlogBlock( + mlog, in, Identifier(...), out, Identifier(...), MlogBodyStart, + MlogBlockContent + MlogBodyEnd + ) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/mindcode/declarations.txt b/webapp/frontend/src/lib/grammars/test/mindcode/declarations.txt new file mode 100644 index 000000000..903cfdf3e --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mindcode/declarations.txt @@ -0,0 +1,25 @@ +# Allocate heap + +allocate heap in bank1; + +==> + +Program( + allocate, heap, in, Identifier(...) +) + +# Declare arrays + +var a[10]; +external $a[10]; + +==> + +Program( + VariableDeclaration( + var, VariableDeclarationItem(Identifier(SimpleIdentifier), IntLiteral) + ), + VariableDeclaration( + external, VariableDeclarationItem(Identifier(ExternalIdentifier), IntLiteral) + ) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/mindcode/expressions.txt b/webapp/frontend/src/lib/grammars/test/mindcode/expressions.txt new file mode 100644 index 000000000..9bbf1ff26 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mindcode/expressions.txt @@ -0,0 +1,35 @@ +# Case expression +f = case a + when 1 then x; y; z; + when 2, 3, 4 then y; + when 5 .. 10, 15 ... 20 then z; + else w; +end; + +g = case x +end; + +h = case z + else 0; +end; + +==> + +Program( + AssignmentExpression( + Identifier(...), UpdateOp, CaseExpression(case, Identifier(...), + CaseAlternative(when, IntLiteral, then, + Identifier(...), Identifier(...), Identifier(...), + ), + CaseAlternative(when, IntLiteral, ",", IntLiteral, ",", IntLiteral, then, + Identifier(...), + ), + CaseAlternative(when, Range(IntLiteral, "..", IntLiteral), ",", Range(IntLiteral, "...", IntLiteral), then, + Identifier(...), + ), + else, Identifier(...), + end) + ), + AssignmentExpression(Identifier(...), UpdateOp, CaseExpression(case, Identifier(...), end)), + AssignmentExpression(Identifier(...), UpdateOp, CaseExpression(case, Identifier(...), else, IntLiteral, end)) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/mindcode/functions.txt b/webapp/frontend/src/lib/grammars/test/mindcode/functions.txt new file mode 100644 index 000000000..20896d1d6 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mindcode/functions.txt @@ -0,0 +1,17 @@ +# Function definition + +void setBit(bitIndex) + MEMORY[bitIndex \ MOD] |= (1 << (bitIndex % MOD)); +end; + +==> + +Program( + FunctionDefinition( + void, Identifier(...), ParameterList(Identifier(...)), + FunctionBody( + AssignmentExpression(...), + ), + end + ) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/mindcode/loops.txt b/webapp/frontend/src/lib/grammars/test/mindcode/loops.txt new file mode 100644 index 000000000..49637b31b --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mindcode/loops.txt @@ -0,0 +1,99 @@ +# While loops + +while @unit == null do + ubind(@poly); +end; + +==> + +Program( + WhileLoop( + while, BinaryExpression(Identifier(...), CompareOp, null), do, + CallExpression(...), + end + ) +) + +# Do while loops + +do + ubind(@poly); +while @unit == null; + +==> + +Program( + DoWhileLoop( + do, + CallExpression(...), + while, BinaryExpression(Identifier(...), CompareOp, null) + ) +) + +# Range iteration loops + +var sum = 0; +for var n in firstIndex + 1 .. lastIndex - 1 do + sum += cell1[n]; +end; + +==> + +Program( + VariableDeclaration( + var, VariableDeclarationItem(Identifier(...), IntLiteral) + ), + ForLoop( + for, var, Identifier(...), in, Range(...), do, + AssignmentExpression(...), + end + ) +) + +# Descending iteration loops + +for var n in 14 ... 18 descending do + println(n); +end; +printflush(message1); + +==> + +Program( + ForLoop( + for, var, Identifier(...), in, Range(IntLiteral, "...", IntLiteral), descending, do, + CallExpression(...), + end + ), + CallExpression(...) +) + +# Implicit loop label + +loop + println("Outer 1"); + while true do + println("Inner 1"); + break loop; + println("Inner 2"); + end; + println("Outer 2"); +end; + +==> + +Program( + Loop( + loop, + CallExpression(...), + WhileLoop( + while, true, do, + CallExpression(...), + BreakStatement(break, loop), + CallExpression(...), + end + ), + CallExpression(...), + end + ) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/mindcode/toplevel.txt b/webapp/frontend/src/lib/grammars/test/mindcode/toplevel.txt new file mode 100644 index 000000000..6e70d9219 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mindcode/toplevel.txt @@ -0,0 +1,41 @@ +# Require + +require units; +require "library\drawings.mnd"; + +==> + +Program( + require, Identifier(...), + require, String +) + +# Directives + +#set option; +#set option = value; +#set option = value,value; +#set option = 7; +#declare category :keyword; +#declare category :keyword1, :keyword2; +#declare category @builtin; +#declare category @builtin1, @builtin2; +#declare category identifier; +#declare category identifier1, identifier2; +#declare category :keyword, @builtin, identifier; + +==> + +Program( + Directive("#set", DirectiveValue), + Directive("#set", DirectiveValue, DirectiveValue), + Directive("#set", DirectiveValue, DirectiveValue, DirectiveValue), + Directive("#set", DirectiveValue, DirectiveValue), + Directive("#declare", SimpleIdentifier, KeywordLiteral), + Directive("#declare", SimpleIdentifier, KeywordLiteral, KeywordLiteral), + Directive("#declare", SimpleIdentifier, Identifier(...)), + Directive("#declare", SimpleIdentifier, Identifier(...), Identifier(BuiltinIdentifier)), + Directive("#declare", SimpleIdentifier, Identifier(...)), + Directive("#declare", SimpleIdentifier, Identifier(...), Identifier(SimpleIdentifier)), + Directive("#declare", SimpleIdentifier, KeywordLiteral, Identifier(...), Identifier(...)) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/mlog/embedded.txt b/webapp/frontend/src/lib/grammars/test/mlog/embedded.txt new file mode 100644 index 000000000..347ce6ddf --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mlog/embedded.txt @@ -0,0 +1,15 @@ +# Comments { "dialect": "mindcodeEmbed" } + +# Regular comment +// Mindcode comment +set count 0 # this should work +set count 1 // this should also work + +==> + +Program( + Comment, + MindcodeComment, + Instruction(...), Comment, + Instruction(...), MindcodeComment +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/mlog/labels.txt b/webapp/frontend/src/lib/grammars/test/mlog/labels.txt new file mode 100644 index 000000000..b008c3c26 --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/mlog/labels.txt @@ -0,0 +1,22 @@ +# Labels + +set count 0 + +main: # comment + print count + print "\n" + op add count count 1 + jump main lessThan count 10 + printflush message1 + +==> + +Program( + Instruction(...), + LabelDeclaration, Comment, + Instruction(...), + Instruction(...), + Instruction(...), + Instruction(...), + Instruction(...) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/grammars/test/schemacode/general.txt b/webapp/frontend/src/lib/grammars/test/schemacode/general.txt new file mode 100644 index 000000000..00e04a4ba --- /dev/null +++ b/webapp/frontend/src/lib/grammars/test/schemacode/general.txt @@ -0,0 +1,367 @@ +# Comments +// This is a line comment +/** + This is a block comment +*/ + +==> + +Program( + LineComment, + BlockComment +) + +# Single line strings are accepted where they should +str = "string definition" + +schematic + name = "Test schematic" + description = "ingle line description" + tag = "string tag" + filename = "test.msch" + + @message at (0, 0) text "Message text content" + @micro-processor at (0, 1) processor + mlog = "printchar @router; printflush message1" + end + @micro-processor at (0, 2) processor + mlog = file "source.mlog" + end +end + +==> + +Program( + StringAssignment(Identifier, "=", String(...)), + SchematicDefinition(schematic, + Attribute(name, "=", String(...)), + Attribute(description, "=", String(...)), + Attribute(tag, "=", String(...)), + Attribute(filename, "=", String(...)), + BlockItem(Ref, at, Coordinates("(", Int, ",", Int, ")"), Configuration(text, String(...))), + BlockItem(Ref, at, Coordinates("(", Int, ",", Int, ")"), Configuration( + ProcessorConfiguration( + processor, + ProcessorSourceAssignment( + mlog, "=", ProcessorSource(String(...)) + ), + end + )) + ), + BlockItem(Ref, at, Coordinates("(", Int, ",", Int, ")"), Configuration( + ProcessorConfiguration( + processor, + ProcessorSourceAssignment( + mlog, "=", ProcessorSource(FileReference(file, String(...))) + ), + end + )) + ), + end) +) + +# Text blocks are accepted where they should +str = """string definition""" + +schematic + name = """Test schematic""" + description = """Single line description""" + tag = """string tag""" + + @message at (0, 0) text """Message text content""" + @micro-processor at (0, 1) processor + mlog = """printchar @router; printflush message1""" + end + @micro-processor at (0, 2) processor + mlog = file """source.mlog""" + end +end + +==> + +Program( + StringAssignment(Identifier, "=", TextBlock(...)), + SchematicDefinition(schematic, + Attribute(name, "=", TextBlock(...)), + Attribute(description, "=", TextBlock(...)), + Attribute(tag, "=", TextBlock(...)), + BlockItem(Ref, at, Coordinates("(", Int, ",", Int, ")"), Configuration(text, TextBlock(...))), + BlockItem(Ref, at, Coordinates("(", Int, ",", Int, ")"), Configuration( + ProcessorConfiguration( + processor, + ProcessorSourceAssignment( + mlog, "=", ProcessorSource(TextBlock(...)) + ), + end + )) + ), + BlockItem(Ref, at, Coordinates("(", Int, ",", Int, ")"), Configuration( + ProcessorConfiguration( + processor, + ProcessorSourceAssignment( + mlog, "=", ProcessorSource(FileReference(file, TextBlock(...))) + ), + end + )) + ), + end) +) + +# Dimensions +schematic + dimensions = (10, 15) +end + +==> + +Program( + SchematicDefinition(schematic, + Attribute(dimensions, "=", Coordinates("(" Int, ",", Int ")")) + end) +) + +# Relative positions +schematic + @message at (0, 0) + @micro-processor at +(0, 1) + @micro-processor at -(1, 1) +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...)), + BlockItem(Ref, at, RelativeCoordinates("+", Coordinates(...))), + BlockItem(Ref, at, RelativeCoordinates("-", Coordinates(...))), + end) +) + +# Coordinates relative to named blocks +schematic + center: @router at (2, 2) + @conveyor at center +(1, 0) + @titanium-conveyor at center -(0, 1) +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Identifier, ":", Ref, at, Coordinates(...)), + BlockItem(Ref, at, CoordinatesRelativeTo(...)), + BlockItem(Ref, at, CoordinatesRelativeTo(...)) + end) +) + +# Facing directions +schematic + @conveyor at (0, 1) facing north + @conveyor at (1, 0) facing east + @conveyor at (0, -1) facing south + @conveyor at (-1, 0) facing west +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Direction(facing, north)), + BlockItem(Ref, at, Coordinates(...), Direction(facing, east)), + BlockItem(Ref, at, Coordinates(...), Direction(facing, south)), + BlockItem(Ref, at, Coordinates(...), Direction(facing, west)), + end) +) + +# Virtual blocks +schematic + @switch at (0, 0) virtual +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Configuration(virtual)), + end) +) + +# Color configuration +schematic + @illuminator at (0, 0) color rgba (255, 128, 64, 200) +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Configuration( + color, rgba, "(", Int, ",", Int, ",", Int, ",", Int, ")") + ), + end) +) + +# Connected to configuration +schematic + @power-node at (0, 0) connected to (1, 0), (2, 0), +(1, 1) +end +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Configuration( + connected, to, ConnectionList(Coordinates(...), ",", Coordinates(...), ",", RelativeCoordinates(...)))), + end) +) + +# Block configuration types +schematic + @unloader at (0, 0) item @copper + @mass-driver at (1, 0) block @mass-driver + @command-center at (2, 0) command @flare + @liquid-source at (3, 0) liquid @water + @unit-factory at (4, 0) unit @dagger +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Configuration(item, Ref)), + BlockItem(Ref, at, Coordinates(...), Configuration(block, Ref)), + BlockItem(Ref, at, Coordinates(...), Configuration(command, Ref)), + BlockItem(Ref, at, Coordinates(...), Configuration(liquid, Ref)), + BlockItem(Ref, at, Coordinates(...), Configuration(unit, Ref)), + end) +) + +# Disabled state +schematic + @switch at (0, 0) disabled +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Configuration(disabled)), + end) +) + +# Link patterns +schematic + @micro-processor at (0, 0) processor + links + switch* + cell* + node1 as virtual_node virtual + end + end +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Configuration(ProcessorConfiguration(processor, + ProcessorLinks(links, + LinkDef(LinkPattern), + LinkDef(LinkPattern), + LinkDef(Identifier, as, Identifier, virtual), + end), + end))), + end) +) + +# Concatenated processor sources +schematic + @micro-processor at (0, 0) processor + mlog = "print 1" + file "part2.mlog" + """ + print 3 + printflush message1 + """ + end +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Ref, at, Coordinates(...), Configuration(ProcessorConfiguration(processor, + ProcessorSourceAssignment(mlog ,"=", ProcessorSource( + String(...), "+", + FileReference(file, String(...)), "+", + TextBlock(TextBlockContent)) + ), + end))), + end) +) + +# Tags +schematic + tag = "Defense" + tag = "Power" + tag = ROUTER + tag = BLOCK_CORE_NUCLEUS +end + +==> + +Program( + SchematicDefinition(schematic, + Attribute(tag, "=", String(...)), + Attribute(tag, "=", String(...)), + Attribute(tag, "=", Identifier), + Attribute(tag, "=", Identifier), + end) +) + +# Multiple labels and virtual links +schematic + power1, power2: @battery-large at (0, 0) + msg: @message at (1, 0) text "Alert!" + + @micro-processor at (2, 0) processor + links + power1 + power2 as batteries + msg as message virtual + end + mlog = "set done true" + end +end + +==> + +Program( + SchematicDefinition(schematic, + BlockItem(Identifier, ",", Identifier, ":", Ref, at, Coordinates(...)), + BlockItem(Identifier,":", Ref, at, Coordinates(...), Configuration(text, String(...))), + BlockItem(Ref, at, Coordinates(...), Configuration(ProcessorConfiguration(processor, + ProcessorLinks(links, + LinkDef(Identifier), + LinkDef(Identifier, as, Identifier), + LinkDef(Identifier, as, Identifier, virtual), + end), + ProcessorSourceAssignment(mlog, "=", ProcessorSource(String(...))), + end))), + end) +) + +# Target versions + +schematic + target = 6 + target = 7 + target = 8 +end + +==> + +Program( + SchematicDefinition(schematic, + Attribute(target, "=", Int), + Attribute(target, "=", Int), + Attribute(target, "=", Int), + end) +) \ No newline at end of file diff --git a/webapp/frontend/src/lib/mlog_watcher/channel.ts b/webapp/frontend/src/lib/mlog_watcher/channel.ts new file mode 100644 index 000000000..c5a79466d --- /dev/null +++ b/webapp/frontend/src/lib/mlog_watcher/channel.ts @@ -0,0 +1,58 @@ +const mlogWatcherChannelErrorTag = Symbol('MlogWatcherChannelError'); + +export type MlogWatcherChannelError = { + [mlogWatcherChannelErrorTag]: true; +} & ({ type: 'disconnected' } | { type: 'connectionError' }); + +export class MlogWatcherChannel { + private socket: WebSocket; + ready: Promise; + + constructor(port: number) { + this.socket = new WebSocket(`ws://localhost:${port}`); + + this.ready = new Promise((resolve, reject) => { + this.socket.onopen = () => { + resolve(); + }; + + this.socket.onclose = () => { + reject(channelError({ type: 'disconnected' })); + }; + + this.socket.onerror = () => { + reject(channelError({ type: 'connectionError' })); + }; + }); + } + + private assertSocketOpen(socket: WebSocket) { + if (socket.readyState === WebSocket.OPEN) return; + throw channelError({ type: 'disconnected' }); + } + + async send(mlog: string) { + await this.ready; + this.assertSocketOpen(this.socket); + this.socket.send(mlog); + } + + close() { + this.socket.close(); + } +} + +export function isMlogWatcherChannelError(value: unknown): value is MlogWatcherChannelError { + if (!value || typeof value !== 'object') return false; + return mlogWatcherChannelErrorTag in value && value[mlogWatcherChannelErrorTag] === true; +} + +/** Helper function to create mlog watcher channel errors with strong typing. */ +function channelError( + error: Omit +): MlogWatcherChannelError { + return { + [mlogWatcherChannelErrorTag]: true, + ...error + }; +} diff --git a/webapp/frontend/src/lib/mlog_watcher/completer.ts b/webapp/frontend/src/lib/mlog_watcher/completer.ts new file mode 100644 index 000000000..28cc43e83 --- /dev/null +++ b/webapp/frontend/src/lib/mlog_watcher/completer.ts @@ -0,0 +1,23 @@ +/** A wrapper around a promise that makes it easy to programatically resolve a value. */ +export class Completer { + readonly promise: Promise; + hasResolved = false; + private resolver!: (value: T) => void; + private rejecter!: (reason?: unknown) => void; + + constructor() { + this.promise = new Promise((resolve, reject) => { + this.resolver = resolve; + this.rejecter = reject; + }); + } + + complete(value: T) { + this.hasResolved = true; + this.resolver(value); + } + + reject(reason?: unknown) { + this.rejecter(reason); + } +} diff --git a/webapp/frontend/src/lib/mlog_watcher/index.ts b/webapp/frontend/src/lib/mlog_watcher/index.ts new file mode 100644 index 000000000..a7a48e3da --- /dev/null +++ b/webapp/frontend/src/lib/mlog_watcher/index.ts @@ -0,0 +1,2 @@ +export { MlogWatcherChannel, isMlogWatcherChannelError } from './channel'; +export { MlogWatcherStore } from './store.svelte'; diff --git a/webapp/frontend/src/lib/mlog_watcher/store.svelte.ts b/webapp/frontend/src/lib/mlog_watcher/store.svelte.ts new file mode 100644 index 000000000..91e28037b --- /dev/null +++ b/webapp/frontend/src/lib/mlog_watcher/store.svelte.ts @@ -0,0 +1,47 @@ +import { toast } from 'svelte-sonner'; +import { isMlogWatcherChannelError, MlogWatcherChannel } from './channel'; +import { Completer } from './completer'; + +/** + * Manages a lazily initialized MlogWatcherChannel. + * Integrates with the app's toaster to show error notifications. + */ +export class MlogWatcherStore { + private channel = new Completer(); + #initialized = $state(false); + + constructor(port: () => number) { + $effect.pre(() => { + port(); + this.#initialized = false; + }); + + $effect.pre(() => { + if (!this.#initialized) return; + const channel = new MlogWatcherChannel(port()); + this.channel.complete(channel); + + channel.ready.catch((error) => { + this.channel = new Completer(); + this.#initialized = false; + toast.error( + 'Mlog Watcher connection failed. Please ensure the Mlog Watcher server is running and the port is correct.' + ); + if (!isMlogWatcherChannelError(error)) { + console.error('Mlog Watcher connection error:', error); + } + }); + + return () => { + channel.close(); + this.channel = new Completer(); + }; + }); + } + + async send(mlog: string) { + this.#initialized = true; + const channel = await this.channel.promise; + await channel.send(mlog); + } +} diff --git a/webapp/frontend/src/lib/server.ts b/webapp/frontend/src/lib/server.ts new file mode 100644 index 000000000..40117850e --- /dev/null +++ b/webapp/frontend/src/lib/server.ts @@ -0,0 +1,151 @@ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Sample } from './api'; + +interface SampleMetadata { + filename: string; + title: string; + runnable: boolean; +} + +/** + * Finds the project root directory by looking for the root pom.xml file. + * Starts from the current file's directory and walks up the directory tree. + * + * @returns The absolute path to the project root directory + * @throws Error if the project root cannot be found + */ +export async function findProjectRoot(): Promise { + // Start from this file's directory + const currentDir = dirname(fileURLToPath(import.meta.url)); + let dir = currentDir; + + // Walk up the directory tree looking for the root pom.xml + // The root pom.xml contains pom and the modules list + while (dir !== dirname(dir)) { + // Stop at filesystem root + const pomPath = join(dir, 'pom.xml'); + if (existsSync(pomPath)) { + const content = await readFile(pomPath, 'utf-8'); + // Check if this is the root pom (has pom and ) + if (content.includes('pom') && content.includes('')) { + return dir; + } + } + dir = dirname(dir); + } + + throw new Error( + 'Could not find project root. Make sure you are running from within the mindcode project.' + ); +} + +/** + * Reads the revision (version) from the root pom.xml file. + * + * @returns The revision string (e.g., "3.13.0") + * @throws Error if the revision cannot be read + */ +export async function getProjectRevision(): Promise { + const projectRoot = await findProjectRoot(); + const pomPath = join(projectRoot, 'pom.xml'); + const content = await readFile(pomPath, 'utf-8'); + + // Extract revision from X.Y.Z + const match = content.match(/([^<]+)<\/revision>/); + if (!match) { + throw new Error('Could not find in pom.xml'); + } + + return match[1]; +} + +/** + * Reads mindcode samples from the samples directory. + * + * @returns Array of Sample objects with id, title, and source + */ +export async function getMindcodeSamples(): Promise { + const projectRoot = await findProjectRoot(); + const samplesDir = join(projectRoot, 'samples', 'src', 'main', 'resources', 'samples'); + + const metadataPath = join(samplesDir, 'mindcode.json'); + const metadataContent = await readFile(metadataPath, 'utf-8'); + const metadata: SampleMetadata[] = JSON.parse(metadataContent); + + const sources = await readSamplesFromDirectory( + join(samplesDir, 'mindcode'), + metadata.map((m) => m.filename) + ); + + return metadata.map((meta, index) => { + const id = meta.filename.slice(0, meta.filename.lastIndexOf('.')); + const { title, runnable } = meta; + const source = sources[index]; + return { id, title, source, runnable }; + }); +} + +/** + * Reads schemacode samples from the samples directory. + * + * @returns Array of Sample objects with id, title, and source + */ +export async function getSchemacodeSamples(): Promise { + const projectRoot = await findProjectRoot(); + const samplesDir = join(projectRoot, 'samples', 'src', 'main', 'resources', 'samples'); + const metadataPath = join(samplesDir, 'schematics.json'); + const metadataContent = await readFile(metadataPath, 'utf-8'); + const metadata: SampleMetadata[] = JSON.parse(metadataContent); + + const sources = await readSamplesFromDirectory( + join(samplesDir, 'schematics'), + metadata.map((m) => m.filename) + ); + + return metadata.map((meta, index) => { + const id = meta.filename.slice(0, meta.filename.lastIndexOf('.')); + const { title, runnable } = meta; + const source = sources[index]; + return { id, title, source, runnable }; + }); +} + +/** + * Reads all sample files from a directory. + * + * @param dir The directory to read samples from + * @param extension The file extension to filter by + * @returns Array of Sample objects + */ +async function readSamplesFromDirectory(dir: string, files: string[]): Promise { + if (!existsSync(dir)) { + console.warn(`Samples directory not found: ${dir}`); + return []; + } + + return Promise.all( + files.map(async (file): Promise => { + const filePath = join(dir, file); + const source = await readFile(filePath, 'utf-8'); + return source; + }) + ); +} + +/** + * Gets the full version string for display. + * + * @returns Version string like "Mindcode 3.13.0" + */ +export async function getVersionString(): Promise { + try { + const revision = await getProjectRevision(); + return `Mindcode ${revision}`; + } catch (e) { + console.warn('Could not read project revision:', e); + return 'Mindcode'; + } +} diff --git a/webapp/frontend/src/lib/settings.svelte.ts b/webapp/frontend/src/lib/settings.svelte.ts new file mode 100644 index 000000000..ee892f994 --- /dev/null +++ b/webapp/frontend/src/lib/settings.svelte.ts @@ -0,0 +1,111 @@ +import { browser } from '$app/environment'; +import { getContext, setContext, tick } from 'svelte'; +import { toast } from 'svelte-sonner'; + +const storageKey = 'mindcode-settings'; + +export interface Settings { + mlogWatcherPort: number; + lineWrapping: boolean; +} + +const defaultSettings: Settings = { + mlogWatcherPort: 9992, + lineWrapping: true +}; + +/** + * Manages application settings with automatic persistence to localStorage. + * Settings are reactive and automatically saved when changed. + */ +export class SettingsStore { + mlogWatcherPort = $state(defaultSettings.mlogWatcherPort); + lineWrapping = $state(defaultSettings.lineWrapping); + + constructor() { + if (!browser) return; + + this.#loadSettings(); + + // Auto-save settings when they change + $effect(() => { + const currentSettings: Settings = { + mlogWatcherPort: this.mlogWatcherPort, + lineWrapping: this.lineWrapping + }; + + this.#saveSettings(currentSettings); + }); + } + + /** + * Load settings from localStorage + */ + #loadSettings() { + try { + const stored = localStorage.getItem(storageKey); + if (stored) { + const parsed = JSON.parse(stored) as Partial; + + // Apply loaded settings with defaults as fallback + this.mlogWatcherPort = parsed.mlogWatcherPort ?? defaultSettings.mlogWatcherPort; + this.lineWrapping = parsed.lineWrapping ?? defaultSettings.lineWrapping; + } + } catch (error) { + console.error('Failed to load settings from localStorage:', error); + tick().then(() => toast.error('Failed to load settings. Using defaults.')); + } + } + + /** + * Save settings to localStorage + */ + #saveSettings(settings: Settings) { + try { + localStorage.setItem(storageKey, JSON.stringify(settings)); + } catch (error) { + console.error('Failed to save settings to localStorage:', error); + tick().then(() => toast.error('Failed to save settings. Changes may not persist.')); + } + } + + /** + * Reset all settings to defaults + */ + reset() { + this.mlogWatcherPort = defaultSettings.mlogWatcherPort; + this.lineWrapping = defaultSettings.lineWrapping; + } + + /** + * Export current settings as JSON + */ + export(): Settings { + return { + mlogWatcherPort: this.mlogWatcherPort, + lineWrapping: this.lineWrapping + }; + } + + /** + * Import settings from JSON + */ + import(settings: Partial) { + if (settings.mlogWatcherPort !== undefined) { + this.mlogWatcherPort = settings.mlogWatcherPort; + } + if (settings.lineWrapping !== undefined) { + this.lineWrapping = settings.lineWrapping; + } + } +} + +const settingsContextKey = 'settingsStore'; + +export function setSettingsContext() { + setContext(settingsContextKey, new SettingsStore()); +} + +export function getSettingsContext(): SettingsStore { + return getContext(settingsContextKey); +} diff --git a/webapp/frontend/src/lib/stores.svelte.ts b/webapp/frontend/src/lib/stores.svelte.ts new file mode 100644 index 000000000..4d77170b9 --- /dev/null +++ b/webapp/frontend/src/lib/stores.svelte.ts @@ -0,0 +1,90 @@ +import { page } from '$app/state'; +import { getContext, setContext } from 'svelte'; +import { goto } from '$app/navigation'; +import { browser } from '$app/environment'; + +export const sourceIdKey = 's'; +export const compilerTargetKey = 'compilerTarget'; +export const defaultGameVersion = '7'; +export const defaultProcessorType = 'm'; + +export class LocalCompilerTarget { + value: string; + + constructor(public defaultValue: string = defaultGameVersion) { + this.value = $derived( + browser ? page.url.searchParams.get(compilerTargetKey) || defaultValue : defaultValue + ); + } + + updateParams(searchParams: URLSearchParams) { + searchParams.set(compilerTargetKey, this.value); + } + + resetToDefault() { + this.value = this.defaultValue; + } +} + +function updateParams(searchParams: URLSearchParams, key: string, value: string | null) { + if (value !== null) { + searchParams.set(key, value); + } else { + searchParams.delete(key); + } +} + +export async function syncUrl({ + sourceId, + compilerTarget, + replaceState = true +}: { + sourceId?: string | null; + compilerTarget?: string; + replaceState?: boolean; +}) { + const url = new URL(page.url); + + if ( + (sourceId === undefined || url.searchParams.get(sourceIdKey) === sourceId) && + (!compilerTarget || url.searchParams.get(compilerTargetKey) === compilerTarget) + ) { + return; + } + + if (sourceId !== undefined) { + updateParams(url.searchParams, sourceIdKey, sourceId); + } + if (compilerTarget !== undefined) { + updateParams(url.searchParams, compilerTargetKey, compilerTarget); + } + + await goto(url, { replaceState, noScroll: true, keepFocus: true }); +} + +export class ThemeStore { + isDark = $state(false); + + constructor() { + if (!browser) return; + this.isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + $effect(() => { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const onChange = (e: MediaQueryListEvent) => { + this.isDark = e.matches; + }; + + mediaQuery.addEventListener('change', onChange); + return () => mediaQuery.removeEventListener('change', onChange); + }); + } +} + +export function setThemeContext() { + setContext('themeStore', new ThemeStore()); +} + +export function getThemeContext(): ThemeStore { + return getContext('themeStore'); +} diff --git a/webapp/frontend/src/lib/utils.ts b/webapp/frontend/src/lib/utils.ts new file mode 100644 index 000000000..55b3a918e --- /dev/null +++ b/webapp/frontend/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/webapp/frontend/src/routes/+error.svelte b/webapp/frontend/src/routes/+error.svelte new file mode 100644 index 000000000..c99e482d4 --- /dev/null +++ b/webapp/frontend/src/routes/+error.svelte @@ -0,0 +1,25 @@ + + +
+ + +
+ +
+ Error {page.status} + {page.error?.message ?? 'An unexpected error occurred'} +
+ + + +
+
diff --git a/webapp/frontend/src/routes/+layout.server.ts b/webapp/frontend/src/routes/+layout.server.ts new file mode 100644 index 000000000..8e3f5ce4d --- /dev/null +++ b/webapp/frontend/src/routes/+layout.server.ts @@ -0,0 +1,8 @@ +import { getVersionString } from '$lib/server'; + +export const prerender = true; + +export const load = async () => { + const version = await getVersionString(); + return { version }; +}; diff --git a/webapp/frontend/src/routes/+layout.svelte b/webapp/frontend/src/routes/+layout.svelte new file mode 100644 index 000000000..c778ac9d3 --- /dev/null +++ b/webapp/frontend/src/routes/+layout.svelte @@ -0,0 +1,113 @@ + + + + + + + + + +
+
+
+
+

Mindcode

+ + + + {tools.find((t) => t.value === currentTool)?.label || 'Select tool...'} + + + {#each tools as tool} + + {tool.label} + + {/each} + + +
+ + +
+
+ +
+ + {@render children()} + +
+ +
+
+
+
+ {data.version} +
+
+ PRIVACY POLICY: This website does not track its users. The Mindcode and + schematics you submit for compilation/decompilation is kept for later analysis. No other + information is kept about you or your actions on the site. +
+
+ Created by François (GitHub, + Twitter).
+ Maintained by + Cardillan. +
+
+
+
+
diff --git a/webapp/frontend/src/routes/+page.server.ts b/webapp/frontend/src/routes/+page.server.ts new file mode 100644 index 000000000..3a9ce1aa2 --- /dev/null +++ b/webapp/frontend/src/routes/+page.server.ts @@ -0,0 +1,6 @@ +import { getMindcodeSamples } from '$lib/server'; + +export const load = async () => { + const samples = await getMindcodeSamples(); + return { samples }; +}; diff --git a/webapp/frontend/src/routes/+page.svelte b/webapp/frontend/src/routes/+page.svelte new file mode 100644 index 000000000..c496efa89 --- /dev/null +++ b/webapp/frontend/src/routes/+page.svelte @@ -0,0 +1,240 @@ + + + + Mindcode: a Mindustry Logic high-level language + + +
+ + + + +
+ + +
+ + + + {#snippet inputActions()} + + + + {/snippet} + {#snippet outputActions(showingCode)} + mlogEditor.view?.state.doc.toString() ?? ''} + disabled={!showingCode} + /> + {/snippet} + + + + compile(false) + }} + secondaryAction={{ + label: 'Compile and Run', + icon: Play, + onclick: () => compile(true) + }} + loading={mindcodeEditor.isLoading || loadingAction !== null} + /> + +
diff --git a/webapp/frontend/src/routes/decompiler/+page.svelte b/webapp/frontend/src/routes/decompiler/+page.svelte new file mode 100644 index 000000000..c41966b14 --- /dev/null +++ b/webapp/frontend/src/routes/decompiler/+page.svelte @@ -0,0 +1,178 @@ + + + + Mindcode: Mlog decompiler + + +
+ + +

+ Here you can partially decompile an mlog code into Mindcode. The resulting code cannot be directly compiled by Mindcode. + Jump instructions in the original mlog are transcribed as if + and goto statements which aren't supported by Mindcode and must be rewritten using + conditional statements, loops and other high-level constructs. The decompiler is mainly useful to + produce expressions and function calls in the correct Mindcode syntax, saving some time and possibly + helping to avoid some mistakes compared to a manual rewrite of the entire mlog code from scratch. +

+
+ + + + + +
+ +
+ + + + {#snippet inputActions()} + + + + {/snippet} + + + + handleDecompile(false) + }} + secondaryAction={{ + label: 'Decompile and Run', + icon: Play, + onclick: () => handleDecompile(true) + }} + loading={mlogEditor.isLoading || loadingAction !== null} + /> + +
diff --git a/webapp/frontend/src/routes/layout.css b/webapp/frontend/src/routes/layout.css new file mode 100644 index 000000000..b3ed64c2c --- /dev/null +++ b/webapp/frontend/src/routes/layout.css @@ -0,0 +1,128 @@ +@import 'tailwindcss'; + +@import 'tw-animate-css'; + +@custom-variant dark (&:is(.dark *)); + +:root { + /* app theme */ + --editor-height: 60vh; + --editor-height: 60dvh; /* use dynamic viewport units if supported, fallback to regular vh otherwise */ + + /* shadcn theme */ + --radius: 0.65rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.606 0.25 292.717); + --primary-foreground: oklch(0.969 0.016 293.756); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.606 0.25 292.717); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.606 0.25 292.717); + --sidebar-primary-foreground: oklch(0.969 0.016 293.756); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.606 0.25 292.717); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.541 0.281 293.009); + --primary-foreground: oklch(0.969 0.016 293.756); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.541 0.281 293.009); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.541 0.281 293.009); + --sidebar-primary-foreground: oklch(0.969 0.016 293.756); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.541 0.281 293.009); + } +} + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/webapp/frontend/src/routes/schematics/+page.server.ts b/webapp/frontend/src/routes/schematics/+page.server.ts new file mode 100644 index 000000000..88d936476 --- /dev/null +++ b/webapp/frontend/src/routes/schematics/+page.server.ts @@ -0,0 +1,6 @@ +import { getSchemacodeSamples } from '$lib/server'; + +export const load = async () => { + const samples = await getSchemacodeSamples(); + return { samples }; +}; diff --git a/webapp/frontend/src/routes/schematics/+page.svelte b/webapp/frontend/src/routes/schematics/+page.svelte new file mode 100644 index 000000000..58b41e131 --- /dev/null +++ b/webapp/frontend/src/routes/schematics/+page.svelte @@ -0,0 +1,206 @@ + + + + Mindcode: Schematics editor + + +
+ + +

+ Here you can compile a schematics definition written in Schemacode, a schema definition language, into a text representation to be pasted into Mindustry using + the Import schematics... button. If your schematics contain processors, you + can specify code for the processor using either Mindustry Logic or Mindcode. You can also + decompile an existing schematics using the + Decompiler and modify the resulting code. +

+
+ + + + +
+ + +
+ + + {#snippet inputActions()} + + + + {/snippet} + + + + handleBuild(false) + }} + secondaryAction={{ + label: 'Build and Run', + icon: Play, + onclick: () => handleBuild(true) + }} + loading={schemacodeEditor.isLoading || loadingAction !== null} + /> + +
diff --git a/webapp/frontend/src/routes/schematics/decompiler/+page.svelte b/webapp/frontend/src/routes/schematics/decompiler/+page.svelte new file mode 100644 index 000000000..fc6a094c2 --- /dev/null +++ b/webapp/frontend/src/routes/schematics/decompiler/+page.svelte @@ -0,0 +1,180 @@ + + + + Mindcode: Schematics decompiler + + +
+ +

+ Here you can decompile schematics copied from Mindustry into Schemacode, a schema definition + language, which can then be modified and compiled back into a schema again. Press the Export + button on a Mindustry schematic, choose Copy to clipboard and paste the text + into the left pane. Then press Decompile. You can compile the Schematic + definition back to Mindustry schematics on the + Schematics Builder page. +

+

If your schematic contains processor(s), the code is decompiled into mlog.

+
+ + + + + +
+ +
+ + + + {#snippet inputActions()} + + + + {/snippet} + + + + handleDecompile(false) + }} + secondaryAction={{ + label: 'Decompile and Run', + icon: Play, + onclick: () => handleDecompile(true) + }} + loading={encodedEditor.isLoading || loadingAction !== null} + /> + +
diff --git a/webapp/frontend/static/robots.txt b/webapp/frontend/static/robots.txt new file mode 100644 index 000000000..b6dd6670c --- /dev/null +++ b/webapp/frontend/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/webapp/frontend/svelte.config.js b/webapp/frontend/svelte.config.js new file mode 100644 index 000000000..299afa161 --- /dev/null +++ b/webapp/frontend/svelte.config.js @@ -0,0 +1,23 @@ +import { mdsvex } from 'mdsvex'; +import adapter from '@sveltejs/adapter-static'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ + // Output directly into Spring Boot's static folder + pages: '../src/main/resources/static', + assets: '../src/main/resources/static', + // SPA fallback so client routing works for non-prerendered routes + fallback: '200.html' + }), + prerender: { + // Crawl and prerender all reachable pages + entries: ['*'] + } + }, + preprocess: [mdsvex()], + extensions: ['.svelte', '.svx'] +}; + +export default config; diff --git a/webapp/frontend/tsconfig.json b/webapp/frontend/tsconfig.json new file mode 100644 index 000000000..f147694e8 --- /dev/null +++ b/webapp/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler", + "noUnusedLocals": true + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/webapp/frontend/vite.config.ts b/webapp/frontend/vite.config.ts new file mode 100644 index 000000000..e6d1a208f --- /dev/null +++ b/webapp/frontend/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vitest/config'; +import tailwindcss from '@tailwindcss/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { lezer } from '@lezer/generator/rollup'; + +export default defineConfig({ + plugins: [tailwindcss(), lezer(), sveltekit()], + server: { proxy: { '/api': 'http://localhost:8080' } }, + test: { + expect: { requireAssertions: true }, + projects: [ + { + extends: './vite.config.ts', + test: { + name: 'server', + environment: 'node', + include: ['src/**/*.{test,spec}.{js,ts}'], + exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'] + } + } + ] + } +}); diff --git a/webapp/pom.xml b/webapp/pom.xml index 77a0cab66..b7f8853de 100644 --- a/webapp/pom.xml +++ b/webapp/pom.xml @@ -21,8 +21,82 @@ 22 UTF-8 3.4.0 + false + + + heroku + + + env.HEROKU + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.4.0 + + + clean-frontend-after-build + verify + + clean + + + true + + + frontend + + + target + + **/* + + + mindcode-webapp.jar + + + + ../annotations + + + ../bin + + + ../compiler + + + ../doc + + + ../exttest + + + ../java + + + ../schemacode + + + ../support + + + ../toolapp + + + + + + + + + + + @@ -121,10 +195,59 @@ + + com.github.eirslett + frontend-maven-plugin + 1.15.1 + + frontend + v22.22.0 + ${skipFrontend} + + + + install-node-and-npm + + install-node-and-npm + + generate-resources + + + npm-install + + npm + + generate-resources + + ci + + + + npm-build + + npm + + generate-resources + + run build + + + + org.springframework.boot spring-boot-maven-plugin ${spring.version} + + mindcode-webapp + + + + + repackage + + + org.apache.maven.plugins diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/ApiController.java b/webapp/src/main/java/info/teksol/mindcode/webapp/ApiController.java new file mode 100644 index 000000000..2463d92c1 --- /dev/null +++ b/webapp/src/main/java/info/teksol/mindcode/webapp/ApiController.java @@ -0,0 +1,436 @@ +package info.teksol.mindcode.webapp; + +import info.teksol.mc.common.CompilerOutput; +import info.teksol.mc.common.InputFiles; +import info.teksol.mc.emulator.*; +import info.teksol.mc.emulator.blocks.BlockPosition; +import info.teksol.mc.emulator.blocks.LogicBlock; +import info.teksol.mc.emulator.mimex.BasicEmulator; +import info.teksol.mc.messages.*; +import info.teksol.mc.mindcode.compiler.MindcodeCompiler; +import info.teksol.mc.mindcode.compiler.generation.variables.StandardNameCreator; +import info.teksol.mc.mindcode.decompiler.MlogDecompiler; +import info.teksol.mc.mindcode.logic.instructions.InstructionProcessor; +import info.teksol.mc.mindcode.logic.instructions.InstructionProcessorFactory; +import info.teksol.mc.mindcode.logic.instructions.LogicInstruction; +import info.teksol.mc.mindcode.logic.mimex.MindustryMetadata; +import info.teksol.mc.mindcode.logic.opcodes.Opcode; +import info.teksol.mc.profile.CompilerProfile; +import info.teksol.mc.profile.options.Target; +import info.teksol.mindcode.samples.Sample; +import info.teksol.mindcode.samples.Samples; +import info.teksol.schemacode.SchemacodeCompiler; +import info.teksol.schemacode.SchematicsDecompiler; +import info.teksol.schemacode.SchematicsMetadata; +import info.teksol.schemacode.mindustry.SchematicsIO; +import info.teksol.schemacode.schematics.Schematic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api") +public class ApiController { + private static final Logger logger = LoggerFactory.getLogger(ApiController.class); + private static final Map mindcodeSamples = Samples.loadMindcodeSamples(); + private static final Map schemacodeSamples = Samples.loadSchemacodeSamples(); + + @Autowired + private SourceRepository sourceRepository; + + @GetMapping("/source/{id}") + public Source getSource(@PathVariable String id) { + try { + return sourceRepository.findById(UUID.fromString(id)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Source not found")); + } catch (IllegalArgumentException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid ID format"); + } + } + + @PostMapping("/compile") + public CompileResponse compile(@RequestBody CompileRequest request) { + Target target = Target.fromString(request.target); + boolean run = request.run(); + ApiSource apiSource = getApiSource(request.sourceId, request.source, GetSourceMode.compileMindcode); + + ListMessageLogger messageLogger = new ListMessageLogger(); + MindcodeCompiler compiler = new MindcodeCompiler( + messageLogger, + CompilerProfile.fullOptimizations(false, true) + .setTarget(target) + .setRun(run), + InputFiles.fromSource(apiSource.content)); + + final long start = System.nanoTime(); + compiler.safeCompile(); + final long end = System.nanoTime(); + logger.info("performance compiled_in={}ms", TimeUnit.NANOSECONDS.toMillis(end - start)); + + String compiled = getCompilationMessage(apiSource.content, compiler); + boolean isText = compiled != null; + if(compiled == null) { + compiled = compiler.getOutput(); + } + + return new CompileResponse( + apiSource.id, + compiled, + errors(messageLogger), + warnings(messageLogger), + infos(messageLogger), + processRunOutput(compiler), + isText + ); + } + + @PostMapping("/decompile") + public DecompileResponse decompileMlog(@RequestBody DecompileRequest request) { + Target target = Target.fromString(request.target); + boolean run = request.run(); + ApiSource apiSource = getApiSource(request.sourceId, request.source, GetSourceMode.other); + String mlog = apiSource.content; + final String decompiled = MlogDecompiler.decompile(mlog); + + ListMessageLogger messageLogger = new ListMessageLogger(); + List runResults = List.of(); + + if(run) { + CompilerProfile profile = CompilerProfile.fullOptimizations(false, true) + .setTarget(target) + .setRun(true); + InstructionProcessor instructionProcessor = InstructionProcessorFactory.getInstructionProcessor( + messageLogger, + new StandardNameCreator(), + profile); + MindustryMetadata metadata = instructionProcessor.getMetadata(); + + LogicBlock logicBlock = LogicBlock.createProcessor(metadata, profile.getEmulatorTarget().type(), + BlockPosition.ZERO_POSITION, mlog); + logicBlock.createDefaultBlocks(metadata); + + EmulatorSchematic schematic = new EmulatorSchematic(List.of(logicBlock)); + Emulator emulator = new BasicEmulator(messageLogger, profile, schematic); + + emulator.run(profile.getStepLimit()); + runResults = processEmulatorResults(emulator); + } + + return new DecompileResponse( + apiSource.id, + decompiled, + errors(messageLogger), + warnings(messageLogger), + infos(messageLogger), + runResults + ); + } + + @PostMapping("/schemacode/compile") + public SchemacodeCompileResponse compileSchemacode(@RequestBody SchemacodeCompileRequest request) { + Target target = Target.fromString(request.target); + ApiSource apiSource = getApiSource(request.sourceId, request.source, GetSourceMode.compileSchemacode); + + ListMessageLogger messageLogger = new ListMessageLogger(); + final CompilerOutput result = SchemacodeCompiler.compileAndEncode( + messageLogger, + InputFiles.fromSource(apiSource.content), + CompilerProfile.fullOptimizations(true, true) + .setTarget(target) + .setRun(request.run)); + + String compiledCode = result.getStringOutput(); + List runResults = result.emulator() instanceof Emulator emulator ? processEmulatorResults(emulator) : List.of(); + + return new SchemacodeCompileResponse( + apiSource.id, + compiledCode, + errors(messageLogger), + warnings(messageLogger), + infos(messageLogger), + runResults + ); + } + + @PostMapping("/schemacode/decompile") + public DecompileResponse decompileSchematic(@RequestBody DecompileRequest request) { + ApiSource apiSource = getApiSource(request.sourceId, request.source, GetSourceMode.other); + ListMessageLogger messageLogger = new ListMessageLogger(); + final CompilerOutput compilerOutput = SchematicsDecompiler.decompile(messageLogger, apiSource.content); + List runResults = List.of(); + + run: if (request.run) { + Target target = Target.fromString(request.target); + CompilerProfile profile = CompilerProfile.fullOptimizations(false, true) + .setTarget(target) + .setRun(true); + Schematic schematic = parseSchematic(apiSource.content, messageLogger); + if(schematic == null) break run; + + EmulatorSchematic emulatorSchematic = schematic.toEmulatorSchematic(SchematicsMetadata.getMetadata()); + Emulator emulator = new BasicEmulator(messageLogger, profile, emulatorSchematic); + emulator.run(profile.getStepLimit()); + + runResults = processEmulatorResults(emulator); + } + + return new DecompileResponse( + apiSource.id, + compilerOutput.output(), + errors(messageLogger), + warnings(messageLogger), + infos(messageLogger), + runResults + ); + } + + private ApiSource getApiSource(String id, String source, GetSourceMode mode) { + ApiSource apiSource; + if (mode == GetSourceMode.compileMindcode && mindcodeSamples.containsKey(id)) { + apiSource = new ApiSource(id, source); + } else if (mode == GetSourceMode.compileSchemacode && schemacodeSamples.containsKey(id)) { + apiSource = new ApiSource(id, source); + } else if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { + final Optional dto = sourceRepository.findById(UUID.fromString(id)); + final Source newSource = dto + .map(sdto -> sdto.withSource(source)) + .orElseGet(() -> new Source(source, Instant.now())); + + final Source sourceDto = sourceRepository.save(newSource); + apiSource = new ApiSource(sourceDto.getId().toString(), source); + } else { + Source sourceDto = sourceRepository.save(new Source(source, Instant.now())); + apiSource = new ApiSource(sourceDto.getId().toString(), source); + } + + return apiSource; + } + + private String getCompilationMessage(String sourceCode, MindcodeCompiler compiler) { + if (compiler.hasInternalError()) { + return """ + Oh no! Mindcode crashed. + + Please report the error, either by creating an issue on GitHub, + or in one of the Discord servers - I monitor Mindustry, Mindustry Logic, + and, of course, Mindcode. + + It is quite possible that a workaround will be found for this problem, + in which case you'll be able to continue developing your program + even before a fix is available. + """; + } else if (sourceCode.isBlank() || compiler.hasCompilerErrors()) { + return ""; + } else if (isEmpty(compiler.getUnoptimized())) { + if (compiler.getCallGraph().getFunctions().stream().filter(f -> !f.getDeclaration().sourcePosition().isLibrary()).count() > 1) { + return """ + Oops! Your program didn't generate any code. + + However, it looks like you defined some functions. + Maybe you just forgot to call them? + """; + } else { + return """ + Oops! Your program didn't generate any code. + + It appears you haven't entered any statements + or expressions that actually do something. + Maybe you only have comments in your program, + or declarations that don't produce actual code, + such as constant declarations. + """; + } + } else if (isEmpty(compiler.getInstructions())) { + return """ + Whoa! Your program generated some code, + but it was all removed by the optimizer. + + Mindcode removes all unused parts of the program, + and those statements that to not have an effect + on the Mindustry world. + + If your program computes some values, just add + a print() function to output the results of your + computations. For example: + + a = 3; + b = 4; + c = a * a + b * b; + println(c); // <-- prints the result of the computation + """; + } + + return null; + } + + private boolean isEmpty(List program) { + return program.isEmpty() || program.size() <= 2 && program.getFirst().getOpcode() == Opcode.END; + } + + public List errors(ListMessageLogger logger) { + return formatMessages(logger.getMessages(), MindcodeMessage::isError); + } + + public List warnings(ListMessageLogger logger) { + return formatMessages(logger.getMessages(), MindcodeMessage::isWarning); + } + + public List infos(ListMessageLogger logger) { + return formatMessages(logger.getMessages(), MindcodeMessage::isInfo); + } + + public List formatMessages(List messages, Predicate filter) { + return messages.stream().filter(filter.and(m -> !(m instanceof EmulatorMessage))) + .map(CompileResponseMessage::transform) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private RunResult processRunOutput(MindcodeCompiler compiler) { + if (compiler.hasCompilerErrors()) { + return null; + } else if(!compiler.compilerProfile().isRun()) { + return null; + } else { + String output = compiler.getTextBufferOutput(); + String text = output.isEmpty() ? "The program produced no output." : output; + + Optional emulatorError = compiler.getMessages().stream() + .filter(m -> m.level() == MessageLevel.ERROR && m instanceof EmulatorMessage) + .map(EmulatorMessage.class::cast) + .findFirst(); + + if (emulatorError.isPresent()) { + text = text + "\n" + emulatorError.get().message(); + } + + String processorId = compiler.getEmulator().getExecutorResults(0).getProcessorId(); + int steps = compiler.getSteps(); + + return new RunResult(processorId, text, steps); + } + } + + private List processEmulatorResults(Emulator emulator) { + ArrayList results = new ArrayList<>(); + + for(ExecutorResults executorResult : emulator.getExecutorResults()) { + String id = executorResult.getProcessorId(); + int steps = executorResult.getSteps(); + String output = executorResult.getFormattedOutput(); + if(output.isEmpty()) { + output = "The program didn't generate any output."; + } + results.add(new RunResult(id, output, steps)); + } + + return results; + } + + private Schematic parseSchematic(String encodedSchematic, MessageConsumer messageConsumer) { + if (encodedSchematic.isBlank()) { + return null; + } + + final byte[] binary; + try { + binary = Base64.getDecoder().decode(encodedSchematic.trim()); + } catch (IllegalArgumentException e) { + messageConsumer.addMessage(ToolMessage.error("Error decoding schematics string: " + e.getMessage())); + return null; + } + + try (InputStream is = new ByteArrayInputStream(binary)) { + return SchematicsIO.read("", is); + } catch (Exception e) { + messageConsumer.addMessage(ToolMessage.error("Error decoding schematics: " + e.getMessage())); + return null; + } + } + + + private enum GetSourceMode { + compileMindcode, + compileSchemacode, + other, + } + + /** + *

+ * Can represent either a {@link Source} object loaded from the server (saved on compile requests) + * or a sample received for compilation (not saved on compile requests). + *

+ *

+ * This class had to be created because {@link Source} only supports {@link UUID} ids, which + * are not compatible with the sample ids. + *

+ */ + private record ApiSource(String id, String content) {} + + public record CompileRequest(String sourceId, String source, String target, boolean run) {} + public record CompileResponse( + String sourceId, + String compiled, + List errors, + List warnings, + List infos, + RunResult runResult, + boolean isPlainText) {} + public record SchemacodeCompileRequest(String sourceId, String source, String target, boolean run) {} + public record SchemacodeCompileResponse( + String sourceId, + String compiled, + List errors, + List warnings, + List infos, + List runResults) {} + + + public record RunResult(String processorId, String output, int steps) {} + + public record DecompileRequest(String sourceId, String source, String target, boolean run) {} + public record DecompileResponse( + String sourceId, + String source, + List errors, + List warnings, + List infos, + List runResults) {} + + public record SourceRange(String path, int startLine, int startColumn, int endLine, int endColumn) {} + public record CompileResponseMessage(String prefix, String message, SourceRange range) { + static CompileResponseMessage transform(MindcodeMessage msg) { + if (msg.sourcePosition().isEmpty()) { + return new CompileResponseMessage("", msg.message(), null); + } else if (msg.sourcePosition().isLibrary()) { + var sourcePosition = msg.sourcePosition(); + return new CompileResponseMessage("", msg.level().getTitle() + msg.message(), new SourceRange( + sourcePosition.getDistinctPath(), + sourcePosition.start().line(), + sourcePosition.start().column(), + sourcePosition.end().line(), + sourcePosition.end().column() + )); + } else { + var sourcePosition = msg.sourcePosition(); + return new CompileResponseMessage(msg.level().getTitle(), msg.message(), new SourceRange( + sourcePosition.getDistinctPath(), + sourcePosition.start().line(), + sourcePosition.start().column(), + sourcePosition.end().line(), + sourcePosition.end().column() + )); + } + } + } +} diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/DecompilerController.java b/webapp/src/main/java/info/teksol/mindcode/webapp/DecompilerController.java deleted file mode 100644 index 73041084f..000000000 --- a/webapp/src/main/java/info/teksol/mindcode/webapp/DecompilerController.java +++ /dev/null @@ -1,89 +0,0 @@ -package info.teksol.mindcode.webapp; - -import info.teksol.mc.common.CompilerOutput; -import info.teksol.mc.messages.MindcodeMessage; -import info.teksol.schemacode.SchematicsDecompiler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -@Controller -@RequestMapping(value = "/decompiler") -public class DecompilerController { - private static final Logger logger = LoggerFactory.getLogger(DecompilerController.class); - - @Autowired - private SourceRepository sourceRepository; - - @PostMapping("/decompile") - public String postCompile(@RequestParam(required = false) String id, - @RequestParam String source) { - Source schematicDto; - if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - final Optional dto = sourceRepository.findById(UUID.fromString(id)); - final Source newSource = dto - .map(sdto -> sdto.withSource(source)) - .orElseGet(() -> new Source(source, Instant.now())); - schematicDto = sourceRepository.save(newSource); - } else { - schematicDto = sourceRepository.save(new Source(source, Instant.now())); - } - - return "redirect:/decompiler?s=" + schematicDto.getId().toString(); - } - - @GetMapping - public ModelAndView getHomePage(@RequestParam(name = "s", defaultValue = "") String id) { - final String sourceCode; - if (id != null && id.equals("clean")) { - sourceCode = ""; - } else if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - final Optional source = sourceRepository.findById(UUID.fromString(id)); - if (source.isPresent()) { - sourceCode = source.get().getSource(); - } else { - sourceCode = "// 404 Not Found"; - } - } else { - sourceCode = ""; - } - - final long start = System.nanoTime(); - List messages = new ArrayList<>(); - final CompilerOutput result = SchematicsDecompiler.decompile(messages::add, sourceCode); - final long end = System.nanoTime(); - logger.info("performance decompiled_in={}ms", TimeUnit.NANOSECONDS.toMillis(end - start)); - - final String compiledCode = result.getStringOutput(); - return new ModelAndView( - "decompiler", - "model", - new HomePageData( - id, - "", - sourceCode, - (int) sourceCode.chars().filter(ch -> ch == '\n').count(), - compiledCode, - (int) compiledCode.chars().filter(ch -> ch == '\n').count(), - WebappMessage.transform(messages, MindcodeMessage::isError), - WebappMessage.transform(messages, MindcodeMessage::isWarning), - WebappMessage.transform(messages, MindcodeMessage::isInfo), - "", - null, - 0) - ); - } -} diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/HomeController.java b/webapp/src/main/java/info/teksol/mindcode/webapp/HomeController.java deleted file mode 100644 index 2a7d9f79e..000000000 --- a/webapp/src/main/java/info/teksol/mindcode/webapp/HomeController.java +++ /dev/null @@ -1,262 +0,0 @@ -package info.teksol.mindcode.webapp; - -import info.teksol.mc.common.InputFiles; -import info.teksol.mc.emulator.EmulatorMessage; -import info.teksol.mc.messages.ListMessageLogger; -import info.teksol.mc.messages.MessageLevel; -import info.teksol.mc.messages.MindcodeMessage; -import info.teksol.mc.mindcode.compiler.MindcodeCompiler; -import info.teksol.mc.mindcode.logic.instructions.LogicInstruction; -import info.teksol.mc.mindcode.logic.opcodes.Opcode; -import info.teksol.mc.profile.CompilerProfile; -import info.teksol.mc.profile.options.Target; -import info.teksol.mindcode.samples.Sample; -import info.teksol.mindcode.samples.Samples; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; -import org.springframework.web.util.UriComponentsBuilder; - -import java.time.Instant; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -@Controller -@RequestMapping(value = "/") -public class HomeController { - private static final Logger logger = LoggerFactory.getLogger(HomeController.class); - private static final Map samples = Samples.loadMindcodeSamples(); - private static final List quickSamples = samples.values().stream().filter(s -> !s.slow()).toList(); - - private final Random random = new Random(); - @Autowired - private SourceRepository sourceRepository; - - @PostMapping("/compile") - public RedirectView postCompile(@RequestParam(required = false) String id, - @RequestParam String source, - @RequestParam(required = false) String compilerTarget) { - Source sourceDto; - if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - final Optional dto = sourceRepository.findById(UUID.fromString(id)); - final Source newSource = dto - .map(sdto -> sdto.withSource(source)) - .orElseGet(() -> new Source(source, Instant.now())); - sourceDto = sourceRepository.save(newSource); - } else { - sourceDto = sourceRepository.save(new Source(source, Instant.now())); - } - - String targetUrl = UriComponentsBuilder - .fromPath("/") - .queryParam("compilerTarget", compilerTarget) - .queryParam("s", sourceDto.getId().toString()) - .build() - .toUriString(); - - return new RedirectView(targetUrl); - } - - @PostMapping("/compileandrun") - public RedirectView postCompileAndRun(@RequestParam(required = false) String id, - @RequestParam String source, - @RequestParam(required = false) String compilerTarget) { - Source sourceDto; - if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - final Optional dto = sourceRepository.findById(UUID.fromString(id)); - final Source newSource = dto - .map(sdto -> sdto.withSource(source)) - .orElseGet(() -> new Source(source, Instant.now())); - sourceDto = sourceRepository.save(newSource); - } else { - sourceDto = sourceRepository.save(new Source(source, Instant.now())); - } - - String targetUrl = UriComponentsBuilder - .fromPath("/") - .queryParam("compilerTarget", compilerTarget) - .queryParam("s", sourceDto.getId().toString()) - .queryParam("run", "true") - .build() - .toUriString(); - - return new RedirectView(targetUrl); - } - - @GetMapping - public ModelAndView getHomePage( - @RequestParam(name = "s", defaultValue = "") String id, - @RequestParam(name = "mindcode", defaultValue = "") String src, - @RequestParam(name = "compilerTarget", defaultValue = "7s") String compilerTarget, - @RequestParam(name = "run", defaultValue = "false") String compileAndRun - ) { - Target target = Target.fromString(compilerTarget); - boolean run = "true".equals(compileAndRun); - final String sampleName; - final String sourceCode; - if (samples.containsKey(id)) { - sampleName = id; - sourceCode = samples.get(sampleName).source(); - } else if (id != null && id.equals("clean")) { - sampleName = ""; - sourceCode = ""; - } else if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - sampleName = ""; - final Optional source = sourceRepository.findById(UUID.fromString(id)); - if (source.isPresent()) { - sourceCode = source.get().getSource(); - } else { - sourceCode = "// 404 Not Found"; - } - } else if (!src.isEmpty()) { - sourceCode = src; - sampleName = ""; - } else { - final int skipCount = random.nextInt(quickSamples.size()); - sampleName = quickSamples.get(skipCount).name(); - sourceCode = samples.get(sampleName).source(); - } - - if ("sum-of-primes".equals(sampleName)) { - run = true; - } - - ListMessageLogger messageLogger = new ListMessageLogger(); - MindcodeCompiler compiler = new MindcodeCompiler( - messageLogger, - CompilerProfile.fullOptimizations(false, true).setTarget(target).setRun(run), - InputFiles.fromSource(sourceCode)); - - final long start = System.nanoTime(); - compiler.safeCompile(); - final long end = System.nanoTime(); - logger.info("performance compiled_in={}ms", TimeUnit.NANOSECONDS.toMillis(end - start)); - - final String compiledCode = getCompilerCode(sourceCode, compiler); - return new ModelAndView( - "home", - "model", - new HomePageData( - id, - sampleName, - sourceCode, - (int) sourceCode.chars().filter(ch -> ch == '\n').count(), - compiledCode, - (int) compiledCode.chars().filter(ch -> ch == '\n').count(), - errors(compiler), - warnings(compiler), - messages(compiler), - target.webpageTargetName(), - processRunOutput(compiler), - compiler.getSteps()) - ); - } - - private String getCompilerCode(String sourceCode, MindcodeCompiler compiler) { - if (compiler.hasInternalError()) { - return """ - Oh no! Mindcode crashed. - - Please report the error, either by creating an issue on GitHub, - or in one of the Discord servers - I monitor Mindustry, Mindustry Logic, - and, of course, Mindcode. - - It is quite possible that a workaround will be found for this problem, - in which case you'll be able to continue developing your program - even before a fix is available. - """; - } else if (sourceCode.isBlank() || compiler.hasCompilerErrors()) { - return ""; - } else if (isEmpty(compiler.getUnoptimized())) { - if (compiler.getCallGraph().getFunctions().stream().filter(f -> !f.getDeclaration().sourcePosition().isLibrary()).count() > 1) { - return """ - Oops! Your program didn't generate any code. - - However, it looks like you defined some functions. - Maybe you just forgot to call them? - """; - } else { - return """ - Oops! Your program didn't generate any code. - - It appears you haven't entered any statements - or expressions that actually do something. - Maybe you only have comments in your program, - or declarations that don't produce actual code, - such as constant declarations. - """; - } - } else if (isEmpty(compiler.getInstructions())) { - return """ - Whoa! Your program generated some code, - but it was all removed by the optimizer. - - Mindcode removes all unused parts of the program, - and those statements that to not have an effect - on the Mindustry world. - - If your program computes some values, just add - a print() function to output the results of your - computations. For example: - - a = 3; - b = 4; - c = a * a + b * b; - println(c); // <-- prints the result of the computation - """; - } else { - return compiler.getOutput(); - } - } - - private boolean isEmpty(List program) { - return program.isEmpty() || program.size() <= 2 && program.getFirst().getOpcode() == Opcode.END; - } - - public List errors(MindcodeCompiler compiler) { - return formatMessages(compiler, MindcodeMessage::isError); - } - - public List warnings(MindcodeCompiler compiler) { - return formatMessages(compiler, MindcodeMessage::isWarning); - } - - public List messages(MindcodeCompiler compiler) { - return formatMessages(compiler, MindcodeMessage::isInfo); - } - - public List formatMessages(MindcodeCompiler compiler, Predicate filter) { - return compiler.getMessages().stream().filter(filter.and(m -> !(m instanceof EmulatorMessage))) - .map(WebappMessage::transform) - .collect(Collectors.toCollection(ArrayList::new)); - } - - private String processRunOutput(MindcodeCompiler compiler) { - if (compiler.hasCompilerErrors()) { - return null; - } else { - String output = compiler.getTextBufferOutput(); - String text = output.isEmpty() ? "The program produced no output." : output; - - Optional emulatorError = compiler.getMessages().stream() - .filter(m -> m.level() == MessageLevel.ERROR && m instanceof EmulatorMessage) - .map(EmulatorMessage.class::cast) - .findFirst(); - - if (emulatorError.isPresent()) { - text = text + "\n" + emulatorError.get().message(); - } - - return text; - } - } -} diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/HomePageData.java b/webapp/src/main/java/info/teksol/mindcode/webapp/HomePageData.java deleted file mode 100644 index a2d2f0437..000000000 --- a/webapp/src/main/java/info/teksol/mindcode/webapp/HomePageData.java +++ /dev/null @@ -1,102 +0,0 @@ -package info.teksol.mindcode.webapp; - -import info.teksol.mc.Version; - -import java.util.List; - -public class HomePageData { - private final String version = Version.getVersion(); - private final String id; - private final String sample; - private final String source; - private final int sourceLoc; - private final String compiled; - private final int compiledLoc; - private final List errors; - private final List warnings; - private final List messages; - private final String compilerTarget; - private final String runOutput; - private final int runSteps; - - HomePageData(String id, String sample, String source, int sourceLoc, String compiled, int compiledLoc, - List errors, List warnings, List messages, - String compilerTarget, String runOutput, int runSteps) { - this.id = id; - this.sample = sample; - this.source = source; - this.sourceLoc = sourceLoc; - this.compiled = compiled; - this.compiledLoc = compiledLoc; - this.errors = errors; - this.warnings = warnings; - this.messages = messages; - this.compilerTarget = compilerTarget; - this.runOutput = runOutput; - this.runSteps = runSteps; - } - - public String getVersion() { - return version; - } - - public String getId() { - return id; - } - - public String getSample() { - return sample; - } - - public String getSource() { - return source; - } - - public int getSourceLoc() { - return sourceLoc; - } - - public String getCompiled() { - return compiled; - } - - public int getCompiledLoc() { - return compiledLoc; - } - - public List getErrors() { - return errors; - } - - public List getWarnings() { - return warnings; - } - - public List getMessages() { - return messages; - } - - public boolean getHasErrors() { - return !errors.isEmpty(); - } - - public boolean getHasMessages() { - return !warnings.isEmpty() || !messages.isEmpty(); - } - - public boolean isLoggedIn() { - return false; - } - - public String getCompilerTarget() { - return compilerTarget; - } - - public String getRunOutput() { - return runOutput == null ? "" : runOutput; - } - - public int getRunSteps() { - return runSteps; - } -} diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/MlogDecompilerController.java b/webapp/src/main/java/info/teksol/mindcode/webapp/MlogDecompilerController.java deleted file mode 100644 index bbd4b8200..000000000 --- a/webapp/src/main/java/info/teksol/mindcode/webapp/MlogDecompilerController.java +++ /dev/null @@ -1,92 +0,0 @@ -package info.teksol.mindcode.webapp; - -import info.teksol.mc.mindcode.decompiler.MlogDecompiler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; - -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -@Controller -@RequestMapping(value = "/mlog-decompiler") -public class MlogDecompilerController { - private static final Logger logger = LoggerFactory.getLogger(MlogDecompilerController.class); - - @Autowired - private SourceRepository sourceRepository; - - @PostMapping("/decompile-mlog") - public String postCompile(@RequestParam(required = false) String id, - @RequestParam String source) { - Source textDto; - if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - final Optional dto = sourceRepository.findById(UUID.fromString(id)); - final Source newSource = dto - .map(sdto -> sdto.withSource(source)) - .orElseGet(() -> new Source(source, Instant.now())); - textDto = sourceRepository.save(newSource); - } else { - textDto = sourceRepository.save(new Source(source, Instant.now())); - } - - return "redirect:/mlog-decompiler?s=" + textDto.getId().toString(); - } - - @GetMapping - public ModelAndView getHomePage(@RequestParam(name = "s", defaultValue = "") String id) { - final String mlog; - if (id != null && id.equals("clean")) { - mlog = ""; - } else if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - final Optional source = sourceRepository.findById(UUID.fromString(id)); - if (source.isPresent()) { - mlog = source.get().getSource(); - } else { - mlog = "// 404 Not Found"; - } - } else { - mlog = ""; - } - - final long start = System.nanoTime(); - String result; - try { - result = MlogDecompiler.decompile(mlog); - } catch (Exception e) { - logger.error("Error decompiling mlog code", e); - result = "Internal error"; - } - - final long end = System.nanoTime(); - logger.info("performance mlog_decompiled_in={}ms", TimeUnit.NANOSECONDS.toMillis(end - start)); - - return new ModelAndView( - "mlog-decompiler", - "model", - new HomePageData( - id, - "", - mlog, - (int) mlog.chars().filter(ch -> ch == '\n').count(), - result, - (int) result.chars().filter(ch -> ch == '\n').count(), - List.of(), - List.of(), - List.of(), - "", - null, - 0) - ); - } - -} diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/SchematicsController.java b/webapp/src/main/java/info/teksol/mindcode/webapp/SchematicsController.java deleted file mode 100644 index e16a17f5a..000000000 --- a/webapp/src/main/java/info/teksol/mindcode/webapp/SchematicsController.java +++ /dev/null @@ -1,117 +0,0 @@ -package info.teksol.mindcode.webapp; - -import info.teksol.mc.common.CompilerOutput; -import info.teksol.mc.common.InputFiles; -import info.teksol.mc.messages.MindcodeMessage; -import info.teksol.mc.profile.CompilerProfile; -import info.teksol.mc.profile.options.Target; -import info.teksol.mindcode.samples.Sample; -import info.teksol.mindcode.samples.Samples; -import info.teksol.schemacode.SchemacodeCompiler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; -import org.springframework.web.util.UriComponentsBuilder; - -import java.time.Instant; -import java.util.*; -import java.util.concurrent.TimeUnit; - -@Controller -@RequestMapping(value = "/schematics") -public class SchematicsController { - private static final Logger logger = LoggerFactory.getLogger(SchematicsController.class); - private static final Map samples = Samples.loadSchemacodeSamples(); - private static final List quickSamples = samples.values().stream().filter(s -> !s.slow()).toList(); - - private final Random random = new Random(); - @Autowired - private SourceRepository sourceRepository; - - @PostMapping("/compile") - public RedirectView postCompile(@RequestParam(required = false) String id, - @RequestParam String source, - @RequestParam(required = false) String compilerTarget) { - Source schematicDto; - if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - final Optional dto = sourceRepository.findById(UUID.fromString(id)); - final Source newSource = dto - .map(sdto -> sdto.withSource(source)) - .orElseGet(() -> new Source(source, Instant.now())); - schematicDto = sourceRepository.save(newSource); - } else { - schematicDto = sourceRepository.save(new Source(source, Instant.now())); - } - - String targetUrl = UriComponentsBuilder - .fromPath("/schematics") - .queryParam("compilerTarget", compilerTarget) - .queryParam("s", schematicDto.getId().toString()) - .build() - .toUriString(); - - return new RedirectView(targetUrl); - } - - @GetMapping - public ModelAndView getHomePage(@RequestParam(name = "s", defaultValue = "") String id, - @RequestParam(name = "compilerTarget", defaultValue = "7s") String compilerTarget) { - Target target = Target.fromString(compilerTarget); - final String sampleName; - final String sourceCode; - if (samples.containsKey(id)) { - sampleName = id; - sourceCode = samples.get(sampleName).source(); - } else if (id != null && id.equals("clean")) { - sampleName = ""; - sourceCode = ""; - } else if (id != null && id.matches("\\A[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\\z")) { - sampleName = ""; - final Optional source = sourceRepository.findById(UUID.fromString(id)); - if (source.isPresent()) { - sourceCode = source.get().getSource(); - } else { - sourceCode = "// 404 Not Found"; - } - } else { - final int skipCount = random.nextInt(quickSamples.size()); - sampleName = quickSamples.get(skipCount).name(); - sourceCode = samples.get(sampleName).source(); - } - - final long start = System.nanoTime(); - List messages = new ArrayList<>(); - final CompilerOutput result = SchemacodeCompiler.compileAndEncode(messages::add, - InputFiles.fromSource(sourceCode), - CompilerProfile.fullOptimizations(true, true).setTarget(target)); - final long end = System.nanoTime(); - logger.info("performance built_in={}ms", TimeUnit.NANOSECONDS.toMillis(end - start)); - - final String compiledCode = result.getStringOutput(); - return new ModelAndView( - "schematic", - "model", - new HomePageData( - id, - sampleName, - sourceCode, - (int) sourceCode.chars().filter(ch -> ch == '\n').count(), - compiledCode, - (int) compiledCode.chars().filter(ch -> ch == '\n').count(), - WebappMessage.transform(messages, MindcodeMessage::isError), - WebappMessage.transform(messages, MindcodeMessage::isWarning), - WebappMessage.transform(messages, MindcodeMessage::isInfo), - target.webpageTargetName(), - null, - 0) - ); - } - -} diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/WebConfig.java b/webapp/src/main/java/info/teksol/mindcode/webapp/WebConfig.java new file mode 100644 index 000000000..5e4e9874e --- /dev/null +++ b/webapp/src/main/java/info/teksol/mindcode/webapp/WebConfig.java @@ -0,0 +1,49 @@ +package info.teksol.mindcode.webapp; + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.resource.PathResourceResolver; + +import java.io.IOException; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/**") + .addResourceLocations("classpath:/static/") + .resourceChain(true) + .addResolver(new PathResourceResolver() { + @Override + protected Resource getResource(String resourcePath, Resource location) throws IOException { + Resource requested = location.createRelative(resourcePath); + + // If it's a real file (CSS, JS, Images, or actual JSON), serve it + if (requested.exists() && requested.isReadable()) { + return requested; + } + + // If it's a request for data or a static asset with an extension, + // DO NOT serve the fallback. Return null so Spring sends a 404. + if (resourcePath.contains(".") && !resourcePath.endsWith(".html")) { + return null; + } + + // Try appending .html for pretty URLs + if (!resourcePath.contains(".")) { + Resource htmlResource = location.createRelative(resourcePath + ".html"); + if (htmlResource.exists() && htmlResource.isReadable()) { + return htmlResource; + } + } + + // Fallback for SPA routing (only for page requests) + Resource fallback = location.createRelative("200.html"); + return (fallback.exists() && fallback.isReadable()) ? fallback : null; + } + }); + } +} \ No newline at end of file diff --git a/webapp/src/main/java/info/teksol/mindcode/webapp/WebappMessage.java b/webapp/src/main/java/info/teksol/mindcode/webapp/WebappMessage.java deleted file mode 100644 index ec5a6183d..000000000 --- a/webapp/src/main/java/info/teksol/mindcode/webapp/WebappMessage.java +++ /dev/null @@ -1,70 +0,0 @@ -package info.teksol.mindcode.webapp; - -import info.teksol.mc.common.TextFilePosition; -import info.teksol.mc.messages.MindcodeMessage; - -import java.util.List; -import java.util.function.Predicate; - -public final class WebappMessage { - private final String prefix; - private final boolean position; - private final TextFilePosition start; - private final TextFilePosition end; - private final String message; - - public WebappMessage(String prefix, boolean position, TextFilePosition start, TextFilePosition end, String message) { - this.prefix = prefix; - this.position = position; - this.start = start; - this.end = end; - this.message = message; - } - - public boolean hasPosition() { - return position; - } - - public int getStartLine() { - return start.line(); - } - - public int getStartColumn() { - return start.column(); - } - - public int getEndLine() { - return end.line(); - } - - public int getEndColumn() { - return end.column(); - } - - public String getPrefix() { - return prefix; - } - - public String getMessage() { - return message; - } - - public String getPosition() { - return position ? prefix + " at line " + getStartLine() + ", column " + getStartColumn() : ""; - } - - public static WebappMessage transform(MindcodeMessage msg) { - if (msg.sourcePosition().isEmpty()) { - return new WebappMessage("", false, null, null, msg.message()); - } else if (msg.sourcePosition().isLibrary()) { - String position = " at " + msg.sourcePosition().getDistinctPath() + ":" + msg.sourcePosition().line() + ":" + msg.sourcePosition().column() + ": "; - return new WebappMessage("", false, null, null, msg.level().getTitle() + position + msg.message()); - } else { - return new WebappMessage(msg.level().getTitle(), true, msg.sourcePosition().start(), msg.sourcePosition().end(), msg.message()); - } - } - - public static List transform(List messages, Predicate filter) { - return messages.stream().filter(filter).map(WebappMessage::transform).toList(); - } -} diff --git a/webapp/src/main/resources/templates/common.ftlh b/webapp/src/main/resources/templates/common.ftlh deleted file mode 100644 index a8ace9e08..000000000 --- a/webapp/src/main/resources/templates/common.ftlh +++ /dev/null @@ -1,334 +0,0 @@ -<#-- @ftlvariable name="model" type="info.teksol.mindcode.webapp.HomePageData" --> -<#setting locale="en_US"> - -<#macro header> - - - - - Mindcode: a Mindustry Logic high-level language - - - - - - -<#macro h1> -

- <#nested> -

- - -<#macro navigation selection> -

- -

- - -<#macro sample name> -
  • <#nested> - - -<#macro codeareas mlogWatcher sourceTitle targetTitle sourceLines targetLines> - - -
    - - <#if sourceLines == "Y">

    ${model.sourceLoc} lines of code

    -
    -
    - - <#if targetLines == "Y">

    ${model.compiledLoc} lines of code

    - - <#if mlogWatcher = "Y"> - - -
    - - -<#macro link href title=""> - - - -<#macro submit name="" value="" class="" title=""> - - - -<#macro targetSettings > - - - -<#macro compilerMessages messageTitle> -
    - <#if model.hasErrors> - -
      - <#list model.errors> - <#items as msg> -
    • - <#if msg.hasPosition()> - ${msg.position}: - - ${msg.message} -
    • - - -
    -
    Errors were encountered. See - Mindcode Troubleshooting - for some tips, or ask for help. -
    - <#elseif model.hasMessages> - - <#list model.warnings> -
      - <#items as msg> -
    • - <#if msg.hasPosition()> - ${msg.position} - - ${msg.message} -
    • - -
    - - <#list model.messages> -
      - <#items as msg> -
    • ${msg.message}
    • - -
    - - -
    - - -<#macro footerLinks mindcode> -
    - -

    Bug reports, suggestions and questions are welcome at the project page.

    -

    - - -<#macro mlogwatcher> - - - -<#macro footer> - - - - -
    -
    -
    ${model.version}
    -
    PRIVACY POLICY: This website does not track its users. The Mindcode and schematics you submit for compilation/decompilation is kept for later analysis. - No other information is kept about you or your actions on the site.
    -
    Created by François (GitHub, - Twitter).
    - Maintained by Cardillan.
    -
    -
    - - - - diff --git a/webapp/src/main/resources/templates/decompiler.ftlh b/webapp/src/main/resources/templates/decompiler.ftlh deleted file mode 100644 index 440ffaa56..000000000 --- a/webapp/src/main/resources/templates/decompiler.ftlh +++ /dev/null @@ -1,31 +0,0 @@ -<#-- @ftlvariable name="model" type="info.teksol.mindcode.webapp.HomePageData" --> -<#import "common.ftlh" as com> -<@com.header/> -<@com.navigation selection="decompiler"/> - -

    Here you can decompile schematics copied from Mindustry into Schemacode, a schema definition language, - which can then be modified and compiled back into a schema again. Press the Export button on a Mindustry schematic, - choose Copy to clipboard and paste the text into the left pane. Then press Decompile. - You can compile the Schematic definition back to Mindustry schematics on the Schematics Builder page.

    -

    If your schematic contains processor(s), the code is decompiled into mlog.

    - -
    -
    - <#if model.id??> - - -
    - <@com.codeareas mlogWatcher="N" sourceTitle="Encoded schematic:" targetTitle="Decompiled schemacode:" sourceLines="N" targetLines="Y" /> -
    -
    - - <@com.link href="/decompiler">Erase schematic -
    - <@com.footerLinks mindcode="N" /> -
    - <@com.compilerMessages messageTitle="Decompiler messages:" /> -
    -
    -
    - -<@com.footer/> diff --git a/webapp/src/main/resources/templates/home.ftlh b/webapp/src/main/resources/templates/home.ftlh deleted file mode 100644 index fa78529f2..000000000 --- a/webapp/src/main/resources/templates/home.ftlh +++ /dev/null @@ -1,55 +0,0 @@ -<#-- @ftlvariable name="model" type="info.teksol.mindcode.webapp.HomePageData" --> -<#import "common.ftlh" as com> -<@com.header/> -<@com.navigation selection="mindcode"/> - -<#if model.sample != ""> -

    - Mindcode is a high-level language that compiles down to Mindustry Logic - mlog. - Mindcode includes higher-level constructs, such as expressions, user-defined functions and control statements. - Here are some examples to give you an idea how to use Mindcode: -

    -

    - - -
    -
    - <#if model.id??> - - -
    - <@com.codeareas mlogWatcher="Y" sourceTitle="Mindcode Source Code:" targetTitle="Mindustry Logic:" sourceLines="Y" targetLines="Y" /> -
    -
    - <@com.targetSettings /> - - - <@com.link href="/?s=clean">Start with a new script -
    - <@com.footerLinks mindcode="Y" /> -
    -
    - <#if model.runOutput != ""> - - - - - <@com.compilerMessages messageTitle="Compiler messages:" /> -
    -
    -
    -
    - -<@com.mlogwatcher/> - -<@com.footer/> diff --git a/webapp/src/main/resources/templates/mlog-decompiler.ftlh b/webapp/src/main/resources/templates/mlog-decompiler.ftlh deleted file mode 100644 index 636d91d53..000000000 --- a/webapp/src/main/resources/templates/mlog-decompiler.ftlh +++ /dev/null @@ -1,33 +0,0 @@ -<#-- @ftlvariable name="model" type="info.teksol.mindcode.webapp.HomePageData" --> -<#import "common.ftlh" as com> -<@com.header/> -<@com.navigation selection="mlog-decompiler"/> - -

    Here you can partially decompile an mlog code into Mindcode. The resulting code cannot - be directly compiled by Mindcode. Jump instructions in the original mlog are transcribed as if - and goto statements which aren't supported by Mindcode and must be rewritten using conditional - statements, loops and other high-level constructs. The decompiler is mainly useful to produce expressions and - function calls in the correct Mindcode syntax, saving some time and possibly helping to avoid some mistakes - compared to a manual rewrite of the entire mlog code from scratch. -

    - -
    -
    - <#if model.id??> - - -
    - <@com.codeareas mlogWatcher="N" sourceTitle="Mlog code:" targetTitle="Decompiled Mindcode:" sourceLines="N" targetLines="Y" /> -
    -
    - - <@com.link href="/mlog-decompiler">Erase mlog -
    - <@com.footerLinks mindcode="Y" /> -
    - <@com.compilerMessages messageTitle="Decompiler messages:" /> -
    -
    -
    - -<@com.footer/> diff --git a/webapp/src/main/resources/templates/schematic.ftlh b/webapp/src/main/resources/templates/schematic.ftlh deleted file mode 100644 index e5d38a51a..000000000 --- a/webapp/src/main/resources/templates/schematic.ftlh +++ /dev/null @@ -1,54 +0,0 @@ -<#-- @ftlvariable name="model" type="info.teksol.mindcode.webapp.HomePageData" --> -<#import "common.ftlh" as com> -<@com.header/> -<@com.navigation selection="schematics"/> - -<#if model.sample != ""> -

    - Here you can compile a schematics definition written in Schemacode, a schema definition language, - into a text representation to be pasted into Mindustry using the Import schematics... button. - If your schematics contain processors, you can specify code for the processor using either Mindustry Logic or Mindcode. - You can also decompile an existing schematics using the Decompiler and modify the resulting code. -

    -

    - Here are some examples to give you an idea how to use Schemacode: -

    -

    - - -
    -
    - <#if model.id??> - - -
    - <@com.codeareas mlogWatcher="Y" sourceTitle="Schemacode definition:" targetTitle="Encoded schematic:" sourceLines="Y" targetLines="N" /> -
    -
    - <@com.targetSettings /> - - <@com.link href="/schematics?s=clean">Start with a new schematic -
    - <@com.footerLinks mindcode="N" /> -
    - <@com.compilerMessages messageTitle="Build messages:" /> -
    -
    -
    - -<@com.mlogwatcher/> - -<@com.footer/>