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..2a17cd7f 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@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 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@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 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..8297249e 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: "false" - name: Setup mise-en-place - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 + uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.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..b5509e5f 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,19 +27,17 @@ "@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", + "@types/node": "npm:@types/node@^26.0.0", + "arkenv": "npm:arkenv@~0.12.0", "arktype": "npm:arktype@^2.2.0", "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.55.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.1.2" }, "fmt": { "exclude": ["**"] diff --git a/deno.lock b/deno.lock index d61f95db..74f255b5 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.24", + "jsr:@hono/hono@^4.8.3": "4.12.24", "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@26": "26.0.0", + "npm:arkenv@0.12": "0.12.2_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.55": "0.55.0", + "npm:oxlint-tsgolint@0.23": "0.23.0", + "npm:oxlint@^1.62.0": "1.69.0_oxlint-tsgolint@0.23.0", + "npm:rolldown@1.1.2": "1.1.2" }, "jsr": { "@deno/loader@0.5.0": { "integrity": "a6d94408de5e6bacac404f8f6963c8b8cc278cfd1a878aa2f06b34a083d6bfee" }, - "@hono/hono@4.12.16": { - "integrity": "d3a3ca0d713852c91881541e74d5ffac0b4af38b8c1285e8ed5ee47b68c58f94" + "@hono/hono@4.12.24": { + "integrity": "a74a40f06ae6704ddd0e8e576f6da9d34666c3e1e0f5412a2c1ff800ffd092f4" }, "@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": { @@ -120,318 +110,318 @@ "@ark/util@0.56.0": { "integrity": "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==" }, - "@emnapi/core@1.10.0": { - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "@emnapi/core@1.11.1": { + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "dependencies": [ "@emnapi/wasi-threads", "tslib" ] }, - "@emnapi/runtime@1.10.0": { - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "@emnapi/runtime@1.11.1": { + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dependencies": [ "tslib" ] }, - "@emnapi/wasi-threads@1.2.1": { - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "@emnapi/wasi-threads@1.2.2": { + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dependencies": [ "tslib" ] }, - "@napi-rs/wasm-runtime@1.1.4_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0": { - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "@napi-rs/wasm-runtime@1.1.5_@emnapi+core@1.11.1_@emnapi+runtime@1.11.1": { + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dependencies": [ "@emnapi/core", "@emnapi/runtime", "@tybys/wasm-util" ] }, - "@oxc-project/types@0.129.0": { - "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==" + "@oxc-project/types@0.137.0": { + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==" }, - "@oxfmt/binding-android-arm-eabi@0.48.0": { - "integrity": "sha512-uwqk+/KhQvBIpULD8SMM/zAafMRC/+DV/xsEQjkkIsJ/kLmEI/2bxonVowcYTiXqqZ/a0FEW8DPkZY3VvwELDA==", + "@oxfmt/binding-android-arm-eabi@0.55.0": { + "integrity": "sha512-+rFDOqQe5LOWgxrAJaZgLRudr6GQm0wGI6gtu7vVkrdLGjNMUSGbAlaCr8j7F2H2Er97vYQCU8WDb30onqMM1g==", "os": ["android"], "cpu": ["arm"] }, - "@oxfmt/binding-android-arm64@0.48.0": { - "integrity": "sha512-VUCiKuXK5+McVssgHEJdrcGK7hRJzrRb36zm9/jwzMholyYt4BgXhw5Nm1V1DX6Ce717Zi/1jk432b/tgmQgtQ==", + "@oxfmt/binding-android-arm64@0.55.0": { + "integrity": "sha512-ctulLq8s3x8Zmvw6+iccB09TIKERAklRSmbJ10gk8mlAn05qZxoyo52dj3Hi9IJcmDSwF54fQaTVh2CbL6PInw==", "os": ["android"], "cpu": ["arm64"] }, - "@oxfmt/binding-darwin-arm64@0.48.0": { - "integrity": "sha512-IkKp8rnIyQLW6Jt+6jragCbUVYSayk55lapiprLjIVvt4NczLyO/nwX2GgefLQ5iaBdfS8UEAFgCs/pLO6Cl0w==", + "@oxfmt/binding-darwin-arm64@0.55.0": { + "integrity": "sha512-xDQczLH9pw/RBk1h/GH0qcGMm8hQtmtVHBNLSH3lk1gEIR09hZ4L+mJQl4VqiVAvPK9VG9PYrWWuSQLt7xTbiA==", "os": ["darwin"], "cpu": ["arm64"] }, - "@oxfmt/binding-darwin-x64@0.48.0": { - "integrity": "sha512-+aFuhsGIuvnoOjXyKVHMhPKJZR1kQkAl8QyrKoMlA7yJsSTC3N0Asl53La8TChSHhW8epToQ/Q0nvLmEmfNmLg==", + "@oxfmt/binding-darwin-x64@0.55.0": { + "integrity": "sha512-JaNoFCkF2CJdGgpPSMbuO9HVyXyoNGIhMHPvp6NYAjeVKw9XEYc0HcUWJLPQa3Q69WV5wMa9m5jPMJPtbLtcRg==", "os": ["darwin"], "cpu": ["x64"] }, - "@oxfmt/binding-freebsd-x64@0.48.0": { - "integrity": "sha512-fbqzQL8FjI9gGnktI7RIo0dksDziTAYBy7xlI7jU7eID5fxLF/25fS4Xj6GydD8Y5oWHL83U4NK160QaOAxtyg==", + "@oxfmt/binding-freebsd-x64@0.55.0": { + "integrity": "sha512-DNbszhpg6S2MIzax5azdHFTTBIVkR5xr8yyRZuA4yoDAwOkzIp3tmldgKZM2+VlT+hJIG0xUksA+elISzMEAfA==", "os": ["freebsd"], "cpu": ["x64"] }, - "@oxfmt/binding-linux-arm-gnueabihf@0.48.0": { - "integrity": "sha512-hn4i0zhAyTiB3ZHjQfYUZkDvrbVkohw1S7pySWxWUoZ87HnkDoTFThj7QTxk40hNPOTUP0vHbPRNamFIv1HBJQ==", + "@oxfmt/binding-linux-arm-gnueabihf@0.55.0": { + "integrity": "sha512-2snoaoRfFFyGnbOcKUK36rREBYxe/Xgz3uHbiA5zbCB/s6R4DQj4mHqYAaWWhgizCUSDxV8cE9zAZ0XleNpKGw==", "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.55.0": { + "integrity": "sha512-q1aktHF/WRpSK81BX1dE/9vWrS2jGw1Nax2kb4DBLGAewubCLcoNyp4Zl/NSMgbv3vUS46Z33wIQkBVYOP3PYg==", "os": ["linux"], "cpu": ["arm"] }, - "@oxfmt/binding-linux-arm64-gnu@0.48.0": { - "integrity": "sha512-5bVdwSwlm1M8wbYCorLOxWxUBw/8tBvHYyQNIfwWVPwOJaj5vg1APSGJQVpwJfV5VNE9PSrR91UKEpoNwHhqUA==", + "@oxfmt/binding-linux-arm64-gnu@0.55.0": { + "integrity": "sha512-VD0y36aENezl/3tsclA/4G53Cc7iV+7Uoh7gz4yvcOTaEYBtJpQsE6PKDGTtUtOvGS4kv51ybfXY/nWZejO5IA==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxfmt/binding-linux-arm64-musl@0.48.0": { - "integrity": "sha512-vCS3Fk7gFslTqE1lUE2IlroyVV7u/9SmMA/uBqDoshuck2psGWcjW0ePyPZI3rM3+qtf2pDaMVIKMHozraifuw==", + "@oxfmt/binding-linux-arm64-musl@0.55.0": { + "integrity": "sha512-r8xlKJFcsRmn0H5jZrdORae6RX9jDBrZVvOoxF+bCQtampQJClv80aZEHsv+NsLsp2KCE5ql79O7DpPVzYWpXA==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxfmt/binding-linux-ppc64-gnu@0.48.0": { - "integrity": "sha512-gKtfFfueUClXDumyoHUbymqRf7prHejOOyzJK0eIJn93GF9JBdFHdo60TM1ZBHxkEwZvjuOgHmKtneKbEOc/Eg==", + "@oxfmt/binding-linux-ppc64-gnu@0.55.0": { + "integrity": "sha512-GRKv/HXHcwIVld/WU61rF0g0R16hl5EJ+ScKdpjevT57lnLnagj/U2YUbXf2mT+2Pg1uCzWC+mvGicPV3CDdLQ==", "os": ["linux"], "cpu": ["ppc64"] }, - "@oxfmt/binding-linux-riscv64-gnu@0.48.0": { - "integrity": "sha512-SYt0UhOvZD/UwZz9sXq6J2uAw8o24f5VZpLB2DH01f6MevshmlgakQlZe2lwek2sZJkd07eLu7mZa0g7yeiw7Q==", + "@oxfmt/binding-linux-riscv64-gnu@0.55.0": { + "integrity": "sha512-rdv57enTiPtpSYRMKfAiEbQb0Puw5t9N7isVinDoo5qeLDScro2gznmZqSgSWbVZRzLisTeCTW8Qwgw0bOHv3A==", "os": ["linux"], "cpu": ["riscv64"] }, - "@oxfmt/binding-linux-riscv64-musl@0.48.0": { - "integrity": "sha512-JLbrwck2AopG4ud/XklZO5N+qxGC7cS7ROvXZVNfx0MCLDDL2kGOLvzuWORkVjnjAM0CMAfIMU2zNBtQbM+4dw==", + "@oxfmt/binding-linux-riscv64-musl@0.55.0": { + "integrity": "sha512-7v1nNrlD43VY6+sYQ6efYyb3lE6QY182304PD/768ZxTjOmFd/3dQa3u/nGBUAXYdGSWOQc5N3PnS0QzUXyEIA==", "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.55.0": { + "integrity": "sha512-f4lJLUSPOgScjFl9LiflKCTocyNRwE25JmTMbN4XQdDjoZzEHjqf3wA3VESF1/csg7i8m7+EQLbrZyYDqe10UQ==", "os": ["linux"], "cpu": ["s390x"] }, - "@oxfmt/binding-linux-x64-gnu@0.48.0": { - "integrity": "sha512-oEz1BQwMrV7OMEFx/3VPDU3n9TM0AnxpktDYXjEg5i6nTX87wo18wSfBvkl4tzAICdKtoAQAdBIl7Y7hsPlx5w==", + "@oxfmt/binding-linux-x64-gnu@0.55.0": { + "integrity": "sha512-MihqiPziJNoWy4MqNSV+jVA1g+07iQDjZiR0vaCaDoPgFEiJpCMsxamktzLV07cEeQsSJ04vQaU4CzCQwIvtDA==", "os": ["linux"], "cpu": ["x64"] }, - "@oxfmt/binding-linux-x64-musl@0.48.0": { - "integrity": "sha512-g2SKTTurP5mWjd8Ecait0erYqmltL4IqW1EwttM25BxM6NiTt4ubobJYMR1uox1V2QgG4UfHH10CGRvWlUixjw==", + "@oxfmt/binding-linux-x64-musl@0.55.0": { + "integrity": "sha512-Yqghym7KYAVjP9MmSrNZiDeerMuoejNjo0r3ox5H3GDKk8eAfl8VyJm9i+pWCLDCTnAbcTUMMN2ZKjUYXH1v3g==", "os": ["linux"], "cpu": ["x64"] }, - "@oxfmt/binding-openharmony-arm64@0.48.0": { - "integrity": "sha512-CIg24VgheEpvolHL2gQuax5qcQ602bRMHrJ9g8XsQr3iVj9aSPgopigBKuMqrXsupwkrU+RQCn5cG8PgFntR6w==", + "@oxfmt/binding-openharmony-arm64@0.55.0": { + "integrity": "sha512-s5SDvVVSbyQl1V5UU3Yl12M+XLUQ3rl5SglNqgAA2K4PXUtQhyNSS00wivONPEnNo5W01rCou8WkDNyvI/RGHg==", "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.55.0": { + "integrity": "sha512-7p9FB5R32tw2KyyNX3wpQrR2WHwEHvMEiBlGXxeTCaRMCVNx3UtFMAUbaQ/pRNWIrEUZmYhJ6tcUH52uPTRYjQ==", "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.55.0": { + "integrity": "sha512-ZYqj3fDnOT1IaVGMP5kpmkQl4F3tQIm2ZyAxvqkJYmI0xgWWak4ss4XYwv3VDfM+TWXeC9K4uQ/wW5jm/5XABA==", "os": ["win32"], "cpu": ["ia32"] }, - "@oxfmt/binding-win32-x64-msvc@0.48.0": { - "integrity": "sha512-GSD2+7t2UoVMV2NgxXypa4bKewflPMAjYnF0Xw9/ht82ZfafAHhb8STwrEd7wlH2PFogt5zw3WVCxYJaHUdbeQ==", + "@oxfmt/binding-win32-x64-msvc@0.55.0": { + "integrity": "sha512-eEYT5tivGnGbPHuOHuQpi6CGLObhh0re/5jcNQHihD2GRYkTM85dyi5a19zjP8Q00t1uqAx+/QGLUGdHeqzWyg==", "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.69.0": { + "integrity": "sha512-DKQQbD5cZ/MYfDgDI7YGyGD9FSxABlsBsYFo5p26lloob543tP9+4N3guwdXIYJN+7HSZxLe8YJuwcOWw5qnHg==", "os": ["android"], "cpu": ["arm"] }, - "@oxlint/binding-android-arm64@1.62.0": { - "integrity": "sha512-b1AUNViByvgmR2xJDubvLIr+dSuu3uraG7bsAoKo+xrpspPvu6RIn6Fhr2JUhobfep3jwUTy18Huco6GkwdvGQ==", + "@oxlint/binding-android-arm64@1.69.0": { + "integrity": "sha512-lEhb+I5pr4inux+JFwfCa1HRq3Os7NirEFQ0H1I35SVEHPm6byX0Ah47xmRha3qi6LAkxUcxViL8o/9PivjzBg==", "os": ["android"], "cpu": ["arm64"] }, - "@oxlint/binding-darwin-arm64@1.62.0": { - "integrity": "sha512-iG+Tvf70UJ6otfwFYIHk36Sjq9cpPP5YLxkoggANNRtzgi3Tj3g8q6Ybqi6AtkU3+yg9QwF7bDCkCS6bbL4PCg==", + "@oxlint/binding-darwin-arm64@1.69.0": { + "integrity": "sha512-GY2YE8lOZW59BW1Ia1y+1gR0XyjrZRvVWHAr8LGeGhYHE0OQJ/7cRKXTkx1P+E9/6awEc3SX8a68SFTjh/E//A==", "os": ["darwin"], "cpu": ["arm64"] }, - "@oxlint/binding-darwin-x64@1.62.0": { - "integrity": "sha512-oOWI6YPPr5AJUx+yIDlxmuUbQjS5gZX3OH3QisawYvsZgLiQVvZtR0rPBcJTxLWqt2ClrWg0DlSrlUiG5SQNHg==", + "@oxlint/binding-darwin-x64@1.69.0": { + "integrity": "sha512-ax1oZnOjHX3LB7myQyHEaQkDwfLb6str3/nSP6O7EVUviQGNkEGzGV0EqcBJWK+Ufwx0l4xPgyYayurvhAdl2Q==", "os": ["darwin"], "cpu": ["x64"] }, - "@oxlint/binding-freebsd-x64@1.62.0": { - "integrity": "sha512-dLP33T7VLCmLVv4cvjkVX+rmkcwNk2UfxmsZPNur/7BQHoQR60zJ7XLiRvNUawlzn0u8ngCa3itjEG73MAMa/w==", + "@oxlint/binding-freebsd-x64@1.69.0": { + "integrity": "sha512-kHWeHv4g2h8NY+mpCxzCtY4uerMJWTN/TSnNj1CPbakFpHEJ6cTya2wWV0pDSYWOJ2+0UiEbhn3AtXxHtsnKjg==", "os": ["freebsd"], "cpu": ["x64"] }, - "@oxlint/binding-linux-arm-gnueabihf@1.62.0": { - "integrity": "sha512-fl//LWNks6qo9chNY60UDYyIwtp7a5cEx4Y/rHPjaarhuwqx6jtbzEpD5V5AqmdL4a6Y5D8zeXg5HF2Cr0QmSQ==", + "@oxlint/binding-linux-arm-gnueabihf@1.69.0": { + "integrity": "sha512-gq84vM1a1oEehXo27YCDzGVcxPsZDI1yswZwz2Da1/cbnWtrL16XZZnz0G/+gIU8edtHpfjxq5c+vWEHqJfWoQ==", "os": ["linux"], "cpu": ["arm"] }, - "@oxlint/binding-linux-arm-musleabihf@1.62.0": { - "integrity": "sha512-i5vkAuxvueTODV3J2dL61/TXewDHhMFKvtD156cIsk7GsdfiAu7zW7kY0NJXhKeFHeiMZIh7eFNjkPYH6J47HQ==", + "@oxlint/binding-linux-arm-musleabihf@1.69.0": { + "integrity": "sha512-kIqEa98JQ0VRyrcncxA417m2AzasqTlD+FyVT1AksjvjkqQcvm7pBWYvoW3/mpyOP2XYvi5nSCCTIe6De1yu5g==", "os": ["linux"], "cpu": ["arm"] }, - "@oxlint/binding-linux-arm64-gnu@1.62.0": { - "integrity": "sha512-QwN19LLuIGuOjEflSeJkZmOTfBdBMlTmW8xbMf8TZhjd//cxVNYQPq75q7oKZBJc6hRx3gY7sX0Egc8cEIFZYg==", + "@oxlint/binding-linux-arm64-gnu@1.69.0": { + "integrity": "sha512-j+xYiXozxGWx2cpjCrwwGR4awTxPFsRv3JZrv23RCogEPMc4R7UqjHW47p/RG0aRlbWiROCJ8coUfCwy0dvzHA==", "os": ["linux"], "cpu": ["arm64"] }, - "@oxlint/binding-linux-arm64-musl@1.62.0": { - "integrity": "sha512-8eCy3FCDuWUM5hWujAv6heMvfZPbcCOU3SdQUAkixZLu5bSzOkNfirJiLGoQFO943xceOKkiQRMQNzH++jM3WA==", + "@oxlint/binding-linux-arm64-musl@1.69.0": { + "integrity": "sha512-xEPpNppTfN1l/nM7gYSf9iocscu/as+p/7vxkLeLEKnYU+09Dm+5V6IhDYDh+Uz6FajEupWwCLt5SOG0y1PCKg==", "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.69.0": { + "integrity": "sha512-Ug0+eU7HJBlek+SjklYH62IlOMirEJsdxpihH0kSqX0XdrDD4NdHpQc10fK1JC35yn6KrrcN+uYzlHD38XAf8Q==", "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.69.0": { + "integrity": "sha512-iEyI3GIg0l/s3G4qy2TlaaWKdzj4PJJStwtlocpDTC00PY9hZueotf6OKUj9+yfQh0lrpBW/pLMgTztbAHKJEg==", "os": ["linux"], "cpu": ["riscv64"] }, - "@oxlint/binding-linux-riscv64-musl@1.62.0": { - "integrity": "sha512-gBjBxQ+9lGpAYq+ELqw0w8QXsBnkZclFc7GRX2r0LnEVn3ZTEqeIKpKcGjucmp76Q53bvJD0i4qBWBhcfhSfGA==", + "@oxlint/binding-linux-riscv64-musl@1.69.0": { + "integrity": "sha512-NjHjpiI4WIKSMwuoJSZi5VToPeoYOS1FR52HLIDG6lidMdqquusgtODb4iLk0+lb1q3Z0nv2/aPRcC/olmpQGg==", "os": ["linux"], "cpu": ["riscv64"] }, - "@oxlint/binding-linux-s390x-gnu@1.62.0": { - "integrity": "sha512-Ew2Kxs9EQ9/mbAIJ2hvocMC0wsOu6YKzStI2eFBDt+Td5O8seVC/oxgRIHqCcl5sf5ratA1nozQBAuv7tphkHg==", + "@oxlint/binding-linux-s390x-gnu@1.69.0": { + "integrity": "sha512-Ai/prDewoItkDXbp38gwGZi41DycZbUTZJ3UidwoHgQC0/DaqC2TGdtBTQLJ6hSD+SAxASzh8+/eSBPmxfOacA==", "os": ["linux"], "cpu": ["s390x"] }, - "@oxlint/binding-linux-x64-gnu@1.62.0": { - "integrity": "sha512-5z25jcAA0gfKyVwz71A0VXgaPlocPoTAxhlv/hgoK6tlCrfoNuw7haWbDHvGMfjXhdic4EqVXGRv5XsTqFnbRQ==", + "@oxlint/binding-linux-x64-gnu@1.69.0": { + "integrity": "sha512-Gt3KHgp46mRKz4sJeaASmKvD8ayXookRw07RMf+NowhEztGGDZ7VrXpoW96XuKJLjFukWizOFVNjmYb/u7caNQ==", "os": ["linux"], "cpu": ["x64"] }, - "@oxlint/binding-linux-x64-musl@1.62.0": { - "integrity": "sha512-IWpHmMB6ZDllPvqWDkG6AmXrN7JF5e/c4g/0PuURsmlK+vHoYZPB70rr4u1bn3I4LsKCSpqqfveyx6UCOC8wdg==", + "@oxlint/binding-linux-x64-musl@1.69.0": { + "integrity": "sha512-7tQhJ2+p/oHv1zcfnjYI7YVzC/7iBaVOfIvFYtxdJ5F45mWgEdrCyXZXZGfiLey5t/5JhOhsaMnnv1kAzckd7g==", "os": ["linux"], "cpu": ["x64"] }, - "@oxlint/binding-openharmony-arm64@1.62.0": { - "integrity": "sha512-fjlSxxrD5pA594vkyikCS9MnPRjQawW6/BLgyTYkO+73wwPlYjkcZ7LSd974l0Q2zkHQmu4DPvJFLYA7o8xrxQ==", + "@oxlint/binding-openharmony-arm64@1.69.0": { + "integrity": "sha512-vmWz6TKp/3hfA4lksR0zHBv/6xuX1jhym6eqOjdH2DXsDDHZWcp2f0KG0VCAnlVbIrjk29G4wAWMXb/Hn1YobA==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@oxlint/binding-win32-arm64-msvc@1.62.0": { - "integrity": "sha512-EiFXr8loNS0Ul3Gu80+9nr1T8jRmnKocqmHHg16tj5ZqTgUXyb97l2rrspVHdDluyFn9JfR4PoJFdNzw4paHww==", + "@oxlint/binding-win32-arm64-msvc@1.69.0": { + "integrity": "sha512-9RExaLgmaw6IoIkU9cTpT71mLfI0xZ86iZH8x518LVsOkjquJMYqb9P7KpC8lgd1t0Dxs41p2pxynq4XR3Ttzw==", "os": ["win32"], "cpu": ["arm64"] }, - "@oxlint/binding-win32-ia32-msvc@1.62.0": { - "integrity": "sha512-IgOFvL73li1bFgab+hThXYA0N2Xms2kV2MvZN95cebV+fmrZ9AVui1JSxfeeqRLo3CpPxKZlzhyq4G0cnaAvIw==", + "@oxlint/binding-win32-ia32-msvc@1.69.0": { + "integrity": "sha512-1907kRPF8/PrcIw1E7LMs9JbVrpgnt/MvFdss3an8oDkYNAACXzTntV3t3869ZZhMZxb2AzRGbz1pA/jdFatXA==", "os": ["win32"], "cpu": ["ia32"] }, - "@oxlint/binding-win32-x64-msvc@1.62.0": { - "integrity": "sha512-6hMpyDWQ2zGA1OXFKBrdYMUveUCO8UJhkO6JdwZPd78xIdHZNhjx+pib+4fC2Cljuhjyl0QwA2F3df/bs4Bp6A==", + "@oxlint/binding-win32-x64-msvc@1.69.0": { + "integrity": "sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA==", "os": ["win32"], "cpu": ["x64"] }, - "@rolldown/binding-android-arm64@1.0.0": { - "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", + "@rolldown/binding-android-arm64@1.1.2": { + "integrity": "sha512-2cZ+7xRS+DBcuJBJKnfzsbleumJhBqSlJVpuzHC0nTqfd3QQ7Vx2/x5YR/D7cBamKSeWplwo82Fn9lqYUDEMfA==", "os": ["android"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-arm64@1.0.0": { - "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", + "@rolldown/binding-darwin-arm64@1.1.2": { + "integrity": "sha512-RkPMJnygxsgOYdkfqgpwY0/Fzm8d0VQe6HGU2/B00Xa9eqdLbrII+DOKAodbJAn3ZL1AJxGHkZRPYazgGY6Ljw==", "os": ["darwin"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-x64@1.0.0": { - "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", + "@rolldown/binding-darwin-x64@1.1.2": { + "integrity": "sha512-Uiczh6vFhwyfd7WNe7Q7mCA4KxAiLdz7jPE/WGizfRpIieoyFuNVMmM8HqZ9HwudTkY6/AeMQwlNJ9NJijguWw==", "os": ["darwin"], "cpu": ["x64"] }, - "@rolldown/binding-freebsd-x64@1.0.0": { - "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", + "@rolldown/binding-freebsd-x64@1.1.2": { + "integrity": "sha512-+TpdtTRgHiJFjCVFbw311SuLk3KfytPOQQn+VlAEv+gBxYPtL7E6JS9e/tk+8CwxhIZvemJKo4rTKgfWNsKkkA==", "os": ["freebsd"], "cpu": ["x64"] }, - "@rolldown/binding-linux-arm-gnueabihf@1.0.0": { - "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", + "@rolldown/binding-linux-arm-gnueabihf@1.1.2": { + "integrity": "sha512-4lv1/tkmi7ueIVHnyreaOeUpiZP26BH9rRy6hoYfR9310A2B9nUEVRDvBx69vx64Nr3eTPPRkyciqJJs+j9Jmw==", "os": ["linux"], "cpu": ["arm"] }, - "@rolldown/binding-linux-arm64-gnu@1.0.0": { - "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", + "@rolldown/binding-linux-arm64-gnu@1.1.2": { + "integrity": "sha512-gBSUVO0eaWgw1JMjK3gB8BMlX2Mk148s2lTiVT3e9vjVxbl7UDfMWWY8CfIaaqiXuM9fVTMxIpUz6CAo/B6Vlw==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-arm64-musl@1.0.0": { - "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", + "@rolldown/binding-linux-arm64-musl@1.1.2": { + "integrity": "sha512-LjQP/iZLBu8o8PjIfk4x3At0/mT6h282pvz8Z5LAyhGbu/kDezyO7ea62rF5uoqmgnIYqbN/MqJ3Si3Aymi7xQ==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-ppc64-gnu@1.0.0": { - "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", + "@rolldown/binding-linux-ppc64-gnu@1.1.2": { + "integrity": "sha512-X/7bVLWelEsbyWDUSXt7zVsTniLLPIY2n1rH58qr78l9i7MNbbxBWD8gI2vRfBWf4NUXJCUuQnfZDsp32LqsfQ==", "os": ["linux"], "cpu": ["ppc64"] }, - "@rolldown/binding-linux-s390x-gnu@1.0.0": { - "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", + "@rolldown/binding-linux-s390x-gnu@1.1.2": { + "integrity": "sha512-gb6dYKW/1KDorGXyy48glEBJs/sxVSC5pcVrox/pFGV4mvwSFeg2sK5L2tRkVsVlh7kueqOgg4GEcuipJcGuKg==", "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.1.2": { + "integrity": "sha512-JY4w85pU3iAiJVMh5nuk4/Mh9GjMsupe8MrIN53rwxAZW64GKrWeJBuN6SxQg9QTU5uB1cxyhDzW8jqRn1EABw==", "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.1.2": { + "integrity": "sha512-xvpA7o5KCYLB0Rwscmuylb1/zHHSUx4g4xilm4prC5jP76pEUlzBmMbgpbh7bVDbId4NcfT96gN5i6mE6UDaiw==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-openharmony-arm64@1.0.0": { - "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", + "@rolldown/binding-openharmony-arm64@1.1.2": { + "integrity": "sha512-p/ts6KBLjuk49Bp21XH77poQGt02iNz7ChgHep7tudPOaLinR/De/RHdxF8w8Yj4r/bF/bqXwH6PZrB2sA+Nvw==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@rolldown/binding-wasm32-wasi@1.0.0": { - "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", + "@rolldown/binding-wasm32-wasi@1.1.2": { + "integrity": "sha512-VMu/wmrZ9hJzYlRhbw7jK5PODlugyKZ5mOdX78+lS8OvuFkWNQdz1pFLrI2p3P0pjXOmUZ7B48o5VnMH9QOGtg==", "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.1.2": { + "integrity": "sha512-xtUJqs8qEkuSviS0n1tsohaPuz3a1SPhZywOji4Oo+sgrJs8daEDMZ0QtqL0OS7dx8PoVpg2J/ZZycPY5I2+Zg==", "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.1.2": { + "integrity": "sha512-85YiLQqjUKgSO/Zjnf9e0XIn5Ymrh1fLDWBeAkZqpuBR/3R8TpfoHXuyblqyQrftSSgWO9qpcHN8mkyKsLraoA==", "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@26.0.0": { + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "dependencies": [ "undici-types" ] }, - "arkenv@0.11.0_arktype@2.2.0": { - "integrity": "sha512-XLVk3uiNRwlaqJiRWqVGz5y75P30DczxuupzVz3EEbs2j539ftwusvlK+5y/MOIerUbskSpsm6c6B61YMr/UPA==", + "arkenv@0.12.2_arktype@2.2.0": { + "integrity": "sha512-PdR+gPNc/YQKTD75qDjREhmJc9nBaY/+zR5JLYQY3uB2zIiN+kwAv+A+kpV9a4A+9ivvYJvZURNcLaoFa9FMhg==", "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.55.0": { + "integrity": "sha512-jSj2wCTakwgPMxkfiVZX0jf+nX+Nz6xlyAZjqNE0qXTFdCBPYlP6JAN+ODjmealw7DXBjOzYbdsqwBMAZnPZ6A==", "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.69.0_oxlint-tsgolint@0.23.0": { + "integrity": "sha512-ypZkK/aDc5NQV8zIR6s2H2Tl3aNW8FmJ1m9+2qsaYuRenl8vgnHNCGwTHviWJdUQzglOlHFchgopdtGhSy17Rw==", "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.1.2": { + "integrity": "sha512-x0CrQQqCXWGeI8dTvFfN/Dnv3yMKT9hv5jFjlOreKAx9wqLq9wz7VvLLHyaAXC90/CpggTu9SisSbsJJTPSjNQ==", "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@8.3.0": { + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" } }, "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:@types/node@26", + "npm:arkenv@0.12", "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.55", + "npm:oxlint-tsgolint@0.23", "npm:oxlint@^1.62.0", - "npm:rolldown@1.0.0" + "npm:rolldown@1.1.2" ] } } 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";