diff --git a/.dockerignore b/.dockerignore index 4841b31e..56da6044 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ * +!/.mise/** !/src/** !/deno.json !/deno.lock diff --git a/.env.example b/.env.example index e45eff73..951a7bd2 100644 --- a/.env.example +++ b/.env.example @@ -1,71 +1,75 @@ -#? -#? Rename this file to ".env" and edit the values as needed. -#? -#?#################### -#? VARIABLE STRUCTURE: -#?#################### -#? [ default ] : type < min - max > -#? ^ ^ ^ -#? | | | -#? | | +---- RANGE between two values (inclusive) -#? | +-------------- TYPE of the variable -#? +------------------------ DEFAULT value if not set -#? -#?################### -#? COMMENT STRUCTURE: -#?################### -#? "#?#...", "###..." for section headers -#? "#?" for help -#? "##" for description -#? "#" for variable definitions -#? - -## Log level: [3]:integer<0-4> -#? 0=none, 1=error, 2=warn, 3=info, 4=debug -#JSPB_LOG_VERBOSITY=3 - -## Include timestamps in logs?: [true]:boolean -#JSPB_LOG_TIME=true - -## Hostname to bind: [::]:string -#JSPB_HOSTNAME=:: - -## Port to bind: [4000]:integer<0-65535> -#JSPB_PORT=4000 - -############ -## DOCUMENT: -############ -## Maximum size per document: [1mb]:string -#? 0=disabled, units: b/k(i)b/m(i)b/g(i)b/t(i)b -#JSPB_DOCUMENT_SIZE=1mb - -## Compress document?: [true]:boolean -#? It doesn't apply retroactively to existing documents. -#JSPB_DOCUMENT_COMPRESSION=true - -## Delete documents older than: [0]:string -#? 0=disabled, units: s/m/h/d/w/M/y -#JSPB_DOCUMENT_AGE=0 - -## Delete anonymous documents older than: [7d]:string -#? 0=disabled, units: s/m/h/d/w/M/y -#JSPB_DOCUMENT_ANONYMOUS_AGE=7d - -######## -## USER: -######## -## Allow user registration?: [true]:boolean -#? Root user can always create new users. -#JSPB_USER_REGISTER=true - -## Restore the root user?: [false]:boolean -#? Make sure to disable this again after successful recovery. -#JSPB_USER_ROOT_RECOVERY=false - -######## -## TASK: -######## -## Cleanup task cron schedule: [0 1 * * *]:string -#? https://crontab.guru/#0_1_*_*_* -#JSPB_TASK_SWEEPER=0 1 * * * +#? +#? Rename this file to ".env" and edit the values as needed. +#? +#?#################### +#? VARIABLE STRUCTURE: +#?#################### +#? [ default ] : type < min - max > +#? ^ ^ ^ +#? | | | +#? | | +---- RANGE between two values (inclusive) +#? | +-------------- TYPE of the variable +#? +------------------------ DEFAULT value if not set +#? +#?################### +#? COMMENT STRUCTURE: +#?################### +#? "#?#...", "###..." for section headers +#? "#?" for help +#? "##" for description +#? "#" for variable definitions +#? + +## Log level: [3]:integer<0-4> +#? 0=none, 1=error, 2=warn, 3=info, 4=debug +#JSPB_LOG_VERBOSITY=3 + +## Include timestamps in logs?: [true]:boolean +#JSPB_LOG_TIME=true + +## Hostname to bind: [::]:string +#JSPB_HOSTNAME=:: + +## Port to bind: [8080]:integer<0-65535> +#JSPB_PORT=8080 + +## API path prefix: [/api/]:string +#? Can be queried from "/.well-known/jspaste". +#JSPB_API=/api/ + +############ +## DOCUMENT: +############ +## Maximum size per document: [1mb]:string +#? 0=disabled, units: b/k(i)b/m(i)b/g(i)b/t(i)b +#JSPB_DOCUMENT_SIZE=1mb + +## Compress document?: [true]:boolean +#? It doesn't apply retroactively to existing documents. +#JSPB_DOCUMENT_COMPRESSION=true + +## Delete documents older than: [0]:string +#? 0=disabled, units: s/m/h/d/w/M/y +#JSPB_DOCUMENT_AGE=0 + +## Delete anonymous documents older than: [7d]:string +#? 0=disabled, units: s/m/h/d/w/M/y +#JSPB_DOCUMENT_ANONYMOUS_AGE=7d + +######## +## USER: +######## +## Allow user registration?: [true]:boolean +#? Root user can always create new users. +#JSPB_USER_REGISTER=true + +## Restore the root user?: [false]:boolean +#? Make sure to disable this again after successful recovery. +#JSPB_USER_ROOT_RECOVERY=false + +######## +## TASK: +######## +## Cleanup task cron schedule: [0 1 * * *]:string +#? https://crontab.guru/#0_1_*_*_* +#JSPB_TASK_SWEEPER=0 1 * * * diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index c2d8ebec..3025d036 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -41,12 +41,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: "audit" - name: Setup mise-en-place - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 + uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 - name: Save context id: ctx @@ -76,7 +76,7 @@ jobs: echo "extended=${TIMESTAMP}-${SHA_SHORT}" >>"$GITHUB_OUTPUT" - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: "false" @@ -102,6 +102,12 @@ jobs: zip -j -X -9 -l -o ./dist/backend-${{ steps.tags.outputs.tag }}_windows-amd64.zip .env.example LICENSE README.md ./dist/backend.windows-amd64.exe zip -T ./dist/backend-${{ steps.tags.outputs.tag }}_windows-amd64.zip + # FIXME: Deno still doesn't expose arm64 target + #mise run build:standalone:windows-arm64 + #chmod 755 ./dist/backend.windows-arm64.exe + #zip -j -X -9 -l -o ./dist/backend-${{ steps.tags.outputs.tag }}_windows-arm64.zip .env.example LICENSE README.md ./dist/backend.windows-arm64.exe + #zip -T ./dist/backend-${{ steps.tags.outputs.tag }}_windows-arm64.zip + - if: inputs.artifact-action == 'build-release' name: Release artifact uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0 @@ -125,9 +131,6 @@ jobs: if: github.repository_owner == 'jspaste' && inputs.image-action != 'none' name: Release container image runs-on: ubuntu-latest - env: - REGISTRY: ghcr.io - permissions: attestations: write id-token: write @@ -135,87 +138,55 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: "audit" - - name: Save context - id: ctx - env: - CTX_BRANCH: "${{ github.head_ref || github.ref_name }}" - CTX_SHA: "${{ github.event.pull_request.head.sha || github.sha }}" - run: | - echo "branch=${CTX_BRANCH}" >>"$GITHUB_OUTPUT" - echo "sha=${CTX_SHA}" >>"$GITHUB_OUTPUT" - echo "sha_short=${CTX_SHA::7}" >>"$GITHUB_OUTPUT" + - name: Setup mise-en-place + uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 - - name: Save tags - id: tags + - name: Setup podman env: - BRANCH: "${{ steps.ctx.outputs.branch }}" - SHA: "${{ steps.ctx.outputs.sha }}" - SHA_SHORT: "${{ steps.ctx.outputs.sha_short }}" + PODMAN_VERSION: "v5.8.2" run: | - TIMESTAMP="$(date +%Y.%m.%d)" - TIMESTAMP_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + sudo apt-get purge -y podman runc crun conmon - if [[ "${BRANCH}" == "stable" ]]; then - TAGS+=("latest") - else - TAGS+=("snapshot") - fi - - TAGS+=("${SHA}") - TAGS+=("${TIMESTAMP}-${SHA_SHORT}") + curl -fsSLO "https://github.com/mgoltzsche/podman-static/releases/download/${{ env.PODMAN_VERSION }}/podman-linux-amd64.tar.gz" + curl -fsSLO "https://github.com/mgoltzsche/podman-static/releases/download/${{ env.PODMAN_VERSION }}/podman-linux-amd64.tar.gz.asc" + gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 0CCF102C4F95D89E583FF1D4F8B5AF50344BB503 + gpg --batch --verify "podman-linux-amd64.tar.gz.asc" "podman-linux-amd64.tar.gz" - echo "timestamp=${TIMESTAMP}" >>"$GITHUB_OUTPUT" - echo "timestamp_iso=${TIMESTAMP_ISO}" >>"$GITHUB_OUTPUT" - echo "version=${TIMESTAMP}-${SHA_SHORT}" >>"$GITHUB_OUTPUT" - echo "list=${TAGS[*]}" >>"$GITHUB_OUTPUT" - - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: "false" + tar -xzf "podman-linux-amd64.tar.gz" + sudo cp -rfv ./podman-linux-amd64/etc/. /etc/ + sudo cp -rfv ./podman-linux-amd64/usr/. /usr/ - - name: Build image - id: build-image - uses: redhat-actions/buildah-build@7a95fa7ee0f02d552a32753e7414641a04307056 # v2.13 - with: - containerfiles: "Dockerfile" - platforms: "linux/amd64,linux/arm64" - image: "${{ github.repository }}" - layers: "true" - oci: "true" - tags: "${{ steps.tags.outputs.list }}" - extra-args: | - --squash - --identity-label=false - --label=org.opencontainers.image.created=${{ steps.tags.outputs.timestamp_iso }} - --label=org.opencontainers.image.revision=${{ steps.ctx.outputs.sha }} - --label=org.opencontainers.image.version=${{ steps.tags.outputs.version }} + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - if: inputs.image-action == 'build-release' name: Login to GHCR - uses: redhat-actions/podman-login@4934294ad0449894bcd1e9f191899d7292469603 # v1.7 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: "${{ github.repository_owner }}" password: "${{ secrets.GITHUB_TOKEN }}" - registry: "${{ env.REGISTRY }}" + registry: "ghcr.io" - if: inputs.image-action == 'build-release' - name: Push to GHCR - id: push-image - uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2.8 + name: Login to Docker Hub + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: - image: "${{ steps.build-image.outputs.image }}" - tags: "${{ steps.build-image.outputs.tags }}" - registry: "${{ env.REGISTRY }}" + username: "${{ secrets.DOCKER_USER }}" + password: "${{ secrets.DOCKER_TOKEN }}" + registry: "docker.io" - - if: inputs.image-action == 'build-release' - name: Attest image - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - subject-name: "${{ env.REGISTRY }}/${{ steps.build-image.outputs.image }}" - subject-digest: "${{ steps.push-image.outputs.digest }}" - push-to-registry: "false" + persist-credentials: "false" + + - name: Build container image + run: | + if [ "${{ inputs.image-action }}" = "build-release" ]; then + mise run build:container --release + else + mise run build:container + fi \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc3c3565..555256d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: branches: - dev paths-ignore: - - '*.md' + - "*.md" concurrency: group: ${{ github.workflow }} @@ -26,7 +26,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: "audit" @@ -41,12 +41,12 @@ jobs: echo "sha_short=${CTX_SHA::7}" >>"$GITHUB_OUTPUT" - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: "false" - name: Setup mise-en-place - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 + uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0 - name: Run lint run: mise run lint @@ -62,4 +62,4 @@ jobs: mise run start:server & SERVER_PID=$! sleep 5 - kill $SERVER_PID + kill $SERVER_PID \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6ca3f397..cc3d09a2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ !/.github/ !/.github/renovate.json !/.github/workflows/*.yml +!/.mise/ +!/.mise/** !/.zed/ !/.zed/settings.json !/src/ diff --git a/.mise/snippets/condition_ci.sh b/.mise/snippets/condition_ci.sh new file mode 100755 index 00000000..595ccd33 --- /dev/null +++ b/.mise/snippets/condition_ci.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh +# shellcheck shell=dash +set -eu + +set +u +if [ "$GITHUB_ACTIONS" != "true" ]; then + echo >&2 "This task is intended to be run in GHA" + exit 1 +fi +set -u diff --git a/.mise/snippets/condition_cmd.sh b/.mise/snippets/condition_cmd.sh new file mode 100755 index 00000000..f6ea55de --- /dev/null +++ b/.mise/snippets/condition_cmd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh +# shellcheck shell=dash +set -eu + +if ! command -v -- "$1" >/dev/null 2>&1; then + echo >&2 "$1 isn't available on PATH" + exit 1 +fi diff --git a/.mise/snippets/get_ctx.sh b/.mise/snippets/get_ctx.sh new file mode 100755 index 00000000..f7f1d585 --- /dev/null +++ b/.mise/snippets/get_ctx.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +# shellcheck shell=dash +set -eu + +./.mise/snippets/condition_cmd.sh git + +X_CTX_BRANCH="$(git rev-parse --abbrev-ref HEAD)" +X_CTX_SHA=$(git rev-parse HEAD) +X_CTX_SHA_SHORT="$(printf '%s' "$X_CTX_SHA" | cut -c1-7)" + +export X_CTX_BRANCH X_CTX_SHA X_CTX_SHA_SHORT diff --git a/.mise/tasks/build/container.sh b/.mise/tasks/build/container.sh new file mode 100755 index 00000000..bedbcf13 --- /dev/null +++ b/.mise/tasks/build/container.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env sh +# shellcheck shell=dash +set -eu + +#MISE description="Build container image" + +#USAGE flag "--release" help="Push to registries" default="false" +#USAGE arg "" default="linux/amd64,linux/arm64" + +./.mise/snippets/condition_cmd.sh podman + +. ./.mise/snippets/get_ctx.sh + +X_TIMESTAMP_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +X_PODMAN_TAG_VERSION="$(date -u +%Y.%m.%d)-$X_CTX_SHA_SHORT" + +set +u +if [ "$GITHUB_ACTIONS" != "true" ]; then + X_PODMAN_TAG_HEADER="latest" +elif [ "$X_CTX_BRANCH" = "stable" ]; then + X_PODMAN_TAG_HEADER="latest" +else + X_PODMAN_TAG_HEADER="snapshot" +fi +set -u + +X_PODMAN_TARGET="$usage_target" +X_PODMAN_RELEASE="$usage_release" +X_PODMAN_IMAGE="jspaste/backend" +X_PODMAN_MANIFEST="localhost/$X_PODMAN_IMAGE:latest" +X_PODMAN_TAGS="$X_PODMAN_TAG_VERSION +$X_PODMAN_TAG_HEADER +" +X_PODMAN_REGISTRIES="ghcr.io +docker.io +" + +if podman manifest exists "$X_PODMAN_MANIFEST"; then + podman manifest rm "$X_PODMAN_MANIFEST" +fi + +podman build --format=oci --squash-all --layers --identity-label=false \ + --platform="$X_PODMAN_TARGET" \ + --manifest="$X_PODMAN_MANIFEST" \ + --label="org.opencontainers.image.created=$X_TIMESTAMP_ISO" \ + --label="org.opencontainers.image.revision=$X_CTX_SHA" \ + --label="org.opencontainers.image.version=$X_PODMAN_TAG_VERSION" \ + . + +if [ "$X_PODMAN_RELEASE" = "true" ]; then + printf '%s' "$X_PODMAN_REGISTRIES" | + while IFS='' read -r registry + do + printf '%s' "$X_PODMAN_TAGS" | + while IFS='' read -r tag + do + podman manifest push --all \ + "$X_PODMAN_MANIFEST" \ + "docker://$registry/$X_PODMAN_IMAGE:$tag" + done + done +fi diff --git a/.oxfmtrc.json b/.oxfmtrc.json index a46e3b32..3e15a588 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,8 +1,7 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", "ignorePatterns": ["dist", "node_modules", "storage"], - "sortImports": {}, + "sortImports": true, "sortPackageJson": true, - "sortTailwindcss": {}, "trailingComma": "none" } \ No newline at end of file diff --git a/.oxlintrc.json b/.oxlintrc.json index 2a2f00b5..75cf7def 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -84,6 +84,7 @@ }, "options": { // manual check only - "typeAware": false + "typeAware": false, + "reportUnusedDisableDirectives": "warn" } } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19275ea7..ff341f4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,6 +60,7 @@ mise run build:standalone:linux-amd64 mise run build:standalone:linux-arm64 mise run build:standalone:darwin-arm64 mise run build:standalone:windows-amd64 +mise run build:standalone:windows-arm64 ``` ## API @@ -67,16 +68,16 @@ mise run build:standalone:windows-amd64 The API is documented under OpenAPI specification and can be found at the following path: ```shell -/api/oas.json +/api/docs.json ``` You can get a quick overview with: -- [Swagger Editor](https://editor.swagger.io/?url=https://jspaste.eu/api/oas.json) -- [Scalar Client](https://client.scalar.com/?url=https://jspaste.eu/api/oas.json) +- [Swagger Editor](https://editor.swagger.io/?url=https://jspaste.eu/api/docs.json) +- [Scalar Client](https://client.scalar.com/?url=https://jspaste.eu/api/docs.json) If using Scalar Client, disable the CORS proxy and follow these steps to import the -instance `oas.json`..: +instance `docs.json`..: ![](https://static.inetol.net/jspaste/backend/scalar-t1.webp) diff --git a/Dockerfile b/Dockerfile index a52d8042..973bc879 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,35 +8,35 @@ RUN set -euxo pipefail; \ WORKDIR /build/ COPY . ./ -RUN set -euxo pipefail; \ - mise trust; \ - GITHUB_ACTIONS=true mise run build:server - -RUN echo "root:x:0:root" >/tmp/.group \ - && echo "root:x:0:0:root:/backend:/bin/ash" >/tmp/.passwd \ - && echo "jspaste:x:7777:jspaste" >>/tmp/.group \ - && echo "jspaste:x:7777:7777:jspaste:/backend:/bin/ash" >>/tmp/.passwd - -ARG TARGETOS ARG TARGETARCH RUN set -euxo pipefail; \ - mise run build:standalone + case "$TARGETARCH" in \ + amd64) STANDALONE_TARGET="x86_64-unknown-linux-gnu" ;; \ + arm64) STANDALONE_TARGET="aarch64-unknown-linux-gnu" ;; \ + *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \ + esac; \ + mise trust; \ + STANDALONE_TARGET="$STANDALONE_TARGET" mise run build:standalone + +FROM scratch AS dist +COPY --chown=0:0 --from=cgr.dev/chainguard/busybox:latest / / -FROM --platform=$BUILDPLATFORM scratch AS dist +COPY <.exe" ### Container -- Pull latest image and run the container: +We publish images to multiple registries for redundancy: + +- [`docker.io`](https://hub.docker.com/r/jspaste/backend) +- [`ghcr.io`](https://github.com/jspaste/backend/pkgs/container/backend) + +To pull and run the container: ```shell -docker pull ghcr.io/jspaste/backend:latest -docker run --env-file=.env -d -p 127.0.0.1:4000:4000 \ - ghcr.io/jspaste/backend:latest +docker pull docker.io/jspaste/backend:latest +docker run --env-file=.env -d -p [::1]:8080:8080 docker.io/jspaste/backend:latest ``` ## Validate @@ -36,23 +40,13 @@ docker run --env-file=.env -d -p 127.0.0.1:4000:4000 \ > All artifacts and images originate from GitHub `JSPaste/Backend` repository, no other artifacts or images built and > distributed outside that repository are considered secure nor trusted by the JSPaste team. -You can verify the integrity and origin of an artifact and/or image using the GitHub CLI or manually at +You can verify the integrity and origin of an artifact using the GitHub CLI or manually at [JSPaste Attestations](https://github.com/jspaste/backend/attestations). Artifacts are attested and can be verified using the following command: ```shell -gh attestation verify ./backend_latest_linux-amd64.tar.xz \ - --owner JSPaste -``` - -Since container version -[`2024.05.06-e105023`](https://github.com/orgs/jspaste/packages/container/backend/212635273?tag=2024.05.06-e105023), -images are attested and can be verified using the following command: - -```shell -gh attestation verify oci://ghcr.io/jspaste/backend:latest \ - --owner JSPaste +gh attestation verify ./backend_latest_linux-amd64.tar.xz --owner JSPaste ``` ## Development diff --git a/deno.json b/deno.json index 066466a2..6f339987 100644 --- a/deno.json +++ b/deno.json @@ -6,7 +6,7 @@ "compilerOptions": { "lib": ["deno.window", "deno.unstable", "esnext"] }, - "unstable": ["cron", "raw-imports", "bare-node-builtins"], + "unstable": ["cron", "bare-node-builtins"], "allowScripts": [], "imports": { "#/": "./src/", @@ -27,8 +27,6 @@ "@std/encoding": "jsr:@std/encoding@^1.0.10", "@std/fmt": "jsr:@std/fmt@^1.0.10", "@std/fs": "jsr:@std/fs@^1.0.23", - "@std/path": "jsr:@std/path@^1.1.4", - "@std/streams": "jsr:@std/streams@^1.1.0", "@std/ulid": "jsr:@std/ulid@^1.0.0", "@types/node": "npm:@types/node@^25.6.0", "arkenv": "npm:arkenv@~0.11.0", @@ -36,10 +34,10 @@ "blake3-jit": "npm:blake3-jit@^1.1.0", "hono": "jsr:@hono/hono@^4.12.15", "nanoid": "jsr:@sitnik/nanoid@^5.1.9", - "oxfmt": "npm:oxfmt@^0.48.0", + "oxfmt": "npm:oxfmt@^0.52.0", "oxlint": "npm:oxlint@^1.62.0", - "oxlint-tsgolint": "npm:oxlint-tsgolint@^0.22.1", - "rolldown": "npm:rolldown@1.0.0" + "oxlint-tsgolint": "npm:oxlint-tsgolint@^0.23.0", + "rolldown": "npm:rolldown@1.0.3" }, "fmt": { "exclude": ["**"] diff --git a/deno.lock b/deno.lock index d61f95db..6ed7827b 100644 --- a/deno.lock +++ b/deno.lock @@ -2,41 +2,40 @@ "version": "5", "specifiers": { "jsr:@deno/loader@0.5": "0.5.0", - "jsr:@hono/hono@^4.12.15": "4.12.16", - "jsr:@hono/hono@^4.8.3": "4.12.16", + "jsr:@hono/hono@^4.12.15": "4.12.23", + "jsr:@hono/hono@^4.8.3": "4.12.23", "jsr:@hono/standard-validator@~0.2.2": "0.2.2", "jsr:@sitnik/nanoid@^5.1.9": "5.1.11", "jsr:@standard-schema/spec@1": "1.1.0", "jsr:@std/assert@^1.0.19": "1.0.19", - "jsr:@std/async@^1.3.0": "1.3.0", - "jsr:@std/bytes@^1.0.6": "1.0.6", + "jsr:@std/async@^1.3.0": "1.4.0", "jsr:@std/cache@~0.2.3": "0.2.3", - "jsr:@std/collections@^1.1.7": "1.1.7", - "jsr:@std/data-structures@^1.0.11": "1.0.11", - "jsr:@std/dotenv@~0.225.6": "0.225.6", + "jsr:@std/collections@^1.1.7": "1.2.0", + "jsr:@std/data-structures@^1.1.0": "1.1.0", + "jsr:@std/dotenv@~0.225.6": "0.225.7", "jsr:@std/encoding@^1.0.10": "1.0.10", "jsr:@std/fmt@^1.0.10": "1.0.10", - "jsr:@std/fs@^1.0.23": "1.0.23", - "jsr:@std/internal@^1.0.12": "1.0.13", - "jsr:@std/path@^1.1.4": "1.1.4", - "jsr:@std/streams@^1.1.0": "1.1.0", + "jsr:@std/fs@^1.0.23": "1.0.24", + "jsr:@std/internal@^1.0.12": "1.0.14", + "jsr:@std/internal@^1.0.14": "1.0.14", + "jsr:@std/path@^1.1.5": "1.1.5", "jsr:@std/ulid@1": "1.0.0", - "npm:@types/node@^25.6.0": "25.6.0", - "npm:arkenv@0.11": "0.11.0_arktype@2.2.0", + "npm:@types/node@^25.6.0": "25.9.2", + "npm:arkenv@0.11": "0.11.1_arktype@2.2.0", "npm:arktype@^2.2.0": "2.2.0", "npm:blake3-jit@^1.1.0": "1.1.0", "npm:hono-openapi@^1.3.0": "1.3.0_@standard-community+standard-json@0.3.5__@standard-schema+spec@1.1.0__@types+json-schema@7.0.15__arktype@2.2.0__quansync@0.2.11_@standard-community+standard-openapi@0.2.9__@standard-community+standard-json@0.3.5___@standard-schema+spec@1.1.0___@types+json-schema@7.0.15___arktype@2.2.0___quansync@0.2.11__@standard-schema+spec@1.1.0__arktype@2.2.0__openapi-types@12.1.3__@types+json-schema@7.0.15__quansync@0.2.11_@types+json-schema@7.0.15_openapi-types@12.1.3_@standard-schema+spec@1.1.0_arktype@2.2.0_quansync@0.2.11", - "npm:oxfmt@0.48": "0.48.0", - "npm:oxlint-tsgolint@~0.22.1": "0.22.1", - "npm:oxlint@^1.62.0": "1.62.0_oxlint-tsgolint@0.22.1", - "npm:rolldown@1.0.0": "1.0.0" + "npm:oxfmt@0.52": "0.52.0", + "npm:oxlint-tsgolint@0.23": "0.23.0", + "npm:oxlint@^1.62.0": "1.68.0_oxlint-tsgolint@0.23.0", + "npm:rolldown@1.0.3": "1.0.3" }, "jsr": { "@deno/loader@0.5.0": { "integrity": "a6d94408de5e6bacac404f8f6963c8b8cc278cfd1a878aa2f06b34a083d6bfee" }, - "@hono/hono@4.12.16": { - "integrity": "d3a3ca0d713852c91881541e74d5ffac0b4af38b8c1285e8ed5ee47b68c58f94" + "@hono/hono@4.12.23": { + "integrity": "9d9f3da498f69c311b5f92d973eb3b8ebc973b5fd2b4972781b556e07818a745" }, "@hono/standard-validator@0.2.2": { "integrity": "bc94e1ab41d677a571cb6dd5012823f1162b9856ca24dfd60233734824bb0b0c", @@ -54,29 +53,26 @@ "@std/assert@1.0.19": { "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", "dependencies": [ - "jsr:@std/internal" + "jsr:@std/internal@^1.0.12" ] }, - "@std/async@1.3.0": { - "integrity": "80485538a4f7baaa46bfe2246168069e02ed142b9f9079cd164f43bb060ad9e9", + "@std/async@1.4.0": { + "integrity": "4d70b008634f571cff9b554090d628c76141c32613aae0ff283fd5fa23d0c379", "dependencies": [ "jsr:@std/data-structures" ] }, - "@std/bytes@1.0.6": { - "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" - }, "@std/cache@0.2.3": { "integrity": "4e0bcab2e61f7c5637937bfe2bb13ccdd15e4dc3092beb14b78726bea8c49916" }, - "@std/collections@1.1.7": { - "integrity": "56f659d011218a69740b12829cf5ea2c9b70bbed0af02597e27dc1eb5e69e208" + "@std/collections@1.2.0": { + "integrity": "47627a21d3a13138b77fd0e4d790ba9d2e603c3510b686cde6b132fe9aa98a88" }, - "@std/data-structures@1.0.11": { - "integrity": "53b98ed7efa61f107dfc14244bd2ec5557f7f7ee0bbaef6d449d7937facacb89" + "@std/data-structures@1.1.0": { + "integrity": "c35ae4ad5d8e41a38573c2fe3e19b18ea2505f63cfea201edcb8720aca1f7f58" }, - "@std/dotenv@0.225.6": { - "integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b" + "@std/dotenv@0.225.7": { + "integrity": "11d8db03ca4ad5aba9eba809f2e8058b2a4f320b7b09fea4b360e162928329e3" }, "@std/encoding@1.0.10": { "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" @@ -84,26 +80,20 @@ "@std/fmt@1.0.10": { "integrity": "90dfba288802ac6de82fb31d0917eb9e4450b9925b954d5e51fc29ac07419db5" }, - "@std/fs@1.0.23": { - "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", + "@std/fs@1.0.24": { + "integrity": "f3061b45b81673a2bece689da041df32d174be064c89eb6397fb5718d3fb7877", "dependencies": [ - "jsr:@std/internal", + "jsr:@std/internal@^1.0.14", "jsr:@std/path" ] }, - "@std/internal@1.0.13": { - "integrity": "2f9546691d4ac2d32859c82dff284aaeac980ddeca38430d07941e7e288725c0" - }, - "@std/path@1.1.4": { - "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", - "dependencies": [ - "jsr:@std/internal" - ] + "@std/internal@1.0.14": { + "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7" }, - "@std/streams@1.1.0": { - "integrity": "2f7024d841f343fd478afe0c958a3f0f068ef2a0d2bcc954f550f97ac1fa22e3", + "@std/path@1.1.5": { + "integrity": "ccea00982ea28c36becaf6e62f855406c76a8c32d462f66f415bbb7d83a271bc", "dependencies": [ - "jsr:@std/bytes" + "jsr:@std/internal@^1.0.14" ] }, "@std/ulid@1.0.0": { @@ -147,291 +137,291 @@ "@tybys/wasm-util" ] }, - "@oxc-project/types@0.129.0": { - "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==" + "@oxc-project/types@0.133.0": { + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==" }, - "@oxfmt/binding-android-arm-eabi@0.48.0": { - "integrity": "sha512-uwqk+/KhQvBIpULD8SMM/zAafMRC/+DV/xsEQjkkIsJ/kLmEI/2bxonVowcYTiXqqZ/a0FEW8DPkZY3VvwELDA==", + "@oxfmt/binding-android-arm-eabi@0.52.0": { + "integrity": "sha512-17EMSJnQ9g+upVHrAUYDMfH5lvRKQ9Nvg8WtEoH72oDr1VpWz+7/o3tD97U1EToen2YAQ/68JmtDYkQUi20dfQ==", "os": ["android"], "cpu": ["arm"] }, - "@oxfmt/binding-android-arm64@0.48.0": { - "integrity": "sha512-VUCiKuXK5+McVssgHEJdrcGK7hRJzrRb36zm9/jwzMholyYt4BgXhw5Nm1V1DX6Ce717Zi/1jk432b/tgmQgtQ==", + "@oxfmt/binding-android-arm64@0.52.0": { + "integrity": "sha512-A2G1IdwGEW2lLJkIxcvuirRH1CzSl/e0NX11zTlW1gvxJThfwbI/BEoaKrTNpm7M2FchvIf6guvIQU7d5iz+OQ==", "os": ["android"], "cpu": ["arm64"] }, - "@oxfmt/binding-darwin-arm64@0.48.0": { - "integrity": "sha512-IkKp8rnIyQLW6Jt+6jragCbUVYSayk55lapiprLjIVvt4NczLyO/nwX2GgefLQ5iaBdfS8UEAFgCs/pLO6Cl0w==", + "@oxfmt/binding-darwin-arm64@0.52.0": { + "integrity": "sha512-f9+bLvOYxy7NttCLFTvQ7afmqDOWY4wIP9xdvfj5trQ1qj6f2UFAGwZESlfsMjvJNTyRpXfIlOanCI9FOvoeQA==", "os": ["darwin"], "cpu": ["arm64"] }, - "@oxfmt/binding-darwin-x64@0.48.0": { - "integrity": "sha512-+aFuhsGIuvnoOjXyKVHMhPKJZR1kQkAl8QyrKoMlA7yJsSTC3N0Asl53La8TChSHhW8epToQ/Q0nvLmEmfNmLg==", + "@oxfmt/binding-darwin-x64@0.52.0": { + "integrity": "sha512-YSTB9sJ5nnQd/Q0ddHkgof0ZCHPAnWZT1IW2SJ8omz7CP7KluJhO1fNHrpqdxCtpztJwSs4hY1uAee35wKxxaw==", "os": ["darwin"], "cpu": ["x64"] }, - "@oxfmt/binding-freebsd-x64@0.48.0": { - "integrity": "sha512-fbqzQL8FjI9gGnktI7RIo0dksDziTAYBy7xlI7jU7eID5fxLF/25fS4Xj6GydD8Y5oWHL83U4NK160QaOAxtyg==", + "@oxfmt/binding-freebsd-x64@0.52.0": { + "integrity": "sha512-NIrRNTTPCs4UbmVs0bxLSCDlLCtIRMJIXklNKaXa5Oj2/K1UIMBvgE8+uPVo01Io3N9HF0+GAX+aAHjUgZS7vA==", "os": ["freebsd"], "cpu": ["x64"] }, - "@oxfmt/binding-linux-arm-gnueabihf@0.48.0": { - "integrity": "sha512-hn4i0zhAyTiB3ZHjQfYUZkDvrbVkohw1S7pySWxWUoZ87HnkDoTFThj7QTxk40hNPOTUP0vHbPRNamFIv1HBJQ==", + "@oxfmt/binding-linux-arm-gnueabihf@0.52.0": { + "integrity": "sha512-JXUCde8mn3GpgQouz2PXUokgy/uT1QrRJBL2s983VWcSQp62wTFYiNXgTKdeo1Jgbr0IgUnKKvzIk/YBlj/nVQ==", "os": ["linux"], "cpu": ["arm"] }, - "@oxfmt/binding-linux-arm-musleabihf@0.48.0": { - "integrity": "sha512-R4WBD9qF3QM9hqgdAa+fBGXmquTvDUujrPQ36t2Sjk8RPOSKGHDeN7l/khr10hqbQaOq9KCgPHG9ubNET/X/RQ==", + "@oxfmt/binding-linux-arm-musleabihf@0.52.0": { + "integrity": "sha512-psbUXaRZ+V8DaXz10Qf7LSHtdtdKAmC8fxXgeU608jjzrmWK4quamZMOpl6sf+dikoFHA85uE93Q0BqxrCdQrQ==", "os": ["linux"], "cpu": ["arm"] }, - "@oxfmt/binding-linux-arm64-gnu@0.48.0": { - "integrity": "sha512-5bVdwSwlm1M8wbYCorLOxWxUBw/8tBvHYyQNIfwWVPwOJaj5vg1APSGJQVpwJfV5VNE9PSrR91UKEpoNwHhqUA==", + "@oxfmt/binding-linux-arm64-gnu@0.52.0": { + "integrity": "sha512-Jw7MgWUU9lcLCcy82updISP3EthTlfvAwR6gWNxPzqly7+fLvOi2gHQE9xXQjpqaVLm/8P+gOzlv9ODuoVlaaw==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxfmt/binding-linux-arm64-musl@0.48.0": { - "integrity": "sha512-vCS3Fk7gFslTqE1lUE2IlroyVV7u/9SmMA/uBqDoshuck2psGWcjW0ePyPZI3rM3+qtf2pDaMVIKMHozraifuw==", + "@oxfmt/binding-linux-arm64-musl@0.52.0": { + "integrity": "sha512-wZg6bLjDvh2KibyI3QFUYo8GTXneIFsd0JvehtvJiUmQ8WRPERgxd/VM4ctWb86U5FT1FkqgS8/wZKVB+AZScg==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxfmt/binding-linux-ppc64-gnu@0.48.0": { - "integrity": "sha512-gKtfFfueUClXDumyoHUbymqRf7prHejOOyzJK0eIJn93GF9JBdFHdo60TM1ZBHxkEwZvjuOgHmKtneKbEOc/Eg==", + "@oxfmt/binding-linux-ppc64-gnu@0.52.0": { + "integrity": "sha512-IngE8uxhNvxcMrLjZNDo9xNLY7rEK33AKnaMd2B46he1e/mz2CfcW6If/U1wUjdRZddm1QzQaciqZkuMkdh1FA==", "os": ["linux"], "cpu": ["ppc64"] }, - "@oxfmt/binding-linux-riscv64-gnu@0.48.0": { - "integrity": "sha512-SYt0UhOvZD/UwZz9sXq6J2uAw8o24f5VZpLB2DH01f6MevshmlgakQlZe2lwek2sZJkd07eLu7mZa0g7yeiw7Q==", + "@oxfmt/binding-linux-riscv64-gnu@0.52.0": { + "integrity": "sha512-H3+DdFMv/efN3Efmhsv18jDrpiWWqKG7wsfAlQBqAt6z/E2Bx+TwEj2Nowe51CPOWB8/mFBC2dAMSgVFLvvowA==", "os": ["linux"], "cpu": ["riscv64"] }, - "@oxfmt/binding-linux-riscv64-musl@0.48.0": { - "integrity": "sha512-JLbrwck2AopG4ud/XklZO5N+qxGC7cS7ROvXZVNfx0MCLDDL2kGOLvzuWORkVjnjAM0CMAfIMU2zNBtQbM+4dw==", + "@oxfmt/binding-linux-riscv64-musl@0.52.0": { + "integrity": "sha512-zji+1kb7lJKohSDjzC1IsS+K/cKRs1hdVf0ZH0VbdbiakmtLvN9twBoXo/k8VdjFax7kfo+DyPxS7vv52br1aw==", "os": ["linux"], "cpu": ["riscv64"] }, - "@oxfmt/binding-linux-s390x-gnu@0.48.0": { - "integrity": "sha512-mdxt5L8OQLxkQH+JVpdC/lknZNe0lX4hlO3d8+xvw2wToo+iDrid9tiGOd5bmHfUVd5wVhrUry0qlu5vq66NkQ==", + "@oxfmt/binding-linux-s390x-gnu@0.52.0": { + "integrity": "sha512-hcLBYedpCy7ToUvvBidWk7+11Yhg1oAZ4+6hKPic/mQI6NaqXJSXMps5nFlwUuX2ewhtLZZDPg63TI042qGKBg==", "os": ["linux"], "cpu": ["s390x"] }, - "@oxfmt/binding-linux-x64-gnu@0.48.0": { - "integrity": "sha512-oEz1BQwMrV7OMEFx/3VPDU3n9TM0AnxpktDYXjEg5i6nTX87wo18wSfBvkl4tzAICdKtoAQAdBIl7Y7hsPlx5w==", + "@oxfmt/binding-linux-x64-gnu@0.52.0": { + "integrity": "sha512-IDO2loXK2OtTOhSPchU9MW25mWL2QCDGdJbjN8MXKZVS80qXe5gMTwQWu/gMJ3juoBHbkuUZNB2N1LHzNT7DoA==", "os": ["linux"], "cpu": ["x64"] }, - "@oxfmt/binding-linux-x64-musl@0.48.0": { - "integrity": "sha512-g2SKTTurP5mWjd8Ecait0erYqmltL4IqW1EwttM25BxM6NiTt4ubobJYMR1uox1V2QgG4UfHH10CGRvWlUixjw==", + "@oxfmt/binding-linux-x64-musl@0.52.0": { + "integrity": "sha512-mAV2Hjn0SatJ+KoAzKUC3eJhdJ8wv+3m1KyuS0dTsbF0c5weq+QrCt/DRZZM+uj/XiKzCDEUKYsBF30e2qkcyw==", "os": ["linux"], "cpu": ["x64"] }, - "@oxfmt/binding-openharmony-arm64@0.48.0": { - "integrity": "sha512-CIg24VgheEpvolHL2gQuax5qcQ602bRMHrJ9g8XsQr3iVj9aSPgopigBKuMqrXsupwkrU+RQCn5cG8PgFntR6w==", + "@oxfmt/binding-openharmony-arm64@0.52.0": { + "integrity": "sha512-vd4npaUIwChxp7XzkqmepBWTT9YMcSe/NBApVGPC30/lLyOVaV3dvma1SKo03t8O73BPRAG7EyJzGlN5cJM5hQ==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@oxfmt/binding-win32-arm64-msvc@0.48.0": { - "integrity": "sha512-zeaWkcxcEULwkGF3I/HgEvcDPN8buYDrxibBUa/IFh5Vmwyge+KpLO+hEwSovW349H0O/C0Z2kaFmEzEDm00/Q==", + "@oxfmt/binding-win32-arm64-msvc@0.52.0": { + "integrity": "sha512-k2sz6gWQdMfh5HPpIS+Bw/0UEV/kaK2xuqJRrWL233sEHx9WLlsmvlPFM4HUNThkYbSN0U0vPW7LVKZWDS8hPQ==", "os": ["win32"], "cpu": ["arm64"] }, - "@oxfmt/binding-win32-ia32-msvc@0.48.0": { - "integrity": "sha512-yiEKnIAGvx5CyZQOlMaNlZkAbwT7/Quk0j3WLt+PR5hK+qYjPTRRJYDfD77wCBPLvEYAG41v4KG3iL0H+uxoxg==", + "@oxfmt/binding-win32-ia32-msvc@0.52.0": { + "integrity": "sha512-rhke69GTcArodLHpjMTfNnvjTEBryDeZcUCKK/VjXDMtfTULl6QRh0ymX5/hbCUv2WjYm9h/QbW++q2vE15gWQ==", "os": ["win32"], "cpu": ["ia32"] }, - "@oxfmt/binding-win32-x64-msvc@0.48.0": { - "integrity": "sha512-GSD2+7t2UoVMV2NgxXypa4bKewflPMAjYnF0Xw9/ht82ZfafAHhb8STwrEd7wlH2PFogt5zw3WVCxYJaHUdbeQ==", + "@oxfmt/binding-win32-x64-msvc@0.52.0": { + "integrity": "sha512-q5xL7oeXkZdEtNZWBdvehJcmt+GRu9l2bK40yJs1jJXlqq+r0Hygb1rTjq+FM2o/2xyt4cufH6KRplHp3Jjsvw==", "os": ["win32"], "cpu": ["x64"] }, - "@oxlint-tsgolint/darwin-arm64@0.22.1": { - "integrity": "sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw==", + "@oxlint-tsgolint/darwin-arm64@0.23.0": { + "integrity": "sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==", "os": ["darwin"], "cpu": ["arm64"] }, - "@oxlint-tsgolint/darwin-x64@0.22.1": { - "integrity": "sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA==", + "@oxlint-tsgolint/darwin-x64@0.23.0": { + "integrity": "sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==", "os": ["darwin"], "cpu": ["x64"] }, - "@oxlint-tsgolint/linux-arm64@0.22.1": { - "integrity": "sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew==", + "@oxlint-tsgolint/linux-arm64@0.23.0": { + "integrity": "sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxlint-tsgolint/linux-x64@0.22.1": { - "integrity": "sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA==", + "@oxlint-tsgolint/linux-x64@0.23.0": { + "integrity": "sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==", "os": ["linux"], "cpu": ["x64"] }, - "@oxlint-tsgolint/win32-arm64@0.22.1": { - "integrity": "sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA==", + "@oxlint-tsgolint/win32-arm64@0.23.0": { + "integrity": "sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==", "os": ["win32"], "cpu": ["arm64"] }, - "@oxlint-tsgolint/win32-x64@0.22.1": { - "integrity": "sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg==", + "@oxlint-tsgolint/win32-x64@0.23.0": { + "integrity": "sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==", "os": ["win32"], "cpu": ["x64"] }, - "@oxlint/binding-android-arm-eabi@1.62.0": { - "integrity": "sha512-pKsthNECyvJh8lPTICz6VcwVy2jOqdhhsp1rlxCkhgZR47aKvXPmaRWQDv+zlXpRae4qm1MaaTnutkaOk5aofg==", + "@oxlint/binding-android-arm-eabi@1.68.0": { + "integrity": "sha512-wEdsIspexXLLMCPAEOcCuFLMt6aE3AzTuA/nQKLPRnoJ+EQTturmGheDkhHuuVHx0GbutjQ3JKmEn+Gz6Ag28Q==", "os": ["android"], "cpu": ["arm"] }, - "@oxlint/binding-android-arm64@1.62.0": { - "integrity": "sha512-b1AUNViByvgmR2xJDubvLIr+dSuu3uraG7bsAoKo+xrpspPvu6RIn6Fhr2JUhobfep3jwUTy18Huco6GkwdvGQ==", + "@oxlint/binding-android-arm64@1.68.0": { + "integrity": "sha512-6aZRNNXQTsYtgaus8HTb9nuCcsrQTlKXGnktwvwW0n/SooRWNxNb3925grDkC63aEYZuCIyOVLV16IdYIoC2aQ==", "os": ["android"], "cpu": ["arm64"] }, - "@oxlint/binding-darwin-arm64@1.62.0": { - "integrity": "sha512-iG+Tvf70UJ6otfwFYIHk36Sjq9cpPP5YLxkoggANNRtzgi3Tj3g8q6Ybqi6AtkU3+yg9QwF7bDCkCS6bbL4PCg==", + "@oxlint/binding-darwin-arm64@1.68.0": { + "integrity": "sha512-lVTbsE3kO4bLpZELgjRZuAJc8kP98wb83yMXWH8gaPaFZ+cM2IDeZto4ByoUAYj0Mxv2rvw+A1ssZequSepVSg==", "os": ["darwin"], "cpu": ["arm64"] }, - "@oxlint/binding-darwin-x64@1.62.0": { - "integrity": "sha512-oOWI6YPPr5AJUx+yIDlxmuUbQjS5gZX3OH3QisawYvsZgLiQVvZtR0rPBcJTxLWqt2ClrWg0DlSrlUiG5SQNHg==", + "@oxlint/binding-darwin-x64@1.68.0": { + "integrity": "sha512-nCmw2XrmQskjBUh/sfP5yKs93V68LijQgjd1cuuZ/q4SCARngLYs60/qqyzuMsg8QQ9KArDI98hxs/RDGE4KRQ==", "os": ["darwin"], "cpu": ["x64"] }, - "@oxlint/binding-freebsd-x64@1.62.0": { - "integrity": "sha512-dLP33T7VLCmLVv4cvjkVX+rmkcwNk2UfxmsZPNur/7BQHoQR60zJ7XLiRvNUawlzn0u8ngCa3itjEG73MAMa/w==", + "@oxlint/binding-freebsd-x64@1.68.0": { + "integrity": "sha512-TI4ovQJliYE9V6e06cEv+qEI9uj7Ao65fmif4er4HD+aouyYyh0P31q2jh3KtqsOHHcQqv2PZ61TjJFLpBDGWQ==", "os": ["freebsd"], "cpu": ["x64"] }, - "@oxlint/binding-linux-arm-gnueabihf@1.62.0": { - "integrity": "sha512-fl//LWNks6qo9chNY60UDYyIwtp7a5cEx4Y/rHPjaarhuwqx6jtbzEpD5V5AqmdL4a6Y5D8zeXg5HF2Cr0QmSQ==", + "@oxlint/binding-linux-arm-gnueabihf@1.68.0": { + "integrity": "sha512-LcNnEi9g71Cmry5ZpLbKT+oVv+/zYG3hYVAbBBB5X85nOQZSk8l92CnDkxJMcxUg0NCnMCOFZuaVDlMyv4tYJw==", "os": ["linux"], "cpu": ["arm"] }, - "@oxlint/binding-linux-arm-musleabihf@1.62.0": { - "integrity": "sha512-i5vkAuxvueTODV3J2dL61/TXewDHhMFKvtD156cIsk7GsdfiAu7zW7kY0NJXhKeFHeiMZIh7eFNjkPYH6J47HQ==", + "@oxlint/binding-linux-arm-musleabihf@1.68.0": { + "integrity": "sha512-OovHahL3FX4UaK+hgSf11llUx2vszqjSdQQ61Ck9InOEI/ptZoC4XSQJurITqItVvd53JSlmkLMeaNjM1PoQew==", "os": ["linux"], "cpu": ["arm"] }, - "@oxlint/binding-linux-arm64-gnu@1.62.0": { - "integrity": "sha512-QwN19LLuIGuOjEflSeJkZmOTfBdBMlTmW8xbMf8TZhjd//cxVNYQPq75q7oKZBJc6hRx3gY7sX0Egc8cEIFZYg==", + "@oxlint/binding-linux-arm64-gnu@1.68.0": { + "integrity": "sha512-YbzTglnHLzzi9zv5or8Ztz5fykAoZE8W9iM42/bOrF4HBSB6rJTqdLQWuoP76EHQw9DuKl76K1QmFlG29sPJXQ==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxlint/binding-linux-arm64-musl@1.62.0": { - "integrity": "sha512-8eCy3FCDuWUM5hWujAv6heMvfZPbcCOU3SdQUAkixZLu5bSzOkNfirJiLGoQFO943xceOKkiQRMQNzH++jM3WA==", + "@oxlint/binding-linux-arm64-musl@1.68.0": { + "integrity": "sha512-qVKtCZNic+OoNnOr/hCQAu22HSQzflI7Fsq/Blzkw02SnLuv163k3kfmrVpZjSBlUHgsRKj6WgQiw30d3SX02Q==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxlint/binding-linux-ppc64-gnu@1.62.0": { - "integrity": "sha512-NjQ7K7tpTPDe9J+yq8p/s/J0E7lRCkK2uDBDqvT4XIT6f4Z0tlnr59OBg/WcrmVHER1AbrcfyxhGTXgcG8ytWg==", + "@oxlint/binding-linux-ppc64-gnu@1.68.0": { + "integrity": "sha512-zExyZ8ZOUuAyQ0y9jpTcyjKUz62YY9JhKPyVxzvjTpXzZ3ujdqiVwfPWDdnA1SsIOrxdtxHn7KErDHLWskFjXg==", "os": ["linux"], "cpu": ["ppc64"] }, - "@oxlint/binding-linux-riscv64-gnu@1.62.0": { - "integrity": "sha512-oKZed9gmSwze29dEt3/Wnsv6l/Ygw/FUst+8Kfpv2SGeS/glEoTGZAMQw37SVyzFV76UTHJN2snGgxK2t2+8ow==", + "@oxlint/binding-linux-riscv64-gnu@1.68.0": { + "integrity": "sha512-6C4MPuwewyDavA7sxM14wzgRi5GGL68HPIxRCdVyS75U4MDbpFVYzKO9WNR6KLKTMPq2pcz3THwo1sK2uiqngw==", "os": ["linux"], "cpu": ["riscv64"] }, - "@oxlint/binding-linux-riscv64-musl@1.62.0": { - "integrity": "sha512-gBjBxQ+9lGpAYq+ELqw0w8QXsBnkZclFc7GRX2r0LnEVn3ZTEqeIKpKcGjucmp76Q53bvJD0i4qBWBhcfhSfGA==", + "@oxlint/binding-linux-riscv64-musl@1.68.0": { + "integrity": "sha512-bnZooVeHAcvA+dH0EDLgx+7HY/DRi6e0hFszg3P+OBatuUjV6EvfIyNIzWOusmqAVh4L6r21GGTZtiKE4iqM4Q==", "os": ["linux"], "cpu": ["riscv64"] }, - "@oxlint/binding-linux-s390x-gnu@1.62.0": { - "integrity": "sha512-Ew2Kxs9EQ9/mbAIJ2hvocMC0wsOu6YKzStI2eFBDt+Td5O8seVC/oxgRIHqCcl5sf5ratA1nozQBAuv7tphkHg==", + "@oxlint/binding-linux-s390x-gnu@1.68.0": { + "integrity": "sha512-dIqnZnJSmHCMOUpUcWQOiV14o3DDPVx1DSsMaSzvdhNjC1tB1iEPZbdiMSCIEYbkgbsYznHXWqFdKL8WUB3F8g==", "os": ["linux"], "cpu": ["s390x"] }, - "@oxlint/binding-linux-x64-gnu@1.62.0": { - "integrity": "sha512-5z25jcAA0gfKyVwz71A0VXgaPlocPoTAxhlv/hgoK6tlCrfoNuw7haWbDHvGMfjXhdic4EqVXGRv5XsTqFnbRQ==", + "@oxlint/binding-linux-x64-gnu@1.68.0": { + "integrity": "sha512-zc9lEnfV/HreDTY6gdMlZe+irkwHSxQ4/B1pS9GyK7RVaA5LxhoZY/w6/o2vIwLLEYiXQ5ujGxOM1ZazeFAAIA==", "os": ["linux"], "cpu": ["x64"] }, - "@oxlint/binding-linux-x64-musl@1.62.0": { - "integrity": "sha512-IWpHmMB6ZDllPvqWDkG6AmXrN7JF5e/c4g/0PuURsmlK+vHoYZPB70rr4u1bn3I4LsKCSpqqfveyx6UCOC8wdg==", + "@oxlint/binding-linux-x64-musl@1.68.0": { + "integrity": "sha512-Dl5QEX0TCo/40Cdh1o1JdPS//+YiWqjC+Hrrya5OQmStZZr4svAFtdlqcpCrU9yq2Mo3vRVyO9B3h0dzD8s36Q==", "os": ["linux"], "cpu": ["x64"] }, - "@oxlint/binding-openharmony-arm64@1.62.0": { - "integrity": "sha512-fjlSxxrD5pA594vkyikCS9MnPRjQawW6/BLgyTYkO+73wwPlYjkcZ7LSd974l0Q2zkHQmu4DPvJFLYA7o8xrxQ==", + "@oxlint/binding-openharmony-arm64@1.68.0": { + "integrity": "sha512-/qy6dOvi4S3/LeXq0l5BT5pRKPYA7oj3uKwJOAZOr5HRLL+HK6jdBynvWuXIA2wwfE01RzNYmbBdM7vwYx00sA==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@oxlint/binding-win32-arm64-msvc@1.62.0": { - "integrity": "sha512-EiFXr8loNS0Ul3Gu80+9nr1T8jRmnKocqmHHg16tj5ZqTgUXyb97l2rrspVHdDluyFn9JfR4PoJFdNzw4paHww==", + "@oxlint/binding-win32-arm64-msvc@1.68.0": { + "integrity": "sha512-fHNtVqPHSYE7UFDSLVFUjxQjnSVXxseNJmRW+XuP4pXXDwePdPda43NL7/BBCFTxHjycOc44JNDaOPtFDNui9A==", "os": ["win32"], "cpu": ["arm64"] }, - "@oxlint/binding-win32-ia32-msvc@1.62.0": { - "integrity": "sha512-IgOFvL73li1bFgab+hThXYA0N2Xms2kV2MvZN95cebV+fmrZ9AVui1JSxfeeqRLo3CpPxKZlzhyq4G0cnaAvIw==", + "@oxlint/binding-win32-ia32-msvc@1.68.0": { + "integrity": "sha512-NnKXr4Wgo4nps3erhrE0f8shBvBPZMHg72nDsvX0JyrRvsNiP3f1JNvbCKh+A6VFvpF7ZoJxu904P3cKMhvZnA==", "os": ["win32"], "cpu": ["ia32"] }, - "@oxlint/binding-win32-x64-msvc@1.62.0": { - "integrity": "sha512-6hMpyDWQ2zGA1OXFKBrdYMUveUCO8UJhkO6JdwZPd78xIdHZNhjx+pib+4fC2Cljuhjyl0QwA2F3df/bs4Bp6A==", + "@oxlint/binding-win32-x64-msvc@1.68.0": { + "integrity": "sha512-zg5pA+84AlU6XHJ3ruiRxziO71QTrz8nLsk6u01JGS5+tL9/bnlakFiklFrcy4R1/V7ktWtaNitN3JZWmKnf6g==", "os": ["win32"], "cpu": ["x64"] }, - "@rolldown/binding-android-arm64@1.0.0": { - "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", + "@rolldown/binding-android-arm64@1.0.3": { + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "os": ["android"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-arm64@1.0.0": { - "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", + "@rolldown/binding-darwin-arm64@1.0.3": { + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "os": ["darwin"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-x64@1.0.0": { - "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", + "@rolldown/binding-darwin-x64@1.0.3": { + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "os": ["darwin"], "cpu": ["x64"] }, - "@rolldown/binding-freebsd-x64@1.0.0": { - "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", + "@rolldown/binding-freebsd-x64@1.0.3": { + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "os": ["freebsd"], "cpu": ["x64"] }, - "@rolldown/binding-linux-arm-gnueabihf@1.0.0": { - "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", + "@rolldown/binding-linux-arm-gnueabihf@1.0.3": { + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "os": ["linux"], "cpu": ["arm"] }, - "@rolldown/binding-linux-arm64-gnu@1.0.0": { - "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", + "@rolldown/binding-linux-arm64-gnu@1.0.3": { + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-arm64-musl@1.0.0": { - "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", + "@rolldown/binding-linux-arm64-musl@1.0.3": { + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-ppc64-gnu@1.0.0": { - "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", + "@rolldown/binding-linux-ppc64-gnu@1.0.3": { + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "os": ["linux"], "cpu": ["ppc64"] }, - "@rolldown/binding-linux-s390x-gnu@1.0.0": { - "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", + "@rolldown/binding-linux-s390x-gnu@1.0.3": { + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "os": ["linux"], "cpu": ["s390x"] }, - "@rolldown/binding-linux-x64-gnu@1.0.0": { - "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", + "@rolldown/binding-linux-x64-gnu@1.0.3": { + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-linux-x64-musl@1.0.0": { - "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", + "@rolldown/binding-linux-x64-musl@1.0.3": { + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-openharmony-arm64@1.0.0": { - "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", + "@rolldown/binding-openharmony-arm64@1.0.3": { + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@rolldown/binding-wasm32-wasi@1.0.0": { - "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", + "@rolldown/binding-wasm32-wasi@1.0.3": { + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "dependencies": [ "@emnapi/core", "@emnapi/runtime", @@ -439,18 +429,18 @@ ], "cpu": ["wasm32"] }, - "@rolldown/binding-win32-arm64-msvc@1.0.0": { - "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", + "@rolldown/binding-win32-arm64-msvc@1.0.3": { + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "os": ["win32"], "cpu": ["arm64"] }, - "@rolldown/binding-win32-x64-msvc@1.0.0": { - "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", + "@rolldown/binding-win32-x64-msvc@1.0.3": { + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "os": ["win32"], "cpu": ["x64"] }, - "@rolldown/pluginutils@1.0.0": { - "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==" + "@rolldown/pluginutils@1.0.1": { + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==" }, "@standard-community/standard-json@0.3.5_@standard-schema+spec@1.1.0_@types+json-schema@7.0.15_arktype@2.2.0_quansync@0.2.11": { "integrity": "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA==", @@ -488,14 +478,14 @@ "@types/json-schema@7.0.15": { "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, - "@types/node@25.6.0": { - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "@types/node@25.9.2": { + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", "dependencies": [ "undici-types" ] }, - "arkenv@0.11.0_arktype@2.2.0": { - "integrity": "sha512-XLVk3uiNRwlaqJiRWqVGz5y75P30DczxuupzVz3EEbs2j539ftwusvlK+5y/MOIerUbskSpsm6c6B61YMr/UPA==", + "arkenv@0.11.1_arktype@2.2.0": { + "integrity": "sha512-1b0P61cgHsd0ihUS98fp0XOZrXRbju8qcpLCqGu1Corkd4GzKoDee34D4NVU3Taxlvi+tx+htSVc7g2pqePRqw==", "dependencies": [ "arktype" ], @@ -532,8 +522,8 @@ "openapi-types@12.1.3": { "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" }, - "oxfmt@0.48.0": { - "integrity": "sha512-AVaLh+7XeGx+R1zfFV+f6VV61nT2MWVJXVUDhbTm5LBWGyNt64xAyh3NYYyjeY2WykNt9AvqSQLPHcbWquYF9g==", + "oxfmt@0.52.0": { + "integrity": "sha512-nJlYM35F64zTDMecCNhoHNkf+D/eHv7xcjj9XDSj+bFAVtN93m7v8DQMdHd6nDG6Akf/kEYYHmDUBs2Dz27Sug==", "dependencies": [ "tinypool" ], @@ -560,8 +550,8 @@ ], "bin": true }, - "oxlint-tsgolint@0.22.1": { - "integrity": "sha512-YUSGSLUnoolsu8gxISEDio3q1rtsCozwfOzASUn3DT2mR2EeQ93uEEnen7s+6LpF+lyTQFln1pQfqwBh/fsVEg==", + "oxlint-tsgolint@0.23.0": { + "integrity": "sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==", "optionalDependencies": [ "@oxlint-tsgolint/darwin-arm64", "@oxlint-tsgolint/darwin-x64", @@ -572,8 +562,8 @@ ], "bin": true }, - "oxlint@1.62.0_oxlint-tsgolint@0.22.1": { - "integrity": "sha512-1uFkg6HakjsGIpW9wNdeW4/2LOHW9MEkoWjZUTUfQtIHyLIZPYt00w3Sg+H3lH+206FgBPHBbW5dVE5l2ExECQ==", + "oxlint@1.68.0_oxlint-tsgolint@0.23.0": { + "integrity": "sha512-dXcbq+xsmLrMy6T8d0euf3IYUfLmjHIE11pOxiUSi5LHkFZaYPv568R6sEjcavVpUxoaQe66UBuK4HEi74NxpA==", "dependencies": [ "oxlint-tsgolint" ], @@ -606,8 +596,8 @@ "quansync@0.2.11": { "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==" }, - "rolldown@1.0.0": { - "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", + "rolldown@1.0.3": { + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dependencies": [ "@oxc-project/types", "@rolldown/pluginutils" @@ -637,8 +627,8 @@ "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "undici-types@7.19.2": { - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==" + "undici-types@7.24.6": { + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==" } }, "workspace": { @@ -655,18 +645,16 @@ "jsr:@std/encoding@^1.0.10", "jsr:@std/fmt@^1.0.10", "jsr:@std/fs@^1.0.23", - "jsr:@std/path@^1.1.4", - "jsr:@std/streams@^1.1.0", "jsr:@std/ulid@1", "npm:@types/node@^25.6.0", "npm:arkenv@0.11", "npm:arktype@^2.2.0", "npm:blake3-jit@^1.1.0", "npm:hono-openapi@^1.3.0", - "npm:oxfmt@0.48", - "npm:oxlint-tsgolint@~0.22.1", + "npm:oxfmt@0.52", + "npm:oxlint-tsgolint@0.23", "npm:oxlint@^1.62.0", - "npm:rolldown@1.0.0" + "npm:rolldown@1.0.3" ] } } diff --git a/mise.toml b/mise.toml index fd9bf908..2fad8b2f 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,5 @@ [tools] -deno = "2.7" +deno = "2.8.3" [tasks."install"] description = "Install all dependencies" @@ -79,6 +79,11 @@ description = "Build standalone binary" env = { STANDALONE_OUTPUT = "./dist/backend.windows-amd64.exe", STANDALONE_TARGET = "x86_64-pc-windows-msvc" } run = [{ task = "build:standalone_" }] +[tasks."build:standalone:windows-arm64"] +description = "Build standalone binary" +env = { STANDALONE_OUTPUT = "./dist/backend.windows-arm64.exe", STANDALONE_TARGET = "aarch64-pc-windows-msvc" } +run = [{ task = "build:standalone_" }] + [tasks."test"] description = "Run all tests" run = [{ task = "install" }, "mise exec -- deno test -A"] @@ -119,14 +124,6 @@ run = [{ task = "clean:deno" }, { task = "install:deno" }] description = "Start backend" run = [{ task = "start:server" }] -[tasks."start:dev"] -alias = "dev" -description = "Start devel server" -run = [ - { task = "install" }, - "JSPB_DEBUG_DATABASE_EPHEMERAL=true JSPB_LOG_VERBOSITY=4 mise exec -- deno run -A ./src/index.ts" -] - [tasks."start:build"] description = "Start dedicated server" run = [{ task = "build:server" }, { task = "start:server" }] diff --git a/rolldown.config.ts b/rolldown.config.ts index 2c59e6b8..86592a1e 100644 --- a/rolldown.config.ts +++ b/rolldown.config.ts @@ -18,7 +18,7 @@ export default { ".sql": "text" }, plugins: [ - deno(), + deno({ noTranspile: true }), bundleAnalyzerPlugin({ fileName: "metadata.json" }) diff --git a/rolldown.deno.ts b/rolldown.deno.ts index 5f12b247..b3986aac 100644 --- a/rolldown.deno.ts +++ b/rolldown.deno.ts @@ -2,6 +2,8 @@ // vibecode resolver // based on: https://github.com/denoland/deno-rolldown-plugin +import { fileURLToPath } from "node:url"; + import { type Loader, type LoadResponse, @@ -11,7 +13,6 @@ import { Workspace, type WorkspaceOptions } from "@deno/loader"; -import { fromFileUrl } from "@std/path"; const MARegex = /.*/; @@ -193,7 +194,7 @@ export function deno(pluginOptions: DenoPluginOptions = {}): DenoPlugin { } if (specifier.startsWith("file:///")) { - specifier = fromFileUrl(specifier); + specifier = fileURLToPath(specifier); } modules.set(specifier, { diff --git a/src/database/migration.ts b/src/database/migration.ts index 6d55949c..34680934 100644 --- a/src/database/migration.ts +++ b/src/database/migration.ts @@ -6,6 +6,9 @@ import type { Database } from "#db/index.ts"; import { Logger } from "#util/console.ts"; import { generateHash } from "#util/crypto.ts"; +import sql0001 from "./migrations/0001.sql" with { type: "text" }; +import sql0002 from "./migrations/0002.sql" with { type: "text" }; + const log: Logger = new Logger("database::migration"); type Migration = { @@ -24,7 +27,7 @@ export const migrations: Migration[] = [ */ { id: "0001.base", - sql: (await import("./migrations/0001.sql", { with: { type: "text" } })).default + sql: sql0001 }, /** @@ -68,6 +71,6 @@ export const migrations: Migration[] = [ } } }, - sql: (await import("./migrations/0002.sql", { with: { type: "text" } })).default + sql: sql0002 } ] as const; diff --git a/src/endpoints/legacy/v2/documents/access.route.ts b/src/endpoints/legacy/v2/documents/access.route.ts deleted file mode 100644 index 57123f32..00000000 --- a/src/endpoints/legacy/v2/documents/access.route.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { toText } from "@std/streams"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { verifyHash } from "#util/crypto.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsRead } from "#util/fs.ts"; -import { validatorDocumentName, validatorDocumentPassword } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaHeader = type({ - "password?": validatorDocumentPassword -}); - -const schemaBodyResponse = await resolver( - type({ - key: type.string.configure({ - description: "The document name (formerly key)", - examples: ["abc123"] - }), - data: type.string.configure({ - description: "The document data", - examples: ["Hello, World!"] - }), - url: type.string.configure({ - deprecated: true, - description: "The document URL", - examples: ["https://jspaste.eu/abc123"] - }), - expirationTimestamp: type.number.configure({ - deprecated: true, - description: "The document expiration timestamp (always will be 0)", - examples: [0] - }) - }) -).toOpenAPISchema(); - -export default new Hono().get( - "/:name", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Get document", - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - validator("header", schemaHeader, validatorHandler), - async (ctx) => { - // https://github.com/honojs/hono/issues/1130 - if (ctx.req.method === "HEAD") { - return ctx.body(null); - } - - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - // @ts-expect-error upstream - const header = ctx.req.valid("header") as typeof schemaHeader.infer; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - if (document.password) { - if (!header.password) { - return errorThrow(ErrorCode.DocumentPasswordNeeded); - } - - if (!verifyHash(header.password, document.password)) { - return errorThrow(ErrorCode.DocumentInvalidPassword); - } - } - - return ctx.json({ - key: param.name, - data: await toText(await fsRead(ctx, document, true)), - url: new URL(ctx.req.url).host.concat("/", param.name), - expirationTimestamp: 0 - }); - } -); diff --git a/src/endpoints/legacy/v2/documents/accessRaw.route.ts b/src/endpoints/legacy/v2/documents/accessRaw.route.ts deleted file mode 100644 index 5d16c133..00000000 --- a/src/endpoints/legacy/v2/documents/accessRaw.route.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { verifyHash } from "#util/crypto.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsRead } from "#util/fs.ts"; -import { validatorDocumentName, validatorDocumentPassword } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaHeader = type({ - "password?": validatorDocumentPassword -}); - -const schemaQuery = type({ - "p?": validatorDocumentPassword -}); - -const schemaBodyResponse = await resolver(type.unknown).toOpenAPISchema(); - -export default new Hono().get( - "/:name/raw", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Get document data", - responses: { - 200: { - content: { - "text/plain": { - schema: schemaBodyResponse.schema - }, - "application/octet-stream": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - validator("header", schemaHeader, validatorHandler), - validator("query", schemaQuery, validatorHandler), - async (ctx) => { - // https://github.com/honojs/hono/issues/1130 - if (ctx.req.method === "HEAD") { - return ctx.body(null); - } - - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - // @ts-expect-error upstream - const header = ctx.req.valid("header") as typeof schemaHeader.infer; - // @ts-expect-error upstream - const query = ctx.req.valid("query") as typeof schemaQuery.infer; - const options = { - password: header.password || query.p - }; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - if (document.password) { - if (!options.password) { - return errorThrow(ErrorCode.DocumentPasswordNeeded); - } - - if (!verifyHash(options.password, document.password)) { - return errorThrow(ErrorCode.DocumentInvalidPassword); - } - } - - ctx.res.headers.set("content-type", "text/plain"); - ctx.res.headers.set("transfer-encoding", "chunked"); - - return ctx.body(await fsRead(ctx, document, true)); - } -); diff --git a/src/endpoints/legacy/v2/documents/edit.route.ts b/src/endpoints/legacy/v2/documents/edit.route.ts deleted file mode 100644 index 93167f37..00000000 --- a/src/endpoints/legacy/v2/documents/edit.route.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { bodyStream } from "#http/middleware/bodyStream.ts"; -import { env } from "#util/env.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsWrite } from "#util/fs.ts"; -import { validatorDocumentName } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaBody = await resolver( - type.string.configure({ - description: "Data to replace in the document", - examples: ["Hello world!"] - }) -).toOpenAPISchema(); - -const schemaBodyResponse = await resolver( - type({ - edited: type.boolean.configure({ - description: "Confirmation of edition", - examples: [true] - }) - }) -).toOpenAPISchema(); - -export default new Hono().patch( - "/:name", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Edit document", - requestBody: { - content: { - "text/plain": { - schema: schemaBody.schema - } - } - }, - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - bodyStream, - async (ctx) => { - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id || document.user_id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - - mutableDatabase.document.update("name", param.name, "version", env.JSPB_DOCUMENT_COMPRESSION); - await fsWrite(ctx, document); - - return ctx.json({ - edited: true - }); - } -); diff --git a/src/endpoints/legacy/v2/documents/exists.route.ts b/src/endpoints/legacy/v2/documents/exists.route.ts deleted file mode 100644 index aa91185f..00000000 --- a/src/endpoints/legacy/v2/documents/exists.route.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { genericErrorResponse } from "#util/error.ts"; -import { validatorDocumentName } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaBodyResponse = await resolver(type.boolean).toOpenAPISchema(); - -export default new Hono().get( - "/:name/exists", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Check document", - responses: { - 200: { - content: { - "text/plain": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - (ctx) => { - // https://github.com/honojs/hono/issues/1130 - if (ctx.req.method === "HEAD") { - return ctx.body(null); - } - - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - - return ctx.text(mutableDatabase.document.get("name", param.name)?.name ? "true" : "false"); - } -); diff --git a/src/endpoints/legacy/v2/documents/index.ts b/src/endpoints/legacy/v2/documents/index.ts deleted file mode 100644 index 25ec8cf7..00000000 --- a/src/endpoints/legacy/v2/documents/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Hono } from "hono/tiny"; - -import type { Env } from "#http/handler.ts"; - -import access from "./access.route.ts"; -import accessRaw from "./accessRaw.route.ts"; -import edit from "./edit.route.ts"; -import exists from "./exists.route.ts"; -import publish from "./publish.route.ts"; -import remove from "./remove.route.ts"; - -export const v2LegacyDocumentHandler = new Hono(); - -v2LegacyDocumentHandler.route("/", access); -v2LegacyDocumentHandler.route("/", accessRaw); -v2LegacyDocumentHandler.route("/", edit); -v2LegacyDocumentHandler.route("/", exists); -v2LegacyDocumentHandler.route("/", publish); -v2LegacyDocumentHandler.route("/", remove); diff --git a/src/endpoints/legacy/v2/documents/publish.route.ts b/src/endpoints/legacy/v2/documents/publish.route.ts deleted file mode 100644 index 581386cc..00000000 --- a/src/endpoints/legacy/v2/documents/publish.route.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { monotonicUlid } from "@std/ulid"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { - constantDocumentNameLengthMax, - constantDocumentNameLengthMin, - constantHttpStatusCodes, - mutableDatabase -} from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { bodyStream } from "#http/middleware/bodyStream.ts"; -import { generateHash } from "#util/crypto.ts"; -import { generateName } from "#util/document.ts"; -import { env } from "#util/env.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsWrite } from "#util/fs.ts"; -import { validatorDocumentName, validatorDocumentPassword } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaBody = await resolver( - type.string.configure({ - description: "Data to replace in the document", - examples: ["Hello world!"] - }) -).toOpenAPISchema(); - -const schemaHeader = type({ - "password?": validatorDocumentPassword, - "key?": validatorDocumentName, - "keylength?": type.number.atLeast(constantDocumentNameLengthMin).atMost(constantDocumentNameLengthMax).configure({ - description: "The document name length" - }) -}); - -const schemaBodyResponse = await resolver( - type({ - key: type.string.configure({ - description: "The document name (formerly key)", - examples: ["abc123"] - }) - }) -).toOpenAPISchema(); - -export default new Hono().post( - "/", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Publish document", - requestBody: { - content: { - "text/plain": { - schema: schemaBody.schema - } - } - }, - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("header", schemaHeader, validatorHandler), - bodyStream, - async (ctx) => { - const { - password, - key: name, - keylength: nameLength - // @ts-expect-error upstream - } = ctx.req.valid("header") as typeof schemaHeader.infer; - - let setName: string; - if (name) { - if (mutableDatabase.document.get("name", name)?.name) { - return errorThrow(ErrorCode.DocumentNameAlreadyExists); - } - - setName = name; - } else { - setName = generateName(nameLength); - } - - const id = monotonicUlid(); - - let hashCombo: string | null; - if (password) { - hashCombo = generateHash(password).combo; - } else { - hashCombo = null; - } - - mutableDatabase.document.create({ - id: id, - user_id: null, - version: env.JSPB_DOCUMENT_COMPRESSION, - name: setName, - password: hashCombo - }); - await fsWrite(ctx, { id: id }); - - return ctx.json({ - key: setName, - secret: "", - url: new URL(ctx.req.url).host.concat("/", setName), - expirationTimestamp: 0 - }); - } -); diff --git a/src/endpoints/legacy/v2/documents/remove.route.ts b/src/endpoints/legacy/v2/documents/remove.route.ts deleted file mode 100644 index d45f0a71..00000000 --- a/src/endpoints/legacy/v2/documents/remove.route.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsDelete } from "#util/fs.ts"; -import { validatorDocumentName } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaBodyResponse = await resolver( - type({ - removed: type.true.configure({ - description: "Confirmation of deletion", - examples: [true] - }) - }) -).toOpenAPISchema(); - -export default new Hono().delete( - "/:name", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Remove document", - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - (ctx) => { - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id || document.user_id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - - mutableDatabase.document.delete("name", param.name); - void fsDelete(document); - - return ctx.json({ removed: true }); - } -); diff --git a/src/endpoints/meta/index.ts b/src/endpoints/meta/index.ts new file mode 100644 index 00000000..b1717e11 --- /dev/null +++ b/src/endpoints/meta/index.ts @@ -0,0 +1,11 @@ +import { Hono } from "hono/tiny"; + +import type { Env } from "#http/handler.ts"; + +import robots from "./robots.ts"; +import wellKnown from "./wellKnown.ts"; + +export const metaHandler = new Hono(); + +metaHandler.route("/", robots); +metaHandler.route("/", wellKnown); diff --git a/src/endpoints/meta/robots.ts b/src/endpoints/meta/robots.ts new file mode 100644 index 00000000..7400fe4e --- /dev/null +++ b/src/endpoints/meta/robots.ts @@ -0,0 +1,7 @@ +import { Hono } from "hono/tiny"; + +import type { Env } from "#http/handler.ts"; + +import robots from "./robots.txt" with { type: "text" }; + +export default new Hono().get("/robots.txt", (ctx) => ctx.text(robots)); diff --git a/src/endpoints/meta/robots.txt b/src/endpoints/meta/robots.txt new file mode 100644 index 00000000..1f53798b --- /dev/null +++ b/src/endpoints/meta/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/src/endpoints/meta/wellKnown.ts b/src/endpoints/meta/wellKnown.ts new file mode 100644 index 00000000..fb18c656 --- /dev/null +++ b/src/endpoints/meta/wellKnown.ts @@ -0,0 +1,23 @@ +import { Hono } from "hono/tiny"; + +import type { Env } from "#http/handler.ts"; +import { env } from "#util/env.ts"; + +const wellKnown = new Hono(); + +export default wellKnown; + +wellKnown.get("/.well-known/jspaste", (ctx) => + ctx.json({ + document: { + anonymousTtl: env.JSPB_DOCUMENT_ANONYMOUS_AGE.total("seconds"), + maxSize: env.JSPB_DOCUMENT_SIZE, + minSize: 0, + ttl: env.JSPB_DOCUMENT_AGE.total("seconds") + }, + path: env.JSPB_API, + user: { + public: env.JSPB_USER_REGISTER + } + }) +); diff --git a/src/http/handler.ts b/src/http/handler.ts index 919ea92b..9d491d96 100644 --- a/src/http/handler.ts +++ b/src/http/handler.ts @@ -4,7 +4,7 @@ import { HTTPException } from "hono/http-exception"; import { Hono } from "hono/tiny"; import { v1DocumentHandler } from "#endpoint/document/v1/index.ts"; -import { v2LegacyDocumentHandler } from "#endpoint/legacy/v2/documents/index.ts"; +import { metaHandler } from "#endpoint/meta/index.ts"; import { v1UserHandler } from "#endpoint/user/v1/index.ts"; import { Logger } from "#util/console.ts"; import { env } from "#util/env.ts"; @@ -20,7 +20,7 @@ export type Env = { }; export const handler = (): Hono => { - const handler = new Hono().basePath("/api"); + const handler = new Hono(); handler.notFound((ctx) => { return ctx.body(null, 404); @@ -59,8 +59,10 @@ export const handler = (): Hono => { ctx.res.headers.set("Cache-Control", "no-transform"); }); - handler.get( - "/oas.json", + handler.route("/", metaHandler); + + handler.basePath(env.JSPB_API).get( + "/docs.json", openAPIRouteHandler(handler, { documentation: { openapi: "3.1.0", @@ -73,17 +75,7 @@ export const handler = (): Hono => { ## User class - **Anonymous:** Can alter anonymous documents, everyone can alter their documents. - **Registered:** Can alter their own and anonymous documents, only they and "root" can alter their documents. -- **"root":** Can alter every document, no one can alter their documents except "root" itself. - -## Restrictions -Each instance can impose restrictions to the API usage. These restrictions may include, but not limited to: - -(the following values might change without notice) -- Instance registration policy: ${env.JSPB_USER_REGISTER ? "OPEN" : "CLOSED"} -- Document size limit: ${env.JSPB_DOCUMENT_SIZE === 0 ? "unlimited" : env.JSPB_DOCUMENT_SIZE} -- Document lifetime: ${env.JSPB_DOCUMENT_AGE.total("minutes") === 0 ? "unlimited" : env.JSPB_DOCUMENT_AGE.total("minutes")} -- Document anonymous lifetime: ${env.JSPB_DOCUMENT_ANONYMOUS_AGE.total("minutes") === 0 ? "unlimited" : env.JSPB_DOCUMENT_ANONYMOUS_AGE.total("minutes")} -`, +- **"root":** Can alter every document, no one can alter their documents except "root" itself.`, license: { name: "EUPL-1.2", url: "https://eur-lex.europa.eu/eli/dec_impl/2017/863" @@ -109,7 +101,7 @@ Each instance can impose restrictions to the API usage. These restrictions may i description: "Official JSPaste instance" }, { - url: "http://localhost:4000", + url: "http://localhost:8080", description: "Local instance" } ] @@ -117,16 +109,8 @@ Each instance can impose restrictions to the API usage. These restrictions may i }) ); - // deprecated - handler.get("/documents/*", (ctx) => { - return ctx.redirect(ctx.req.path.replace(/\/documents\//g, "/v2/documents/"), 307); - }); - - handler.route("/document/v1", v1DocumentHandler); - handler.route("/user/v1", v1UserHandler); - - // deprecated - handler.route("/v2/documents", v2LegacyDocumentHandler); + handler.basePath(env.JSPB_API).route("/document/v1", v1DocumentHandler); + handler.basePath(env.JSPB_API).route("/user/v1", v1UserHandler); return handler; }; diff --git a/src/index.ts b/src/index.ts index d071b774..24bd3ce0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,8 @@ import { Logger } from "#util/console.ts"; import "@std/dotenv/load"; declare global { - // oxlint-disable-next-line typescript-eslint/consistent-type-definitions: expected + // expected + // oxlint-disable-next-line typescript-eslint/consistent-type-definitions interface ArkEnv { meta(): { ref?: string; diff --git a/src/init.ts b/src/init.ts index e82464df..aaa95ee2 100644 --- a/src/init.ts +++ b/src/init.ts @@ -40,7 +40,7 @@ export const initHTTPServer = async (handler?: Deno.ServeHandler): Pr run: async (): Promise => { mutableHttpServer.unref(); - // Deno.serve will deadlock on shutdown under pressure + // FIXME: Deno.serve will deadlock on shutdown under pressure await mutableHttpServer.shutdown(); } }); diff --git a/src/utils/env.ts b/src/utils/env.ts index bb853f9e..6af3a2f0 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -1,3 +1,5 @@ +import { normalize } from "node:path/posix"; + import arkenv from "arkenv"; import { type } from "arktype"; @@ -17,7 +19,11 @@ export const env = arkenv( }; }) .default("::"), - JSPB_PORT: type.keywords.number.integer.atLeast(0).atMost(65_535).default(4000), + JSPB_PORT: type.keywords.number.integer.atLeast(0).atMost(65_535).default(8080), + JSPB_API: type(/^\/[\w/-]*$/) + .pipe((string) => normalize(`${string}/`)) + .describe("a valid absolute URL path") + .default("/api/"), // debug JSPB_DEBUG_DATABASE_EPHEMERAL: type.boolean.default(false), diff --git a/src/utils/fs.ts b/src/utils/fs.ts index 75f5b0d5..5ce7882c 100644 --- a/src/utils/fs.ts +++ b/src/utils/fs.ts @@ -18,13 +18,15 @@ export const fsWrite = async (ctx: Context, { id }: Pick): let stream: ReadableStream; switch (env.JSPB_DOCUMENT_COMPRESSION) { case documentVersionV1: { - // oxlint-disable-next-line typescript-eslint/no-non-null-assertion: ctx.req.raw.body is only null on GET/HEAD + // ctx.req.raw.body is only null on GET/HEAD + // oxlint-disable-next-line typescript/no-non-null-assertion stream = ctx.req.raw.body!.pipeThrough(new CompressionStream("deflate")); break; } case documentVersionV2: { - // oxlint-disable-next-line typescript-eslint/no-non-null-assertion: ctx.req.raw.body is only null on GET/HEAD + // ctx.req.raw.body is only null on GET/HEAD + // oxlint-disable-next-line typescript/no-non-null-assertion stream = ctx.req.raw.body!; break; @@ -60,17 +62,14 @@ export const fsDelete = async ({ id }: Pick): Promise => { export const fsRead = async ( ctx: Context, - { id, version }: Pick, - clientIgnoreCapabilities = false + { id, version }: Pick ): Promise> => { const handle = await Deno.open(constantPathStructStorageData + id); - const hasClientDeflate = clientIgnoreCapabilities ? false : ctx.req.header("accept-encoding")?.includes("deflate"); - let stream: ReadableStream; switch (version) { case documentVersionV1: { - if (hasClientDeflate) { + if (ctx.req.header("accept-encoding")?.includes("deflate")) { ctx.res.headers.set("content-encoding", "deflate"); stream = handle.readable; } else { diff --git a/src/utils/validator/document.ts b/src/utils/validator/document.ts index 41a2761e..861e6d3c 100644 --- a/src/utils/validator/document.ts +++ b/src/utils/validator/document.ts @@ -18,7 +18,6 @@ export const validatorDocumentName = type(regexBase64URL) description: "The document name", examples: ["myDocumentNameHere"], expected: (ctx) => { - // oxlint-disable-next-line typescript-eslint/switch-exhaustiveness-check switch (ctx.code) { case "pattern": { return "a valid Base64URL"; @@ -42,7 +41,6 @@ export const validatorDocumentNameLength = type.keywords.string.integer.parse ref: "DocumentNameLength", description: "The name length for the document", expected: (ctx) => { - // oxlint-disable-next-line typescript-eslint/switch-exhaustiveness-check switch (ctx.code) { case "domain": { return "a valid integer"; diff --git a/src/utils/validator/user.ts b/src/utils/validator/user.ts index 9ebb0fb2..32745359 100644 --- a/src/utils/validator/user.ts +++ b/src/utils/validator/user.ts @@ -9,7 +9,6 @@ export const validatorUserToken = type.string.exactlyLength(constantUserTokenLen description: "A user token", examples: ["myUserTokenHere"], expected: (ctx) => { - // oxlint-disable-next-line typescript-eslint/switch-exhaustiveness-check switch (ctx.code) { case "domain": { return "a string";