From 69d687f808e41fa89153e2ab268cc58f46da12c3 Mon Sep 17 00:00:00 2001 From: Janghoon Lee <44862514+its-janghoon@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:47:38 +0900 Subject: [PATCH 1/3] Switch to the shared gitflow, Windows signing and release publish (#68) Switch to the shared redrob-labs/.github gitflow and release workflows. --- .github/workflows/gitflow.yml | 113 +--- .github/workflows/release.yml | 645 ++++++++-------------- AGENTS.md | 25 +- CHANGELOG.md | 15 + CONTRIBUTING.ko.md | 10 +- CONTRIBUTING.md | 11 +- README.ko.md | 2 +- README.md | 2 +- scripts/launcher-asset-name.mjs | 8 +- scripts/launcher-release-manifest.mjs | 85 --- scripts/launcher-release-manifest.test.ts | 99 ---- scripts/prepare-release-config.mjs | 56 +- scripts/release-matrix.mjs | 18 +- scripts/rename-release-assets.mjs | 84 --- scripts/resign-linux-appimage.sh | 92 +-- scripts/rewrite-updater-urls.mjs | 81 --- scripts/stage-release-assets.mjs | 136 +++++ scripts/stage-release-assets.test.ts | 168 ++++++ scripts/write-updater-manifest.mjs | 155 ++++++ scripts/write-updater-manifest.test.ts | 178 ++++++ 20 files changed, 981 insertions(+), 1002 deletions(-) delete mode 100644 scripts/launcher-release-manifest.mjs delete mode 100644 scripts/launcher-release-manifest.test.ts delete mode 100644 scripts/rename-release-assets.mjs delete mode 100644 scripts/rewrite-updater-urls.mjs create mode 100644 scripts/stage-release-assets.mjs create mode 100644 scripts/stage-release-assets.test.ts create mode 100644 scripts/write-updater-manifest.mjs create mode 100644 scripts/write-updater-manifest.test.ts diff --git a/.github/workflows/gitflow.yml b/.github/workflows/gitflow.yml index 6330bd0..8c75c9c 100644 --- a/.github/workflows/gitflow.yml +++ b/.github/workflows/gitflow.yml @@ -1,116 +1,21 @@ -name: gitflow +# Gitflow rules shared by every redrob-labs repository: branch names are / from the +# org-wide type list, and main is contained in develop (a back-merge pull request is opened when +# it is not). The rules live in redrob-labs/.github/.github/workflows/gitflow.yml; this file only +# calls them. Do not add a type list here -- there is one, and it is there. -# Two rules this repository already had in prose, now checked. -# -# A rule nothing checks is a preference. Both of these were written down and both were broken anyway: -# branches appeared under a `kiro/` prefix that no document defines, and a hotfix landed on `main` whose -# back-merge into `develop` was never opened, leaving the DEFAULT branch unable to install for a month -# with nothing anywhere reporting it. -# -# Deliberately two jobs on two different triggers, because they answer different questions at different -# moments. A branch name can only be judged when a pull request exists. A missing back-merge can only be -# judged after `main` has moved. +name: gitflow on: pull_request: - # No branch filter. A filter that omits the default branch silently stops running, which is exactly - # what happened in a sibling repository when the default moved to `develop` and three workflows - # kept filtering on `[main, master]`. types: [opened, reopened, edited, synchronize] push: branches: [main] workflow_dispatch: permissions: - contents: read + contents: write + pull-requests: write jobs: - branch-name: - name: branch name follows the convention - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Check the head branch name - env: - HEAD_REF: ${{ github.head_ref }} - BASE_REF: ${{ github.base_ref }} - run: | - set -euo pipefail - - # ALLOWED_TYPES is this repository's own list. Keep it in step with the branch table in the - # contributing or branching document; the document is the explanation, this is the gate. - ALLOWED_TYPES='feat fix chore docs test refactor perf release hotfix' - - # `develop` and `main` reach here through a promotion or a back-merge pull request, which are - # branches in their own right and are not named after a type. - case "$HEAD_REF" in - develop|main) echo "$HEAD_REF is a long-lived branch, not a working branch"; exit 0 ;; - esac - - type="${HEAD_REF%%/*}" - slug="${HEAD_REF#*/}" - - if [ "$type" = "$HEAD_REF" ]; then - echo "::error::branch '$HEAD_REF' has no type prefix. Use /, type one of: $ALLOWED_TYPES" - exit 1 - fi - - ok=false - for t in $ALLOWED_TYPES; do - [ "$type" = "$t" ] && ok=true && break - done - - if [ "$ok" != true ]; then - { - echo "### Branch name is not one of this repository's types" - echo - echo "\`$HEAD_REF\` uses the prefix \`$type\`. Allowed: $ALLOWED_TYPES" - echo - echo "A branch name says what the change is, not who or what made it. An agent or tool name" - echo "is not a type: rename the branch and reopen the pull request." - } >> "${GITHUB_STEP_SUMMARY:-/dev/null}" - echo "::error::branch prefix '$type' is not allowed. Use one of: $ALLOWED_TYPES" - exit 1 - fi - - if [ -z "$slug" ] || [ "$slug" = "$type" ]; then - echo "::error::branch '$HEAD_REF' has an empty slug. Use /." - exit 1 - fi - - echo "$HEAD_REF is a valid $type branch targeting $BASE_REF" - - back-merged: - name: main is contained in develop - if: github.event_name != 'pull_request' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - # Full history: comparing two branches needs the commits, and a shallow clone answers with - # whatever it happens to have, which is worse than no answer. - fetch-depth: 0 - - - name: Compare main against develop - run: | - set -euo pipefail - if ! git rev-parse -q --verify origin/develop >/dev/null; then - echo "::notice::no develop branch; nothing to back-merge into" - exit 0 - fi - missing="$(git rev-list --count origin/develop..origin/main)" - if [ "$missing" -eq 0 ]; then - echo "main is contained in develop" - exit 0 - fi - { - echo "### \`main\` has $missing commit(s) \`develop\` does not" - echo - echo "Merge \`main\` back into \`develop\`. Until that lands, a branch cut from the default" - echo "branch is missing whatever these changed." - echo - git log --format='- `%h` %s' origin/develop..origin/main - } >> "${GITHUB_STEP_SUMMARY:-/dev/null}" - git log --oneline origin/develop..origin/main - echo "::error::main is $missing commit(s) ahead of develop; back-merge it" - exit 1 + gitflow: + uses: redrob-labs/.github/.github/workflows/gitflow.yml@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b364afb..df3c956 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,36 @@ name: Signed desktop release -# Only a tag builds. There used to be a `release: [published]` trigger here for the CDN -# promotion job; with GitHub Releases as the channel itself, publishing the draft IS the -# promotion, so a second workflow run on that event would have nothing to do. +# The redrob-labs release shape (redrob-labs/.github workflow-templates/release.yml): +# +# tag v on main -> version -> build matrix (build, then sign) -> updater manifest -> publish +# +# The parts every repository shares are not in this file; they are called: +# - redrob-labs/.github/actions/sign-windows/sign.ps1 Authenticode through Azure Artifact Signing, +# run by tauri as `bundle.windows.signCommand` +# - redrob-labs/.github/.github/workflows/release-publish.yml checksums, launcher manifest, the release +# +# Rules the shape encodes: +# - the trigger is the tag push; `workflow_dispatch` with a version is a DRY RUN -- it builds, signs and +# verifies every leg and publishes nothing; +# - Windows: compile, THEN `azure/login` (OIDC), THEN bundle -- tauri signs the executable and both +# installers during bundling, through the shared script. The login token is short-lived and the compile +# is long; never the other way round, never a client secret or a certificate file; +# - the jobs that sign run in the `release` environment with `id-token: write`; +# - the publish job runs only on a tag and only when every build job succeeded, and publishes the +# release as latest (as a prerelease when the version carries a `-`). +# +# Everything a leg uploads is a `release-*` artifact under its FINAL name; the publish job ships those +# bytes unchanged. latest.json, the Tauri updater feed, is one of them (the updater-manifest job). + on: push: tags: ["v*.*.*"] + workflow_dispatch: + inputs: + version: + description: Version for a dry run, e.g. 0.3.0-1 (builds and signs every leg, publishes nothing) + type: string + required: true permissions: contents: read @@ -14,14 +39,18 @@ concurrency: group: release-${{ github.ref }} cancel-in-progress: false +env: + PRODUCT: Redrob Query + PRODUCT_URL: https://github.com/${{ github.repository }} + jobs: - validate: - name: Validate release tag - if: github.event_name == 'push' + version: + name: Resolve the version and verify the tree runs-on: ubuntu-24.04 timeout-minutes: 45 outputs: - # The build matrix and the updater platform keys the draft must carry, both derived from + version: ${{ steps.v.outputs.version }} + # The build matrix and the updater platform keys the manifest must carry, both derived from # the single list in scripts/release-matrix.mjs. matrix: ${{ steps.release-matrix.outputs.matrix }} platforms: ${{ steps.release-matrix.outputs.platforms }} @@ -32,7 +61,45 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + + # A tag names the version the sources declare, or it is refused: the installers, the updater + # payloads and latest.json all carry package.json's version, and a tag that disagrees would + # publish v0.3.0 whose every file says 0.2.4. A dispatch names any well-formed version for the + # dry run, and scripts/prepare-release-config.mjs stamps it onto the build. + - name: Resolve the version + id: v + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + declared="$(node -p 'require("./package.json").version')" + if [ "${GITHUB_REF_TYPE:-}" = tag ]; then + v="${GITHUB_REF_NAME#v}" + if [ "$v" != "$declared" ]; then + echo "::error::tag ${GITHUB_REF_NAME} does not match the package.json version ${declared}" + exit 1 + fi + elif [ -n "$INPUT_VERSION" ]; then + v="${INPUT_VERSION#v}" + else + echo "::error::not a tag push and no version input" + exit 1 + fi + # tauri's MSI bundler maps a pre-release identifier onto the fourth Windows version + # field, so it must be a plain number no greater than 65535 (`0.3.0-1`, not `0.3.0-rc.1`); + # the bundler would refuse `rc.1` two jobs and forty minutes later. + if ! printf '%s' "$v" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]{0,4}))?$'; then + echo "::error::'$v' is not a version like 1.2.3 or 1.2.3-1 (a pre-release identifier must be numeric for the MSI bundler)" + exit 1 + fi + echo "version=$v" >> "$GITHUB_OUTPUT" + { + echo "### ${PRODUCT} v$v (\`${GITHUB_SHA::9}\`)" + [ "${GITHUB_REF_TYPE:-}" = tag ] || echo "Dry run: nothing is published." + } >> "$GITHUB_STEP_SUMMARY" + - name: Require release tag commit on main + if: github.ref_type == 'tag' shell: bash run: | git fetch --no-tags origin main:refs/remotes/origin/main @@ -70,7 +137,6 @@ jobs: rust-advisories: name: Rust security advisories - if: github.event_name == 'push' runs-on: ubuntu-24.04 timeout-minutes: 15 steps: @@ -88,27 +154,45 @@ jobs: build: name: Build ${{ matrix.label }} - if: github.event_name == 'push' - needs: [validate, rust-advisories] + needs: [version, rust-advisories] + # `release` is the environment the organization's federated credential names + # (repo:redrob-labs/:environment:release); the macOS and Linux legs run in it too so one + # set of protection rules covers every leg that produces a release file. + environment: release permissions: - contents: write - environment: production-release + contents: read + # The OIDC token `azure/login` exchanges for the Artifact Signing credential (Windows leg). + id-token: write strategy: fail-fast: false max-parallel: 1 matrix: - # Generated by scripts/release-matrix.mjs in the validate job, so the Windows leg can - # be excluded while Authenticode signing has no certificate, and so the updater - # platform keys the verify job requires come from the same single list. - include: ${{ fromJSON(needs.validate.outputs.matrix) }} + # Generated by scripts/release-matrix.mjs in the version job, so the Windows leg can be + # excluded while Authenticode signing is not configured, and so the updater platform + # keys the manifest job requires come from the same single list. + include: ${{ fromJSON(needs.version.outputs.matrix) }} runs-on: ${{ matrix.platform }} timeout-minutes: 90 + env: + RELEASE_VERSION: ${{ needs.version.outputs.version }} + RELEASE_TARGET: ${{ matrix.target }} steps: - name: Check out source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + # The one copy of the Windows signing recipe. tauri runs it as `bundle.windows.signCommand` + # (scripts/prepare-release-config.mjs points the bundler at it), so this repository carries + # no signing code of its own. + - name: Check out the shared signing recipe + if: runner.os == 'Windows' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: redrob-labs/.github + ref: main + path: .redrob-shared + - name: Install Linux desktop dependencies if: runner.os == 'Linux' # desktop-file-utils: the AppImage repack step below runs appimagetool, which refuses to @@ -187,165 +271,73 @@ jobs: test -n "$CERT_ID" printf 'APPLE_SIGNING_IDENTITY=%s\n' "$CERT_ID" >> "$GITHUB_ENV" - - name: Import Windows code-signing certificate - if: runner.os == 'Windows' - shell: pwsh - env: - WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} - WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }} - run: | - $ErrorActionPreference = "Stop" - if (-not $env:WIN_CSC_LINK -or -not $env:WIN_CSC_KEY_PASSWORD) { - throw "Windows signing secrets are required" - } - - $certificatePath = Join-Path $env:RUNNER_TEMP "redrob-certificate.pfx" - $link = $env:WIN_CSC_LINK.Trim() - if ($link -match '^https://') { - try { - Invoke-WebRequest -Uri $link -OutFile $certificatePath -UseBasicParsing - } catch { - throw "Failed to download the Windows signing certificate over HTTPS" - } - } else { - if ($link -match '^data:[^,]*;base64,') { - $payload = $link.Substring($link.IndexOf(',') + 1) - } else { - $payload = $link - } - $payload = $payload -replace '\s', '' - if ($payload -notmatch '^[A-Za-z0-9+/]+={0,2}$') { - throw "WIN_CSC_LINK must be raw Base64 PFX data, a data:*;base64 URI, or an HTTPS URL" - } - try { - [IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($payload)) - } catch { - throw "WIN_CSC_LINK contains invalid Base64 certificate data" - } - } - - $password = ConvertTo-SecureString $env:WIN_CSC_KEY_PASSWORD -AsPlainText -Force - $imported = @(Import-PfxCertificate -FilePath $certificatePath -CertStoreLocation Cert:\CurrentUser\My -Password $password) - $codeSigningOid = "1.3.6.1.5.5.7.3.3" - # Read the EKU out of the X.509 extension itself, not out of PowerShell's - # EnhancedKeyUsageList. That list's ObjectId is already a STRING, so the - # `.ObjectId.Value` this used to compare is always $null and the `-contains` never - # matched -- the filter rejected the organisation's certificate, which does carry - # Code Signing. The diagnostic printed `eku=Code Signing ()` and "EKU is not Code - # Signing" in the same breath, which is the shape of a check reading the wrong - # property rather than a certificate that is wrong. - function Get-EkuOid([Security.Cryptography.X509Certificates.X509Certificate2]$candidate) { - $oids = @() - foreach ($extension in $candidate.Extensions) { - if ($extension.Oid.Value -ne "2.5.29.37") { continue } - $eku = $extension -as [Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension] - if ($null -eq $eku) { continue } - foreach ($usage in $eku.EnhancedKeyUsages) { - $oids += if ($usage.FriendlyName) { "$($usage.FriendlyName) ($($usage.Value))" } else { $usage.Value } - } - } - return $oids - } - $certificate = $imported | Where-Object { - $_.HasPrivateKey -and - $_.NotBefore.ToUniversalTime() -le [DateTime]::UtcNow -and - $_.NotAfter.ToUniversalTime() -gt [DateTime]::UtcNow -and - ((Get-EkuOid $_) -match [regex]::Escape($codeSigningOid)) - } | Sort-Object NotAfter -Descending | Select-Object -First 1 - if (-not $certificate -or -not $certificate.Thumbprint) { - # One message for four independent conditions is not diagnosable: an expired - # certificate, a certificate with no private key, and one issued for the wrong - # purpose all read the same. Report what each imported certificate actually is - # so the operator knows whether to renew it or replace it. Only public metadata - # is printed -- never the key, the password, or the PFX bytes. - Write-Host "Imported $($imported.Count) certificate(s); none usable for code signing:" - foreach ($candidate in $imported) { - $ekus = @(Get-EkuOid $candidate) - $reasons = @() - if (-not $candidate.HasPrivateKey) { $reasons += "no private key" } - if ($candidate.NotBefore.ToUniversalTime() -gt [DateTime]::UtcNow) { $reasons += "not valid until $($candidate.NotBefore.ToUniversalTime().ToString('u'))" } - if ($candidate.NotAfter.ToUniversalTime() -le [DateTime]::UtcNow) { $reasons += "EXPIRED $($candidate.NotAfter.ToUniversalTime().ToString('u'))" } - if ($ekus.Count -eq 0) { $reasons += "no Enhanced Key Usage at all" } - elseif ($ekus -notmatch [regex]::Escape($codeSigningOid)) { $reasons += "EKU is not Code Signing" } - Write-Host " subject=$($candidate.Subject)" - Write-Host " validity=$($candidate.NotBefore.ToUniversalTime().ToString('u')) .. $($candidate.NotAfter.ToUniversalTime().ToString('u'))" - Write-Host " hasPrivateKey=$($candidate.HasPrivateKey) eku=$(if ($ekus.Count) { $ekus -join ', ' } else { '(none)' })" - Write-Host " unusable because: $($reasons -join '; ')" - } - throw "WIN_CSC_LINK holds no certificate usable for Authenticode signing. See the per-certificate reasons above; the secret needs a PFX with a private key, an unexpired validity window, and the Code Signing EKU ($codeSigningOid)." - } - # The organisation's code-signing certificate is SELF-SIGNED. Measured, not assumed: - # the chain terminates in "a root certificate which is not trusted by the trust - # provider", and a sibling product that signs with this same secret says so in its - # own workflow and accepts NotTrusted there. - # - # So an untrusted or incomplete root is expected and is NOT a defect to fail on. - # Everything else still is -- a revoked certificate, one outside its validity window, - # a broken signature, a bad usage. Those are listed explicitly rather than the check - # being deleted, because deleting it would also stop catching a revoked certificate. - # - # WHAT THIS BUYS AND WHAT IT DOES NOT. It buys tamper-evidence and a stable publisher - # identity: the installer cannot be modified without breaking the signature. It does - # NOT buy trust -- Windows SmartScreen still warns the user that the publisher is - # unknown, exactly as it would for an unsigned installer. Removing that warning needs - # an OV or EV certificate from a CA whose root ships in Windows; no change to this - # workflow can substitute for one. - $chain = [Security.Cryptography.X509Certificates.X509Chain]::new() - $chain.ChainPolicy.RevocationMode = [Security.Cryptography.X509Certificates.X509RevocationMode]::Online - $chain.ChainPolicy.RevocationFlag = [Security.Cryptography.X509Certificates.X509RevocationFlag]::ExcludeRoot - $built = $chain.Build($certificate) - $tolerated = @("UntrustedRoot", "PartialChain", "RevocationStatusUnknown", "OfflineRevocation") - $fatal = @($chain.ChainStatus | Where-Object { $tolerated -notcontains $_.Status.ToString() }) - if ($fatal.Count -gt 0) { - $chainErrors = ($fatal | ForEach-Object { "$($_.Status): $($_.StatusInformation.Trim())" }) -join "; " - throw "Windows code-signing certificate chain validation failed: $chainErrors" - } - if (-not $built) { - Write-Host "::warning::certificate chain is not publicly trusted ($(($chain.ChainStatus | ForEach-Object { $_.Status.ToString() }) -join ', ')). The installer is signed and tamper-evident, but Windows will still warn that the publisher is unknown until an OV/EV certificate replaces this one." - } - "WIN_CSC_CERTIFICATE_THUMBPRINT=$($certificate.Thumbprint)" | Out-File -FilePath $env:GITHUB_ENV -Append - Remove-Item -LiteralPath $certificatePath -Force - + # Writes src-tauri/tauri.release.conf.json: the updater key and feed, the version this run + # builds, and on Windows the shared sign.ps1 as the bundler's sign command. - name: Prepare signed updater configuration env: TAURI_UPDATER_PUBLIC_KEY: ${{ vars.TAURI_SIGNING_PUBLIC_KEY }} - WIN_CSC_CERTIFICATE_THUMBPRINT: ${{ env.WIN_CSC_CERTIFICATE_THUMBPRINT }} + REDROB_SHARED_DIR: ${{ github.workspace }}/.redrob-shared run: npm run release:prepare - - name: Build, sign, and upload installers - uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # action-v1.0.0 + # Windows signs mid-bundling, so the login has to sit between the compile and the bundle: + # compile everything first (no installer yet), log in, then bundle. The second `tauri build` + # finds cargo's output up to date and goes straight to packaging and signing. + - name: Compile before signing (Windows) + if: runner.os == 'Windows' + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: npm run tauri build -- --target ${{ matrix.target }} --no-bundle --config src-tauri/tauri.release.conf.json + + - name: Log in to Azure (OIDC) + if: runner.os == 'Windows' + uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + # On Windows, tauri calls the shared sign.ps1 on the executable, the NSIS installer and the + # MSI as it packages them, with ARTIFACT_SIGNING_* from this environment and the login above. + # On macOS it signs with the imported Developer ID and notarizes with the API key. + - name: Build, bundle and sign env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} APPLE_API_KEY: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} APPLE_API_ISSUER: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }} APPLE_API_KEY_PATH: ${{ env.APPLE_API_KEY_PATH }} - with: - tagName: v__VERSION__ - releaseName: Redrob Query v__VERSION__ - releaseBody: See CHANGELOG.md for release notes. Installers and updater bundles are signed; the release remains a draft until platform smoke tests are approved. - releaseDraft: true - prerelease: false - uploadUpdaterJson: true - updaterJsonPreferNsis: true - args: --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config src-tauri/tauri.release.conf.json + ARTIFACT_SIGNING_ENDPOINT: ${{ runner.os == 'Windows' && secrets.ARTIFACT_SIGNING_ENDPOINT || '' }} + ARTIFACT_SIGNING_ACCOUNT: ${{ runner.os == 'Windows' && secrets.ARTIFACT_SIGNING_ACCOUNT || '' }} + ARTIFACT_SIGNING_CERT_PROFILE: ${{ runner.os == 'Windows' && secrets.ARTIFACT_SIGNING_CERT_PROFILE || '' }} + SIGN_DESCRIPTION: ${{ env.PRODUCT }} + SIGN_URL: ${{ env.PRODUCT_URL }} + run: npm run tauri build -- --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config src-tauri/tauri.release.conf.json + + - name: Locate the bundle directory + id: bundle + shell: bash + run: | + set -euo pipefail + # This is a Cargo workspace rooted at the repo, so the bundle is NOT under src-tauri/; + # ask cargo instead of hardcoding either layout. + target_root="$(cargo metadata --format-version 1 --no-deps | jq -r .target_directory)" + echo "dir=${target_root}/${RELEASE_TARGET}/release/bundle" >> "$GITHUB_OUTPUT" # The default bundler ships the build host's libwayland in the AppImage, and on Mesa 25+ that # makes WebKitWebProcess abort with EGL_BAD_PARAMETER: the window opens and stays blank # (tauri-apps/tauri#15665). Measured on v0.2.0 before this step existed -- strace on the web # process, then the same AppImage with only those four libraries removed rendered with no - # environment overrides. Repacking changes the bytes, so the script also re-signs and patches - # latest.json; a stale signature still looks signed and breaks every future update. + # environment overrides. Repacking changes the bytes, so the script also re-signs; the manifest + # job picks the new .sig up from beside the staged file and verifies it against the bytes. - name: Repack the AppImage without bundled Wayland, and re-sign it if: runner.os == 'Linux' shell: bash env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - RELEASE_TARGET: ${{ matrix.target }} + BUNDLE_DIR: ${{ steps.bundle.outputs.dir }} # Pinned by digest: this binary rewrites a signed release artifact. APPIMAGETOOL_URL: https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage APPIMAGETOOL_SHA256: 46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1 @@ -355,301 +347,110 @@ jobs: curl -fsSL -o "$tool" "$APPIMAGETOOL_URL" echo "${APPIMAGETOOL_SHA256} ${tool}" | sha256sum -c - chmod +x "$tool" - # This is a Cargo workspace rooted at the repo, so the bundle is NOT under src-tauri/; - # ask cargo, the same way the Windows signature step does. - target_root="$(cargo metadata --format-version 1 --no-deps | jq -r .target_directory)" # Runners have no FUSE; both the tool and the AppImage run through their own extractor. export APPIMAGE_EXTRACT_AND_RUN=1 - bash scripts/resign-linux-appimage.sh \ - "${target_root}/${RELEASE_TARGET}/release/bundle" "${GITHUB_REF_NAME}" "$tool" + bash scripts/resign-linux-appimage.sh "$BUNDLE_DIR" "$tool" - - name: Verify Windows installer signatures and timestamps + # sign.ps1 already verified each file it signed (`signtool verify /pa`, Status Valid, signer + # CN). This asserts the OUTCOME over the installers that leave the runner: both are present + # and each carries a Valid, timestamped Authenticode signature by the organisation's + # certificate -- so a bundler that quietly skipped the sign command cannot ship unsigned. + - name: Verify Windows installer signatures if: runner.os == 'Windows' shell: pwsh env: - RELEASE_TARGET: ${{ matrix.target }} - WIN_CSC_CERTIFICATE_THUMBPRINT: ${{ env.WIN_CSC_CERTIFICATE_THUMBPRINT }} + BUNDLE_DIR: ${{ steps.bundle.outputs.dir }} + EXPECTED_CN: McKinley Rice, Inc. run: | $ErrorActionPreference = "Stop" - # Ask cargo where its target directory is. This step used to hardcode - # "src-tauri/target/...", which is where a Tauri app's build output lives when src-tauri - # holds its own crate -- but this repository's Cargo WORKSPACE root is the repository - # root, with src-tauri as a member, so the bundles land in /target. The step is - # Windows-only, so the wrong path was never exercised until the first Windows leg, which - # failed with "Cannot find path ... src-tauri\target\...\bundle because it does not - # exist" while the bundles sat in \target\...\bundle\{msi,nsis}. - # - # Derived rather than corrected in place: moving src-tauri in or out of the workspace - # would silently break a second hardcoded path the same way. - $targetRoot = (cargo metadata --format-version 1 --no-deps | ConvertFrom-Json).target_directory - if (-not $targetRoot) { throw "cargo metadata did not report a target_directory" } - $bundlePath = Join-Path $targetRoot "$env:RELEASE_TARGET/release/bundle" - if (-not (Test-Path -LiteralPath $bundlePath)) { - throw "No bundle directory at $bundlePath (cargo target_directory: $targetRoot)" - } - $installers = @(Get-ChildItem -LiteralPath $bundlePath -Recurse -File | Where-Object { $_.Extension -in '.exe', '.msi' }) + if (-not (Test-Path -LiteralPath $env:BUNDLE_DIR)) { throw "No bundle directory at $env:BUNDLE_DIR" } + $installers = @(Get-ChildItem -LiteralPath $env:BUNDLE_DIR -Recurse -File | Where-Object { $_.Extension -in '.exe', '.msi' }) if (-not ($installers.Extension -contains '.exe') -or -not ($installers.Extension -contains '.msi')) { throw "Expected both signed NSIS and MSI installers" } - # A self-signed signer makes Get-AuthenticodeSignature report NotTrusted or - # UnknownError rather than Valid, on a runner that does not carry our root. Those two - # are accepted; HashMismatch and NotSigned are not, so an unsigned or modified - # installer still fails here. The signer thumbprint and the countersignature are - # asserted unconditionally -- those say it is OUR certificate and that the signing - # time is attested, neither of which depends on the root being trusted. - $accepted = @("Valid", "NotTrusted", "UnknownError") foreach ($installer in $installers) { $signature = Get-AuthenticodeSignature -LiteralPath $installer.FullName - if ($accepted -notcontains $signature.Status.ToString()) { - throw "Installer signature is not usable for $($installer.Name): $($signature.Status) ($($signature.StatusMessage))" + if ($signature.Status -ne 'Valid') { + throw "$($installer.Name) signature status $($signature.Status): $($signature.StatusMessage)" } - if (-not $signature.SignerCertificate) { - throw "Installer carries no Authenticode signer certificate: $($installer.Name)" - } - if ($signature.SignerCertificate.Thumbprint -ne $env:WIN_CSC_CERTIFICATE_THUMBPRINT) { - throw "Installer was signed by an unexpected certificate: $($installer.Name)" + $cn = $signature.SignerCertificate.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) + if ($cn -ne $env:EXPECTED_CN) { + throw "$($installer.Name) signed by CN=$cn, expected CN=$($env:EXPECTED_CN)" } if (-not $signature.TimeStamperCertificate) { - throw "Installer signature carries no trusted timestamp: $($installer.Name)" + throw "$($installer.Name) signature carries no timestamp" } - Write-Host "$($installer.Name) signed by $($signature.SignerCertificate.Subject) status=$($signature.Status)" + Write-Host "$($installer.Name) signed by CN=$cn status=$($signature.Status)" } + # Installers and their .sig files take the names Redrob Launcher installs + # (`redrob-query---.`); the macOS updater payload gets the version + # and architecture tauri leaves out of its name. + # Decided here, before upload, so latest.json can name every asset before the release exists. + - name: Stage the release files under their published names + shell: bash + env: + BUNDLE_DIR: ${{ steps.bundle.outputs.dir }} + run: node scripts/stage-release-assets.mjs "$BUNDLE_DIR" "$RUNNER_TEMP/release-assets" "$RELEASE_VERSION" "$RELEASE_TARGET" - # The CDN promotion job that used to live here is gone: GitHub Releases is now the - # update channel itself, so there is nothing to copy anywhere. What that job also - # did, though, was VERIFY the published channel -- so its checks are re-pointed - # here rather than dropped. Without them the classic failure is silent: the app - # installs fine and never updates again, because `latest.json` is missing or its - # platform URLs point somewhere a client cannot reach. - verify-release-assets: - name: Updater contract on the draft release - if: github.event_name == 'push' - needs: [validate, build] + - name: Upload the release files + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: release-${{ matrix.target }} + path: ${{ runner.temp }}/release-assets/* + if-no-files-found: error + + # latest.json IS the feed on GitHub Releases. Missing it does not fail an install, it silently + # freezes every existing client on its current version; a signature over the wrong bytes passes + # "has a signature" and fails on every user's machine at the first update check. So the manifest + # is written from the staged files, checked against the platforms the matrix built, and every + # signature is verified against the file it names -- on a dry run as well as on a tag. + updater-manifest: + name: Updater manifest + needs: [version, build] runs-on: ubuntu-24.04 timeout-minutes: 15 - permissions: - # write, not read: this job does not merely inspect the manifest, it corrects the - # placeholder URLs tauri-action necessarily wrote while the release was a draft. - contents: write steps: - - name: Check out the tag + - name: Check out source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - # Redrob Launcher reads one small file beside the installers, `redrob-launcher.json`, to - # learn that this release reads its shared account file -- so signing in to the launcher is - # all a user needs. Written by scripts/launcher-release-manifest.mjs (pinned by its test) and - # uploaded here, in the one job that runs after every platform leg, so it is attached exactly - # once. `--clobber` keeps a re-run from failing on the asset it uploaded last time. The rename - # step that follows fails the release if the file is missing. - - name: Attach the Redrob Launcher release manifest - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ github.ref_name }} - shell: bash - run: | - set -euo pipefail - node scripts/launcher-release-manifest.mjs "$RUNNER_TEMP/release-assets/redrob-launcher.json" - gh release upload "${RELEASE_TAG}" "$RUNNER_TEMP/release-assets/redrob-launcher.json" \ - --clobber --repo "${GITHUB_REPOSITORY}" - - # Tauri names its bundles `Redrob Query__.`; Redrob Launcher installs - # only `redrob-query---.`. The installers (and their detached .sig - # files) are renamed in place by asset id, so bytes and signatures are untouched, and the - # manifest rewrite below resolves ids to the NEW names. This runs first for that reason. - # The step also fails the release if any installer is left with a name the launcher cannot - # read -- see scripts/launcher-asset-name.mjs, pinned by scripts/launcher-asset-name.test.ts. - - name: Rename installers to the names Redrob Launcher installs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ github.ref_name }} - shell: bash - run: | - set -euo pipefail - gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" > "$RUNNER_TEMP/releases.json" - release_json="$(jq --arg tag "${RELEASE_TAG}" 'map(select(.tag_name == $tag)) | first // empty' "$RUNNER_TEMP/releases.json")" - if [ -z "$release_json" ]; then - echo "::error::No release found for ${RELEASE_TAG}. A draft is only listed to a caller with push access, so check this job's contents permission if the draft does exist." - exit 1 - fi - printf '%s' "$release_json" > "$RUNNER_TEMP/release.before-rename.json" - node scripts/rename-release-assets.mjs "$RUNNER_TEMP/release.before-rename.json" \ - "${GITHUB_REPOSITORY}" "${RELEASE_TAG}" - - # tauri-action uploads latest.json while the release is a DRAFT, and a draft asset has - # no public download URL yet -- GitHub only mints /releases/download// at - # publication -- so the manifest carries REST asset URLs - # (https://api.github.com/repos/.../releases/assets/) instead. Those would break the - # updater: that endpoint serves the asset's JSON metadata unless the caller sends - # `Accept: application/octet-stream`, which a plain updater fetch does not. The published - # URL is fully determined by the tag and the asset name, so it is written now and is - # correct the moment the draft is published. - - name: Point the updater manifest at the published download URLs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ github.ref_name }} - shell: bash - run: | - set -euo pipefail - # A DRAFT release is NOT on the tags endpoint: GET /releases/tags/ answers 404 - # until publication, because a draft has no published tag to resolve. Since this job - # exists to inspect the draft, it has to find it in the listing, which does include - # drafts for a caller with push access. - gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" > "$RUNNER_TEMP/releases.json" - release_json="$(jq --arg tag "${RELEASE_TAG}" 'map(select(.tag_name == $tag)) | first // empty' "$RUNNER_TEMP/releases.json")" - if [ -z "$release_json" ]; then - echo "::error::No release found for ${RELEASE_TAG}. A draft is only listed to a caller with push access, so check this job's contents permission if the draft does exist." - exit 1 - fi - printf '%s' "$release_json" > release.json - manifest_id="$(printf '%s' "$release_json" | jq -r '.assets[] | select(.name == "latest.json") | .id')" - if [ -z "$manifest_id" ] || [ "$manifest_id" = "null" ]; then - echo "::error::latest.json is not attached to ${RELEASE_TAG}; nothing to rewrite" - exit 1 - fi - gh api -H "Accept: application/octet-stream" \ - "repos/${GITHUB_REPOSITORY}/releases/assets/${manifest_id}" > latest.json - node scripts/rewrite-updater-urls.mjs latest.json release.json \ - "${GITHUB_REPOSITORY}" "${RELEASE_TAG}" - # Replace in place: an asset name is unique per release, so the old one must go - # before the corrected manifest can take its name. - gh api -X DELETE "repos/${GITHUB_REPOSITORY}/releases/assets/${manifest_id}" - gh release upload "${RELEASE_TAG}" latest.json --repo "${GITHUB_REPOSITORY}" - - - name: Assert the draft release carries a usable updater manifest - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ github.ref_name }} - REQUIRED_PLATFORMS: ${{ needs.validate.outputs.platforms }} - REQUIRED_ASSET_SUFFIXES: ${{ needs.validate.outputs.asset_suffixes }} - shell: bash - run: | - set -euo pipefail - - # The release is still a draft at this point, which is deliberate: draft - # assets are invisible to `releases/latest/download/...`, so QA happens - # before any installed client can see the version. - # A DRAFT release is NOT on the tags endpoint: GET /releases/tags/ answers 404 - # until publication, because a draft has no published tag to resolve. Since this job - # exists to inspect the draft, it has to find it in the listing, which does include - # drafts for a caller with push access. - gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" > "$RUNNER_TEMP/releases.json" - release_json="$(jq --arg tag "${RELEASE_TAG}" 'map(select(.tag_name == $tag)) | first // empty' "$RUNNER_TEMP/releases.json")" - if [ -z "$release_json" ]; then - echo "::error::No release found for ${RELEASE_TAG}. A draft is only listed to a caller with push access, so check this job's contents permission if the draft does exist." - exit 1 - fi - draft="$(printf '%s' "$release_json" | jq -r '.draft')" - if [ "$draft" != "true" ]; then - echo "::error::Release ${RELEASE_TAG} must still be a draft when the build finishes" - exit 1 - fi - - names="$(printf '%s' "$release_json" | jq -r '.assets[].name')" - echo "Assets on ${RELEASE_TAG}:" - printf ' %s\n' $names - - # latest.json IS the feed on GitHub Releases. Missing it does not fail an - # install, it silently freezes every existing client on its current version. - if ! printf '%s\n' $names | grep -qx 'latest.json'; then - echo "::error::latest.json is not attached; published clients would never see this release" - exit 1 - fi - - # Suffixes come from the same generator that built the matrix, so a bundle type no - # leg produces is not demanded. Glob matching rather than a regex keeps the list - # free of escaping, since it crosses a job output boundary. - for suffix in ${REQUIRED_ASSET_SUFFIXES}; do - found=0 - for name in $names; do - case "$name" in - *"$suffix") found=1; break ;; - esac - done - if [ "$found" -ne 1 ]; then - echo "::error::no asset ending in ${suffix} -- a built platform produced no installer" - exit 1 - fi - done - - manifest_id="$(printf '%s' "$release_json" | jq -r '.assets[] | select(.name == "latest.json") | .id')" - gh api -H 'Accept: application/octet-stream' \ - "repos/${GITHUB_REPOSITORY}/releases/assets/${manifest_id}" > latest.json - - version="$(jq -r '.version' latest.json)" - if [ "v${version}" != "${RELEASE_TAG}" ]; then - echo "::error::latest.json version ${version} does not match tag ${RELEASE_TAG}" - exit 1 - fi - - # Every architecture a build leg produced must be present, and every URL must - # point at this repository's own release assets. A URL left pointing at a - # decommissioned host is exactly the shape of failure this job exists to catch. - # - # The list comes from the same generator that built the matrix, so a platform that - # was deliberately not built is not demanded here, and a platform that WAS built - # cannot be quietly missing from the manifest. - for platform in ${REQUIRED_PLATFORMS}; do - url="$(jq -r --arg p "$platform" '.platforms[$p].url // empty' latest.json)" - sig="$(jq -r --arg p "$platform" '.platforms[$p].signature // empty' latest.json)" - if [ -z "$url" ] || [ -z "$sig" ]; then - echo "::error::latest.json is missing a signed entry for ${platform}" - exit 1 - fi - case "$url" in - "https://github.com/${GITHUB_REPOSITORY}/releases/download/"*) ;; - *) - echo "::error::${platform} url is not a release asset of this repository: ${url}" - exit 1 - ;; - esac - done - - echo "latest.json covers every built platform (${REQUIRED_PLATFORMS}), is signed, and points only at this repository's release assets." - - # "Has a signature" is not "has the right signature". A signature over different bytes passes - # the check above and fails on every user's machine at the first update check -- and the - # AppImage repack step rewrites bytes after tauri-action signed them, so this is a live risk, - # not a theoretical one. Measured before adding this: the published v0.2.0 signature is VALID - # for the published AppImage and INVALID for the same file repacked. - - name: Verify every updater signature against the bytes it covers + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22 + - name: Download every leg's release files + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: release-* + path: release-assets + merge-multiple: true + - name: Write latest.json and verify every updater signature against its bytes env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ github.ref_name }} + RELEASE_VERSION: ${{ needs.version.outputs.version }} TAURI_UPDATER_PUBLIC_KEY: ${{ vars.TAURI_SIGNING_PUBLIC_KEY }} - shell: bash + REQUIRED_PLATFORMS: ${{ needs.version.outputs.platforms }} + REQUIRED_ASSET_SUFFIXES: ${{ needs.version.outputs.asset_suffixes }} run: | set -euo pipefail - test -n "$TAURI_UPDATER_PUBLIC_KEY" - gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" > "$RUNNER_TEMP/releases.json" - release_json="$(jq --arg tag "${RELEASE_TAG}" 'map(select(.tag_name == $tag)) | first // empty' "$RUNNER_TEMP/releases.json")" - manifest_id="$(printf '%s' "$release_json" | jq -r '.assets[] | select(.name == "latest.json") | .id')" - gh api -H 'Accept: application/octet-stream' \ - "repos/${GITHUB_REPOSITORY}/releases/assets/${manifest_id}" > "$RUNNER_TEMP/latest.json" - - dl="$RUNNER_TEMP/updater-assets" - mkdir -p "$dl" - checked=0 - # One line per platform entry: " ". Signatures are base64, - # so they never contain a space. - while read -r platform asset signature; do - asset_id="$(printf '%s' "$release_json" | jq -r --arg n "$asset" '.assets[] | select(.name == $n) | .id')" - if [ -z "$asset_id" ]; then - echo "::error::${platform} names ${asset}, which is not attached to ${RELEASE_TAG}" - exit 1 - fi - [ -f "$dl/$asset" ] || gh api -H 'Accept: application/octet-stream' \ - "repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}" > "$dl/$asset" - if ! result="$(node scripts/verify-updater-signature.mjs "$TAURI_UPDATER_PUBLIC_KEY" "$signature" "$dl/$asset")"; then - echo "::error::${platform}: ${result} (${asset})" - exit 1 - fi - echo "${platform}: ${result} (${asset})" - checked=$((checked + 1)) - done < <(jq -r '.platforms | to_entries[] | "\(.key) \(.value.url | split("/") | last) \(.value.signature)"' "$RUNNER_TEMP/latest.json") - - if [ "$checked" -eq 0 ]; then - echo "::error::latest.json has no platform entries; nothing was verified" - exit 1 - fi - echo "Every one of ${checked} updater signatures verifies against the bytes it names." + echo "Staged release files:" + ls -1 release-assets | sed 's/^/ /' + mkdir -p manifest + node scripts/write-updater-manifest.mjs release-assets "$RELEASE_VERSION" "$GITHUB_REPOSITORY" manifest/latest.json + cat manifest/latest.json + - name: Upload latest.json + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: release-updater-manifest + path: manifest/latest.json + if-no-files-found: error + + publish: + needs: [version, rust-advisories, build, updater-manifest] + if: always() && github.ref_type == 'tag' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'skipped') + uses: redrob-labs/.github/.github/workflows/release-publish.yml@main + permissions: + contents: write + with: + version: ${{ needs.version.outputs.version }} + title: Redrob Query + launcher_manifest: true + publish: true diff --git a/AGENTS.md b/AGENTS.md index c9578d8..b9592f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ Java code here. Keep it that way: user-visible strings name Redrob Query. | `crates/redrob-core/` | The Rust core: `connection.rs`, `query.rs`, `profile_store.rs`, `secret.rs`, `ai.rs`, `models.rs`, `error.rs`. SQLx drives PostgreSQL/MySQL/SQLite, the `mongodb` crate drives Mongo, `reqwest` with rustls carries AI traffic. `publish = false`. | | `src-tauri/` | The desktop shell. `src/commands.rs` holds 11 `#[tauri::command]` functions and `src/lib.rs` registers exactly those 11 in `generate_handler!`. `capabilities/default.json` grants only `core:default` to the `main` window. | | `src-tauri/gen/schemas/` | Tauri-generated ACL and capability schemas, four JSON files, and they are committed. Do not hand-edit them; they are regenerated by the Tauri build. | -| `scripts/` | Four Node ESM scripts: `validate-release.mjs` (the release-metadata validator wired to `npm run release:check`), `prepare-release-config.mjs`, `release-matrix.mjs`, `rewrite-updater-urls.mjs`. | +| `scripts/` | Node ESM release scripts: `validate-release.mjs` (the release-metadata validator wired to `npm run release:check`), `prepare-release-config.mjs` (the CI-only tauri config override: updater key, version, the shared Windows sign command), `release-matrix.mjs`, `stage-release-assets.mjs` (renames one leg's bundles into its `release-*` artifact), `write-updater-manifest.mjs` (writes and verifies `latest.json`), plus the Linux AppImage repack pair. | | `docs/` | `ARCHITECTURE.md`, `SECURITY.md`, `DEMO.md`, `DBEAVER-PORTING-MAP.md`. | | `dist/`, `target/`, `node_modules/` | Build output and dependencies, all three ignored by `.gitignore` and untracked. `git ls-files dist` returns nothing even though the directory exists locally. | @@ -88,18 +88,17 @@ Working branches are `/` off `develop`, types `feat`, `fix`, `chore` `refactor`, `perf`. Merges are squash-only and the branch is deleted, so the squash body is the pull request body. -`.github/workflows/gitflow.yml` now checks both of those conventions instead of leaving them to -reviewers. Its allowed prefixes are `feat`, `fix`, `chore`, `docs`, `test`, `refactor`, `perf`, -`release`, and `hotfix`: the first seven are the working types above, and `release` and `hotfix` are -added because both documents give those branches a job (they are the only branches that may merge -into `main`, and a back-merge pull request rides on one), so a check that rejected them would fail -a legitimate pull request. A pull request from any other prefix, an agent or tool name for instance, -fails the `branch name follows the convention` job, and `develop` and `main` are exempt because a -promotion or back-merge branch is not named after a type. On every push to `main` the -`main is contained in develop` job counts `origin/develop..origin/main` and fails when it is -nonzero, which is the missing back-merge that cost the sibling repository a month of a broken -default branch. Neither job name collides with a required check, so nothing new gates a merge until -someone adds it to branch protection. +`.github/workflows/gitflow.yml` checks both of those conventions instead of leaving them to +reviewers, and it is the organisation's copy: the file only calls the reusable workflow in +`redrob-labs/.github/.github/workflows/gitflow.yml`, which holds the one org-wide type list (`feat +fix chore docs test refactor perf ci build sync hotfix release`). A pull request from any other +prefix, an agent or tool name for instance, fails the `gitflow / branch name follows the convention` +check, and `develop` and `main` are exempt because a promotion or back-merge branch is not named +after a type. On every push to `main` the `gitflow / main is contained in develop` check counts +`origin/develop..origin/main`, fails when it is nonzero -- the missing back-merge that cost the +sibling repository a month of a broken default branch -- and opens the back-merge pull request +itself (`sync/back-merge-`). Neither check name is a required check, so nothing new gates a +merge until someone adds it to branch protection. ## Protection, and what is actually enforced diff --git a/CHANGELOG.md b/CHANGELOG.md index 0469d8b..c8a5954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ The release workflow's notes point here, so this file is what a download's notes follow the `vMAJOR.MINOR.PATCH` tags that trigger a release; while the major is 0 a minor bump is where behaviour may change. +## Unreleased + +### Changed + +- The release pipeline is the redrob-labs shared shape. Windows installers are Authenticode-signed + through Azure Artifact Signing with the job's OpenID Connect login and the organisation's one + signing script (`redrob-labs/.github/actions/sign-windows`), run by tauri as its sign command; the + self-signed certificate, its secrets and the `NotTrusted` allowance are gone, and every installer + must verify as `Valid`. Each platform leg uploads its files as a `release-*` artifact under the + launcher-readable names, `latest.json` is written from those files with every updater signature + checked against its bytes, and the shared `release-publish` job publishes the release as latest + with `SHA256SUMS.txt` and `redrob-launcher.json`. There is no draft any more; a manual run with a + `version` input is a dry run that publishes nothing. The gitflow check calls the organisation's + reusable workflow, whose back-merge job opens the back-merge pull request itself. + ## 0.2.4 - 2026-10-14 ### Fixed diff --git a/CONTRIBUTING.ko.md b/CONTRIBUTING.ko.md index 5d69ff3..a734e6f 100644 --- a/CONTRIBUTING.ko.md +++ b/CONTRIBUTING.ko.md @@ -60,11 +60,15 @@ git switch -c release/v0.2.0 git switch main && git pull git tag -a v0.2.0 -m "Redrob Query v0.2.0" git push origin v0.2.0 -# bring main's release commit back so develop does not fall behind: -git switch -c chore/sync-main-to-develop main -# open a pull request into develop +# the tag publishes the GitHub Release itself; the gitflow check then opens the +# back-merge pull request (sync/back-merge-) into develop -- merge it ``` +태그는 `package.json`의 버전과 같아야 하고, 모든 플랫폼 레그가 빌드·서명·검증을 마치면 릴리스는 +바로 **발행**됩니다 — 승인할 초안이 없습니다. 릴리스 없이 파이프라인만 돌려 보려면 **Signed +desktop release** 워크플로를 `version` 입력(예: `0.3.0-1`, MSI 번들러 때문에 pre-release 식별자는 +숫자여야 합니다)으로 수동 실행하세요. 모든 레그를 빌드·서명하고 아무것도 발행하지 않습니다. + hotfix도 같은 형태이며, `hotfix/*`를 `develop`이 아니라 `main`에서 떼고 양쪽으로 머지합니다. 저장소를 포크하고, 브랜치를 자기 포크에 push한 뒤 거기서 pull request를 여십시오. 기여하는 데 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4cf0995..e1de9ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,11 +59,16 @@ git switch -c release/v0.2.0 git switch main && git pull git tag -a v0.2.0 -m "Redrob Query v0.2.0" git push origin v0.2.0 -# bring main's release commit back so develop does not fall behind: -git switch -c chore/sync-main-to-develop main -# open a pull request into develop +# the tag publishes the GitHub Release itself; the gitflow check then opens the +# back-merge pull request (sync/back-merge-) into develop -- merge it ``` +The tag must equal the version in `package.json`, and the release is **published** when every +platform leg has built, signed and verified -- there is no draft to approve. To exercise the +pipeline without releasing, run the **Signed desktop release** workflow by hand with a `version` +such as `0.3.0-1` (a pre-release identifier must be numeric for the MSI bundler): it builds and +signs every leg and publishes nothing. + A hotfix is the same shape with `hotfix/*` cut from `main` rather than `develop`, and it merges into both. diff --git a/README.ko.md b/README.ko.md index 2e54fe8..c71de79 100644 --- a/README.ko.md +++ b/README.ko.md @@ -77,7 +77,7 @@ Linux에서는 Tauri가 요구하는 WebKitGTK 4.1, JavaScriptCoreGTK 4.1, libso `src-tauri/icons/`에 표준 Tauri PNG·ICNS·ICO·iOS·Android 아이콘이 들어 있습니다. 모두 RGBA여야 합니다 — `tauri::generate_context!`가 RGBA가 아닌 아이콘에서 컴파일 타임에 패닉하므로, `npm run release:check`가 이를 단정합니다. -서명된 인스톨러는 **Signed desktop release** 워크플로만 만듭니다. `vMAJOR.MINOR.PATCH` 태그를 푸시하면 Linux x64·macOS Intel·macOS Apple Silicon·Windows x64를 빌드하고, 업데이터 번들에 서명하고, macOS 빌드를 공증한 뒤 `latest.json`을 포함한 전부를 **초안** GitHub Release에 올립니다. 그 초안을 발행하는 것이 곧 릴리스입니다 — GitHub Releases가 업데이트 채널이라 `releases/latest/download/latest.json`은 사람이 발행한 버전만 서비스합니다. 자격 증명이 없으면 서명 없는 산출물을 발행하는 대신 릴리스를 중단합니다. +서명된 인스톨러는 **Signed desktop release** 워크플로만 만듭니다. `vMAJOR.MINOR.PATCH` 태그를 푸시하면(태그는 `package.json`의 버전과 같아야 합니다) Linux x64·macOS Intel·macOS Apple Silicon·Windows x64를 빌드하고, 업데이터 번들에 서명하고, macOS 빌드를 공증하고, Windows 인스톨러는 조직의 Azure Artifact Signing 계정으로 Authenticode 서명합니다 — 번들 직전에 잡이 OpenID Connect로 로그인하고 tauri가 `redrob-labs/.github`의 공용 서명 스크립트를 실행하므로 인증서 파일이나 클라이언트 시크릿은 어디에도 없습니다. 각 레그는 산출물을 [Redrob Launcher](https://github.com/redrob-labs/redrob-launcher)가 설치하는 이름으로 `release-*` 아티팩트에 올리고, 그 파일들로 `latest.json`을 쓰면서 모든 업데이터 서명을 실제 바이트와 대조한 뒤, 공용 `release-publish` 잡이 `SHA256SUMS.txt`·`redrob-launcher.json`과 함께 GitHub Release를 latest로 **발행**합니다(`-` 접미사가 있는 버전은 pre-release로 발행되어 `releases/latest`에 잡히지 않습니다). GitHub Releases가 업데이트 채널이라 `releases/latest/download/latest.json`은 가장 최근에 발행된 버전을 서비스합니다. 워크플로를 `version` 입력으로 수동 실행하면 드라이런입니다: 모든 레그를 빌드·서명·검증하고 아무것도 발행하지 않습니다. 자격 증명이 없으면 서명 없는 산출물을 발행하는 대신 릴리스를 중단합니다. ## 데스크톱 로컬 작업공간 데이터 diff --git a/README.md b/README.md index 9885af7..879da28 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Linux requires the WebKitGTK 4.1, JavaScriptCoreGTK 4.1, libsoup 3, and librsvg `src-tauri/icons/` holds standard Tauri PNG, ICNS, ICO, iOS, and Android icons. Every PNG must be RGBA — `tauri::generate_context!` panics at compile time on anything else — and `npm run release:check` asserts it. -Signed installers are produced only by the **Signed desktop release** workflow, which a pushed `vMAJOR.MINOR.PATCH` tag starts. It builds Linux x64, macOS Intel, macOS Apple Silicon, and Windows x64, signs the updater bundles, notarizes the macOS builds, and uploads everything plus `latest.json` to a **draft** GitHub Release. Publishing that draft is the release: GitHub Releases is the update channel, so `releases/latest/download/latest.json` only ever serves a version a human published. Missing credentials stop the release rather than publishing unsigned artifacts. +Signed installers are produced only by the **Signed desktop release** workflow, which a pushed `vMAJOR.MINOR.PATCH` tag starts (the tag must equal the version in `package.json`). It builds Linux x64, macOS Intel, macOS Apple Silicon, and Windows x64, signs the updater bundles, notarizes the macOS builds, and Authenticode-signs the Windows installers through the organisation's Azure Artifact Signing account -- the job logs in with OpenID Connect right before bundling and tauri runs the shared `redrob-labs/.github` signing script; no certificate file or client secret exists anywhere. Each leg uploads its files as a `release-*` artifact under the names [Redrob Launcher](https://github.com/redrob-labs/redrob-launcher) installs, `latest.json` is written from them and every updater signature is verified against its bytes, and the shared `release-publish` job then **publishes** the GitHub Release as latest with `SHA256SUMS.txt` and `redrob-launcher.json` beside the installers (a version with a `-` suffix is published as a pre-release, which `releases/latest` does not serve). GitHub Releases is the update channel, so `releases/latest/download/latest.json` serves the newest published version. A manual run of the workflow with a `version` input is a dry run: it builds, signs and verifies every leg and publishes nothing. Missing credentials stop the release rather than publishing unsigned artifacts. ## Desktop local workspace data diff --git a/scripts/launcher-asset-name.mjs b/scripts/launcher-asset-name.mjs index da43513..2114b69 100644 --- a/scripts/launcher-asset-name.mjs +++ b/scripts/launcher-asset-name.mjs @@ -7,10 +7,10 @@ // `Redrob Query__` instead -- and GitHub replaces the space with a dot // on upload -- so `Redrob.Query_0.2.0_amd64.AppImage` is invisible to the launcher. // -// Renaming is done on the release AFTER the build, by asset id, so the bundler, the signer and -// the updater's signature over the bytes are untouched. The updater does not depend on these -// names: latest.json embeds each signature and is pointed at the renamed assets by -// rewrite-updater-urls.mjs, which resolves asset ids to whatever name they carry at that moment. +// Renaming is done on the build runner AFTER the bundler and the signer have finished, by +// stage-release-assets.mjs, so the bytes and the updater's signature over them are untouched. +// latest.json is then written from the staged names (write-updater-manifest.mjs), so the updater +// points at exactly the names the publish job uploads. // // Only installers and their detached .sig files are renamed. The `.app.tar.gz` updater payloads // and latest.json keep the bundler's names: nothing installs them by hand, and the launcher has diff --git a/scripts/launcher-release-manifest.mjs b/scripts/launcher-release-manifest.mjs deleted file mode 100644 index 502b27c..0000000 --- a/scripts/launcher-release-manifest.mjs +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env node -// The release manifest Redrob Launcher reads from every GitHub Release of this app. -// -// One asset named `redrob-launcher.json`, uploaded next to the installers, tells the launcher -// what this release can do without any change on the launcher's side. Schema 1 carries a single -// fact: `sharedAccount` -- whether the release reads the launcher's shared account file, so that -// signing in to the launcher is all a user needs. It is `true` here because develop carries that -// change (the fallback to the launcher's shared account). -// -// The contract this mirrors lives in the launcher repository at docs/release-manifest.md. The -// content below is the exact bytes that document shows; the launcher ignores unknown fields and -// unknown schemas, so growing it later is safe, shrinking it is not. -// -// Usage: node scripts/launcher-release-manifest.mjs -// writes the manifest to (parent directories are created). - -import { mkdir, writeFile } from 'node:fs/promises'; -import { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -export const LAUNCHER_MANIFEST_NAME = 'redrob-launcher.json'; - -export const LAUNCHER_MANIFEST_SCHEMA = 1; - -// Set to false only if a release stops reading the shared account file; the launcher then -// treats the app as needing its own sign-in, whatever its built-in table says. -export const SHARED_ACCOUNT = true; - -/** The newline-terminated bytes of the manifest, exactly as the contract writes them. */ -export function launcherManifestContent() { - return `{ "schema": ${LAUNCHER_MANIFEST_SCHEMA}, "sharedAccount": ${SHARED_ACCOUNT} }\n`; -} - -/** - * Throws when `text` is not a schema-1 manifest declaring the shared account. Used by the unit - * test on the generated content, and available to anything that reads the file back. - * @param {string} text - */ -export function assertLauncherManifest(text) { - if (!text.endsWith('\n')) throw new Error(`${LAUNCHER_MANIFEST_NAME} must be newline-terminated`); - let parsed; - try { - parsed = JSON.parse(text); - } catch (error) { - throw new Error(`${LAUNCHER_MANIFEST_NAME} is not valid JSON: ${error.message}`); - } - if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new Error(`${LAUNCHER_MANIFEST_NAME} must be a JSON object`); - } - if (parsed.schema !== LAUNCHER_MANIFEST_SCHEMA) { - throw new Error(`${LAUNCHER_MANIFEST_NAME} schema must be ${LAUNCHER_MANIFEST_SCHEMA}, got ${JSON.stringify(parsed.schema)}`); - } - if (parsed.sharedAccount !== true) { - throw new Error(`${LAUNCHER_MANIFEST_NAME} must declare "sharedAccount": true, got ${JSON.stringify(parsed.sharedAccount)}`); - } - return parsed; -} - -/** - * Throws when a release's asset list does not carry the manifest. A release without it still - * installs, but the launcher falls back to its built-in table and may tell the user this - * release needs its own sign-in -- which is the silent failure this check exists to catch. - * @param {{ name: string }[]} assets the release's assets, as the GitHub API lists them - * @param {string} tag for the message only - */ -export function assertReleaseCarriesLauncherManifest(assets, tag) { - if (!assets.some((asset) => asset.name === LAUNCHER_MANIFEST_NAME)) { - throw new Error( - `${LAUNCHER_MANIFEST_NAME} is not attached to ${tag}; Redrob Launcher would not learn this release reads the shared account`, - ); - } -} - -if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { - const [, , outputPath] = process.argv; - if (!outputPath) { - console.error('usage: launcher-release-manifest.mjs '); - process.exit(2); - } - const content = launcherManifestContent(); - assertLauncherManifest(content); - await mkdir(dirname(outputPath), { recursive: true }); - await writeFile(outputPath, content, 'utf8'); - console.log(`wrote ${LAUNCHER_MANIFEST_NAME} to ${outputPath}: ${content.trim()}`); -} diff --git a/scripts/launcher-release-manifest.test.ts b/scripts/launcher-release-manifest.test.ts deleted file mode 100644 index c0cffd5..0000000 --- a/scripts/launcher-release-manifest.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { execFileSync } from 'node:child_process'; -import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; - -import { afterEach, describe, expect, it } from 'vitest'; - -import { - assertLauncherManifest, - assertReleaseCarriesLauncherManifest, - LAUNCHER_MANIFEST_NAME, - launcherManifestContent, -} from './launcher-release-manifest.mjs'; - -// The asset list of a release after rename-release-assets.mjs ran, as the GitHub API lists it. -const RELEASE_ASSETS = [ - 'redrob-query-0.2.1-linux-x86_64.AppImage', - 'redrob-query-0.2.1-linux-x86_64.AppImage.sig', - 'redrob-query-0.2.1-macos-aarch64.dmg', - 'redrob-query-0.2.1-windows-x64.msi', - 'latest.json', -].map((name, id) => ({ id, name })); - -describe('launcherManifestContent', () => { - it('is the exact bytes the launcher contract shows, newline-terminated', () => { - expect(launcherManifestContent()).toBe('{ "schema": 1, "sharedAccount": true }\n'); - }); - - it('is valid JSON with schema 1 and sharedAccount true', () => { - const parsed = JSON.parse(launcherManifestContent()); - expect(parsed).toEqual({ schema: 1, sharedAccount: true }); - expect(() => assertLauncherManifest(launcherManifestContent())).not.toThrow(); - }); - - it('is named the one file the launcher fetches from a release', () => { - expect(LAUNCHER_MANIFEST_NAME).toBe('redrob-launcher.json'); - }); -}); - -describe('assertLauncherManifest', () => { - it('rejects invalid JSON', () => { - expect(() => assertLauncherManifest('{ schema: 1 }\n')).toThrow(/not valid JSON/); - }); - - it('rejects a schema the launcher does not read as this contract', () => { - expect(() => assertLauncherManifest('{ "schema": 2, "sharedAccount": true }\n')).toThrow(/schema must be 1/); - expect(() => assertLauncherManifest('{ "sharedAccount": true }\n')).toThrow(/schema must be 1/); - }); - - it('rejects a manifest that does not declare the shared account', () => { - expect(() => assertLauncherManifest('{ "schema": 1, "sharedAccount": false }\n')).toThrow(/sharedAccount/); - expect(() => assertLauncherManifest('{ "schema": 1 }\n')).toThrow(/sharedAccount/); - expect(() => assertLauncherManifest('{ "schema": 1, "sharedAccount": "true" }\n')).toThrow(/sharedAccount/); - }); - - it('rejects a file without the trailing newline', () => { - expect(() => assertLauncherManifest('{ "schema": 1, "sharedAccount": true }')).toThrow(/newline/); - }); - - it('rejects non-object JSON', () => { - expect(() => assertLauncherManifest('[1]\n')).toThrow(/object/); - expect(() => assertLauncherManifest('null\n')).toThrow(/object/); - }); -}); - -describe('assertReleaseCarriesLauncherManifest', () => { - it('fails a release whose assets do not include redrob-launcher.json', () => { - expect(() => assertReleaseCarriesLauncherManifest(RELEASE_ASSETS, 'v0.2.1')).toThrow( - /redrob-launcher\.json is not attached to v0\.2\.1/, - ); - }); - - it('passes once the manifest is attached', () => { - const assets = [...RELEASE_ASSETS, { id: 99, name: LAUNCHER_MANIFEST_NAME }]; - expect(() => assertReleaseCarriesLauncherManifest(assets, 'v0.2.1')).not.toThrow(); - }); - - it('does not accept a differently named file', () => { - const assets = [...RELEASE_ASSETS, { id: 99, name: 'redrob-launcher.json.sig' }, { id: 100, name: 'launcher.json' }]; - expect(() => assertReleaseCarriesLauncherManifest(assets, 'v0.2.1')).toThrow(/not attached/); - }); -}); - -describe('launcher-release-manifest.mjs as the workflow runs it', () => { - const dirs: string[] = []; - afterEach(() => { - for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); - }); - - it('writes the manifest into a release asset directory that does not exist yet', () => { - const dir = mkdtempSync(join(tmpdir(), 'redrob-query-manifest-')); - dirs.push(dir); - const out = join(dir, 'release-assets', LAUNCHER_MANIFEST_NAME); - execFileSync(process.execPath, ['scripts/launcher-release-manifest.mjs', out], { stdio: 'pipe' }); - const text = readFileSync(out, 'utf8'); - expect(text).toBe(launcherManifestContent()); - expect(assertLauncherManifest(text)).toEqual({ schema: 1, sharedAccount: true }); - }); -}); diff --git a/scripts/prepare-release-config.mjs b/scripts/prepare-release-config.mjs index 68e8217..7e0903b 100644 --- a/scripts/prepare-release-config.mjs +++ b/scripts/prepare-release-config.mjs @@ -1,4 +1,4 @@ -import { mkdir, writeFile } from "node:fs/promises"; +import { access, mkdir, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; const publicKey = process.env.TAURI_UPDATER_PUBLIC_KEY?.trim(); @@ -8,14 +8,15 @@ if (!publicKey) // GitHub Releases IS the update channel -- there is no CDN in front of it. // // `releases/latest/download/` resolves to the newest published, non-prerelease -// release, which is what makes the draft-release QA gate work: a draft is not "latest", -// so no installed client can see a version until a human publishes it. Tauri follows -// the redirect to the asset itself. +// release. The shared publish job (redrob-labs/.github release-publish.yml) publishes a +// tagged build as latest -- or as a prerelease, invisible to this URL, when the version +// carries a `-` -- and a manual dispatch publishes nothing at all. Tauri follows the +// redirect to the asset itself. // -// The tag workflow attaches this manifest with tauri-action's `uploadUpdaterJson`, and -// its verify-release-assets job fails the build if the manifest is missing or if any -// platform URL points outside this repository's own release assets. Without that guard -// the failure is silent: the app installs and simply never updates again. +// The release workflow's updater-manifest job writes latest.json from the staged files and +// fails the build if any platform the matrix built is missing from it or any URL points +// outside this repository's own release assets. Without that guard the failure is silent: +// the app installs and simply never updates again. // // scripts/validate-release.mjs asserts this URL names this repository, so a copy of this // file taken from a sibling product cannot ship a feed pointing at the wrong repo. @@ -35,17 +36,40 @@ const config = { }, }; +// A dry run (workflow_dispatch with a version) builds installers for a version the sources do +// not declare, e.g. 0.3.0-rc.1 on a tree that says 0.2.4. The bundle names, the updater +// payloads and latest.json all have to agree on ONE version, so the dispatched one overrides +// the config here. A tag run passes the version the sources already carry (the version job +// refuses the tag otherwise), so this is a no-op there. +const releaseVersion = process.env.RELEASE_VERSION?.trim(); +if (releaseVersion) config.version = releaseVersion; + if (process.platform === "win32") { - const thumbprint = process.env.WIN_CSC_CERTIFICATE_THUMBPRINT?.trim(); - if (!thumbprint) { + // Authenticode through Azure Artifact Signing, with the ONE copy of the recipe the + // organisation keeps: redrob-labs/.github/actions/sign-windows/sign.ps1, checked out by + // the workflow at `.redrob-shared`. Tauri signs the executable before packaging and each + // installer after, so every one of them goes through this command; it reads the + // ARTIFACT_SIGNING_* variables from the job's environment and authenticates with the + // `azure/login` the job performed right before `tauri build`. No certificate file, no + // thumbprint, no client secret anywhere in this repository. + // + // Absolute path on purpose: the bundler does not promise which directory it runs the + // command from, and a relative path that resolves from src-tauri/ fails only on the + // Windows leg of a real release. + const sharedDir = resolve(process.env.REDROB_SHARED_DIR?.trim() || ".redrob-shared"); + const signScript = resolve(sharedDir, "actions/sign-windows/sign.ps1"); + try { + await access(signScript); + } catch { throw new Error( - "WIN_CSC_CERTIFICATE_THUMBPRINT is required for a Windows release.", + `${signScript} is missing: check out redrob-labs/.github at ${sharedDir} before preparing a Windows release.`, ); } config.bundle.windows = { - certificateThumbprint: thumbprint, - digestAlgorithm: "sha256", - timestampUrl: "http://timestamp.digicert.com", + signCommand: { + cmd: "pwsh", + args: ["-NoProfile", "-File", signScript, "%1"], + }, }; } @@ -57,4 +81,6 @@ await mkdir(resolve("src-tauri"), { recursive: true }); await writeFile(output, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600, }); -console.log(`Prepared release configuration for ${process.platform}.`); +console.log( + `Prepared release configuration for ${process.platform}${releaseVersion ? ` (version ${releaseVersion})` : ""}.`, +); diff --git a/scripts/release-matrix.mjs b/scripts/release-matrix.mjs index 5a64033..c4375f6 100644 --- a/scripts/release-matrix.mjs +++ b/scripts/release-matrix.mjs @@ -2,11 +2,12 @@ // latest.json, from ONE list. // // Two things drove this out of the workflow file. First, a static matrix cannot skip a single -// leg, and the Windows leg has to be skippable: Authenticode signing needs a certificate that -// is not configured yet, and a leg that fails on every tag makes the whole release red, which -// hides real regressions instead of surfacing them. Second, the verify job used to hardcode -// the four updater platform keys it required, so adding or removing a leg meant editing two -// places and getting a correct gate to fail for the wrong reason. +// leg, and the Windows leg has to be skippable: Authenticode signing needs the organisation's +// Azure Artifact Signing account to be configured for this repository, and a leg that fails on +// every tag makes the whole release red, which hides real regressions instead of surfacing them. +// Second, the manifest job used to hardcode the four updater platform keys it required, so +// adding or removing a leg meant editing two places and getting a correct gate to fail for the +// wrong reason. // // Windows is SKIPPED rather than built unsigned. An unsigned installer that reaches a user is // worse than no installer: it trains people to click through the warning. @@ -45,7 +46,7 @@ const legs = [ }, ]; -// Which file each bundle type leaves on the release. The verify job asserted +// Which file each bundle type leaves on the release. The manifest job asserted // `.AppImage .deb .dmg .exe .msi` literally, so skipping the Windows leg would have made a // correct gate fail for a reason that has nothing to do with the release being wrong. const bundleAssetSuffixes = { @@ -67,8 +68,9 @@ const skipped = legs.filter((leg) => !included.includes(leg)); for (const leg of skipped) { console.log( `::notice title=Platform skipped::${leg.label} is not built because the repository ` + - 'variable WINDOWS_SIGNING_READY is not "true". Set it once a code-signing ' + - 'certificate is in WIN_CSC_LINK; nothing else needs to change.', + 'variable WINDOWS_SIGNING_READY is not "true". Set it once Azure Artifact Signing ' + + '(the AZURE_* and ARTIFACT_SIGNING_* organisation secrets and the `release` environment ' + + 'federated credential) covers this repository; nothing else needs to change.', ); } diff --git a/scripts/rename-release-assets.mjs b/scripts/rename-release-assets.mjs deleted file mode 100644 index a5b6883..0000000 --- a/scripts/rename-release-assets.mjs +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env node -// Rename the installers on a draft release to the names Redrob Launcher installs, then assert -// that every platform the release built has an installable name. -// -// Runs in the verify job BEFORE rewrite-updater-urls.mjs: a rename keeps the asset id, and the -// rewriter resolves ids to names, so the manifest ends up pointing at the renamed files without -// either script knowing about the other. See launcher-asset-name.mjs for the mapping. -// -// Usage: node scripts/rename-release-assets.mjs -// Env: GH_TOKEN (for `gh api`) - -import { execFileSync } from 'node:child_process'; -import { readFile } from 'node:fs/promises'; - -import { isLauncherInstallable, launcherAssetName } from './launcher-asset-name.mjs'; -import { assertReleaseCarriesLauncherManifest } from './launcher-release-manifest.mjs'; - -const [, , releaseJsonPath, repository, tag] = process.argv; -if (!releaseJsonPath || !repository || !tag) { - console.error('usage: rename-release-assets.mjs '); - process.exit(2); -} -if (!/^v\d/.test(tag)) { - console.error(`tag ${tag} is not v`); - process.exit(2); -} -const version = tag.slice(1); - -const release = JSON.parse(await readFile(releaseJsonPath, 'utf8')); -const assets = release.assets ?? []; - -let renamed = 0; -for (const asset of assets) { - const target = launcherAssetName(asset.name, version); - if (target === null || target === asset.name) continue; - if (assets.some((other) => other.name === target)) { - console.error(`cannot rename ${asset.name}: ${target} is already attached to ${tag}`); - process.exit(1); - } - execFileSync( - 'gh', - ['api', '-X', 'PATCH', `repos/${repository}/releases/assets/${asset.id}`, '-f', `name=${target}`], - { stdio: ['ignore', 'ignore', 'inherit'] }, - ); - console.log(` ${asset.name} -> ${target}`); - asset.name = target; - renamed += 1; -} - -// One installable name per platform token the release carries an installer for. Judged on the -// final names, so a bundler rename upstream, or a new leg whose arch is not in the table, fails -// here instead of shipping a release the launcher cannot see. -const installable = assets.filter((asset) => isLauncherInstallable(asset.name)); -const installersLeft = assets.filter( - (asset) => - !asset.name.endsWith('.sig') && - /\.(AppImage|deb|dmg|msi)$/.test(asset.name) && - !isLauncherInstallable(asset.name), -); -if (installersLeft.length > 0) { - console.error( - `installers the launcher cannot read are attached to ${tag}:\n- ${installersLeft - .map((a) => a.name) - .join('\n- ')}`, - ); - process.exit(1); -} -if (installable.length === 0) { - console.error(`no launcher-installable asset is attached to ${tag}`); - process.exit(1); -} - -// The manifest the launcher reads beside the installers (scripts/launcher-release-manifest.mjs). -// The workflow uploads it before this script runs; a release without it would still install but -// the launcher would not learn that it reads the shared account. -try { - assertReleaseCarriesLauncherManifest(assets, tag); -} catch (error) { - console.error(error.message); - process.exit(1); -} - -console.log(`Renamed ${renamed} asset(s); ${installable.length} launcher-installable:`); -for (const asset of installable) console.log(` ${asset.name}`); diff --git a/scripts/resign-linux-appimage.sh b/scripts/resign-linux-appimage.sh index 41e438d..3b20c35 100644 --- a/scripts/resign-linux-appimage.sh +++ b/scripts/resign-linux-appimage.sh @@ -1,28 +1,27 @@ #!/usr/bin/env bash -# Repack the Linux AppImage without its over-bundled Wayland libraries, RE-SIGN it, re-upload it, -# and point latest.json at the new signature. +# Repack the Linux AppImage without its over-bundled Wayland libraries and RE-SIGN it, in place. # -# Runs on the Linux release leg AFTER tauri-action has built, signed and uploaded. Each step -# exists because skipping it fails silently rather than loudly: +# Runs on the Linux release leg after `tauri build` and BEFORE the bundle is staged into the +# release artifact. Each step exists because skipping it fails silently rather than loudly: # # repack without it the AppImage opens a blank window on Mesa 25+ (tauri#15665); see # strip-appimage-wayland.sh for the measurement # re-sign the Tauri updater's Linux artifact IS the AppImage, and its .sig covers the # pre-repack bytes. A stale signature still LOOKS signed: the app installs, and the # first update check rejects every download forever -# re-upload the draft would otherwise carry the old AppImage and the old .sig -# manifest latest.json embeds the signature text itself, not a link to the .sig file, so -# uploading a new .sig alone changes nothing a client reads # -# Usage: resign-linux-appimage.sh -# Env: GH_TOKEN, GITHUB_REPOSITORY, TAURI_SIGNING_PRIVATE_KEY, TAURI_SIGNING_PRIVATE_KEY_PASSWORD +# latest.json is written later, by write-updater-manifest.mjs, from whatever .sig sits beside the +# staged file -- so the new signature reaches the manifest without this script knowing about it, +# and that job verifies every signature against the bytes it names before anything is published. +# +# Usage: resign-linux-appimage.sh +# Env: TAURI_SIGNING_PRIVATE_KEY, TAURI_SIGNING_PRIVATE_KEY_PASSWORD set -euo pipefail -bundle_dir="${1:?usage: $0 }" -tag="${2:?usage: $0 }" -appimagetool="${3:?usage: $0 }" +bundle_dir="${1:?usage: $0 }" +appimagetool="${2:?usage: $0 }" here="$(cd "$(dirname "$0")" && pwd)" -: "${GITHUB_REPOSITORY:?}" "${TAURI_SIGNING_PRIVATE_KEY:?}" +: "${TAURI_SIGNING_PRIVATE_KEY:?}" mapfile -t images < <(find "$bundle_dir/appimage" -maxdepth 1 -name '*.AppImage' | sort) if [ "${#images[@]}" -ne 1 ]; then @@ -31,10 +30,6 @@ if [ "${#images[@]}" -ne 1 ]; then fi image="${images[0]}" name="$(basename "$image")" -# GitHub stores an uploaded asset under its name with every space turned into a dot -# ("Redrob Query_0.2.2_amd64.AppImage" becomes "Redrob.Query_0.2.2_amd64.AppImage"), so the -# release is searched for that form; the local file keeps its own name. -asset_name="${name// /.}" bash "$here/strip-appimage-wayland.sh" "$image" "$appimagetool" @@ -42,65 +37,4 @@ rm -f "$image.sig" npx --no-install tauri signer sign "$image" [ -s "$image.sig" ] || { echo "::error::tauri signer produced no $name.sig"; exit 1; } -# The release is a DRAFT, which the tags endpoint does not resolve, so find it in the listing. -work="$(mktemp -d)" -trap 'rm -rf "$work"' EXIT -release_assets() { - gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \ - | jq --arg tag "$tag" 'map(select(.tag_name == $tag)) | first | .assets' -} -asset_id_named() { - jq -r --arg name "$2" 'map(select(.name == $name)) | first | .id // empty' "$1" -} - -# On a draft, tauri-action writes REST asset URLs into latest.json -# (https://api.github.com/repos//releases/assets/) -- a draft asset has no public -# download URL yet -- so the manifest knows this AppImage by its asset ID, not its name. Record -# that ID before the upload below replaces the asset and mints a new one. -release_assets > "$work/assets.before.json" -old_id="$(asset_id_named "$work/assets.before.json" "$asset_name")" -if [ -z "$old_id" ]; then - echo "::error::$name is not attached to $tag; tauri-action did not upload it" - exit 1 -fi - -gh release upload "$tag" "$image" "$image.sig" --clobber --repo "$GITHUB_REPOSITORY" - -release_assets > "$work/assets.after.json" -new_id="$(asset_id_named "$work/assets.after.json" "$asset_name")" -manifest_id="$(asset_id_named "$work/assets.after.json" "latest.json")" -if [ -z "$new_id" ]; then - echo "::error::$name is not attached to $tag after re-upload" - exit 1 -fi -if [ -z "$manifest_id" ]; then - echo "::error::latest.json is not attached to $tag yet; the manifest cannot be updated" - exit 1 -fi -gh api -H 'Accept: application/octet-stream' \ - "repos/${GITHUB_REPOSITORY}/releases/assets/${manifest_id}" > "$work/latest.json" - -# Repoint every platform entry that named this AppImage -- by the old asset ID, or by name should -# a published-form URL ever appear -- at the re-uploaded asset, with the new signature. -# tauri-action writes both `linux-x86_64` and `linux-x86_64-appimage`, and missing one leaves a -# client that resolves the other key verifying against stale bytes. -sig="$(cat "$image.sig")" -new_url="https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${new_id}" -jq --arg name "$asset_name" --arg sig "$sig" --arg old_id "$old_id" --arg new_url "$new_url" ' - .platforms |= with_entries( - if (.value.url | (endswith("/releases/assets/" + $old_id) or endswith("/" + $name))) - then .value.url = $new_url | .value.signature = $sig - else . end) -' "$work/latest.json" > "$work/latest.new.json" - -replaced="$(jq --arg url "$new_url" --arg sig "$sig" \ - '[.platforms[] | select(.url == $url and .signature == $sig)] | length' \ - "$work/latest.new.json")" -if [ "$replaced" -lt 1 ]; then - echo "::error::no latest.json platform entry points at $name (asset $old_id); nothing was re-signed in the manifest" - exit 1 -fi - -cp "$work/latest.new.json" "$work/latest.json" -gh release upload "$tag" "$work/latest.json" --clobber --repo "$GITHUB_REPOSITORY" -echo "re-signed $name and updated $replaced latest.json platform entr$( [ "$replaced" -eq 1 ] && echo y || echo ies)" +echo "repacked and re-signed $name" diff --git a/scripts/rewrite-updater-urls.mjs b/scripts/rewrite-updater-urls.mjs deleted file mode 100644 index 2c5514f..0000000 --- a/scripts/rewrite-updater-urls.mjs +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env node -// Rewrite latest.json's per-platform URLs into the form clients will actually fetch. -// -// tauri-action uploads the updater manifest while the release is still a DRAFT, and a draft -// asset has no usable public download URL yet -- GitHub only mints -// /releases/download// when the release is published. So the action falls back to -// the REST asset endpoint, https://api.github.com/repos//releases/assets/, and that -// is what ends up in the manifest. -// -// Shipping that would break the updater: the REST asset endpoint returns the asset's JSON -// metadata unless the caller sends `Accept: application/octet-stream`, which an updater -// fetching a plain URL does not. The published URL is fully determined by the tag and the -// asset's name, so it can be written now, before publication, and be correct after it. -// -// Usage: node scripts/rewrite-updater-urls.mjs - -import { readFile, writeFile } from "node:fs/promises"; - -const [, , manifestPath, releaseJsonPath, repository, tag] = process.argv; -if (!manifestPath || !releaseJsonPath || !repository || !tag) { - console.error( - "usage: rewrite-updater-urls.mjs ", - ); - process.exit(2); -} - -const [manifest, release] = await Promise.all([ - readFile(manifestPath, "utf8").then(JSON.parse), - readFile(releaseJsonPath, "utf8").then(JSON.parse), -]); - -const namesById = new Map( - (release.assets ?? []).map((asset) => [String(asset.id), asset.name]), -); -const publishedPrefix = `https://github.com/${repository}/releases/download/${encodeURIComponent(tag)}/`; - -const platforms = manifest.platforms ?? {}; -const problems = []; -let rewritten = 0; -let alreadyCorrect = 0; - -for (const [platform, entry] of Object.entries(platforms)) { - const url = entry?.url; - if (typeof url !== "string" || url.length === 0) { - problems.push(`${platform} has no url`); - continue; - } - if (url.startsWith(publishedPrefix)) { - alreadyCorrect += 1; - continue; - } - const match = url.match(/\/releases\/assets\/(\d+)$/); - if (!match) { - problems.push(`${platform} url is neither a published URL nor a REST asset URL: ${url}`); - continue; - } - const name = namesById.get(match[1]); - if (!name) { - problems.push( - `${platform} references asset id ${match[1]}, which is not attached to ${tag}`, - ); - continue; - } - entry.url = `${publishedPrefix}${encodeURIComponent(name)}`; - console.log(` ${platform}: ${name}`); - rewritten += 1; -} - -if (problems.length > 0) { - console.error(`Cannot rewrite the updater manifest:\n- ${problems.join("\n- ")}`); - process.exit(1); -} -if (rewritten === 0 && alreadyCorrect === 0) { - console.error("The updater manifest lists no platforms at all"); - process.exit(1); -} - -await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); -console.log( - `Rewrote ${rewritten} platform URL(s); ${alreadyCorrect} already pointed at the published path.`, -); diff --git a/scripts/stage-release-assets.mjs b/scripts/stage-release-assets.mjs new file mode 100644 index 0000000..d742830 --- /dev/null +++ b/scripts/stage-release-assets.mjs @@ -0,0 +1,136 @@ +#!/usr/bin/env node +// Copy one platform leg's bundler output into the directory that becomes its `release-*` artifact, +// under the names the release will carry. +// +// The shared publish job (redrob-labs/.github release-publish.yml) uploads whatever the build legs +// put in their artifacts, byte for byte and name for name. So the names are decided HERE, on the +// runner that built the files, instead of being patched on the release afterwards by asset id as +// an earlier version of this pipeline did: +// +// - installers and their detached `.sig` files take the launcher-readable name +// `redrob-query---.` (launcher-asset-name.mjs); +// - the macOS updater payload, which tauri writes as `Redrob Query.app.tar.gz` with neither +// version nor architecture, becomes `Redrob.Query__.app.tar.gz` -- the name +// tauri-action gave it, so an installed client's platform key keeps resolving, and so the two +// macOS legs do not overwrite each other in the merged artifact. comes from the build +// target (`aarch64-apple-darwin` -> `aarch64`, `x86_64-apple-darwin` -> `x64`); +// - anything else keeps the bundler's name with every space replaced by a dot, which is the +// name GitHub would store it under anyway -- written out explicitly so latest.json can name +// the asset before it exists on the release. +// +// Renaming a file does not touch its bytes, so the updater signature made over it stays valid. +// +// Usage: node scripts/stage-release-assets.mjs +// is tauri's `target//release/bundle`; has no leading "v"; +// is the Rust target triple the leg built. + +import { copyFile, mkdir, readdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { isLauncherInstallable, launcherAssetName } from './launcher-asset-name.mjs'; + +// What leaves the bundle directory. Anything else tauri writes (the unpacked .app, the .deb's +// working tree, NSIS temp files) stays behind. +export const RELEASE_FILE_SUFFIXES = ['.AppImage', '.deb', '.dmg', '.msi', '-setup.exe', '.app.tar.gz']; + +const BUNDLER_NAME = /^Redrob[. ]Query_(?[^_]+)_/; + +// Rust target triple -> the architecture token tauri-action put in the updater payload's name. +export const PAYLOAD_ARCH = { + 'aarch64-apple-darwin': 'aarch64', + 'x86_64-apple-darwin': 'x64', +}; + +const UNVERSIONED_PAYLOAD = /^Redrob[. ]Query(?\.app\.tar\.gz(\.sig)?)$/; + +/** True for a release file or the detached signature of one. */ +export function isReleaseFile(name) { + const base = name.endsWith('.sig') ? name.slice(0, -'.sig'.length) : name; + return RELEASE_FILE_SUFFIXES.some((suffix) => base.endsWith(suffix)); +} + +/** + * The name a bundler file is published under. + * @param {string} name file name as the bundler wrote it + * @param {string} version release version without the leading "v" + * @param {string} target Rust target triple of the leg that built it + */ +export function stagedName(name, version, target) { + const renamed = launcherAssetName(name, version); + if (renamed !== null) return renamed; + const payload = UNVERSIONED_PAYLOAD.exec(name); + if (payload) { + const arch = PAYLOAD_ARCH[target]; + if (!arch) throw new Error(`${name}: no updater payload architecture is defined for target ${target}`); + return `Redrob.Query_${version}_${arch}${payload.groups.suffix}`; + } + const match = BUNDLER_NAME.exec(name); + if (match && match.groups.version !== version) { + throw new Error(`${name} carries version ${match.groups.version}, not ${version}`); + } + return name.replace(/ /g, '.'); +} + +async function* walk(dir) { + for (const entry of await readdir(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + if (entry.isDirectory()) { + // An unpacked macOS bundle; its .app.tar.gz sibling is the file that ships. + if (entry.name.endsWith('.app')) continue; + yield* walk(path); + } else if (entry.isFile()) { + yield { name: entry.name, path }; + } + } +} + +/** + * Stage every release file under `bundleDir` into `outDir`. + * @returns {Promise} the staged names, sorted + */ +export async function stageReleaseAssets(bundleDir, outDir, version, target) { + const staged = new Map(); + for await (const file of walk(bundleDir)) { + if (!isReleaseFile(file.name)) continue; + const published = stagedName(file.name, version, target); + const previous = staged.get(published); + if (previous) { + throw new Error(`${file.path} and ${previous} would both be published as ${published}`); + } + staged.set(published, file.path); + } + if (staged.size === 0) { + throw new Error(`no release file under ${bundleDir} (looked for ${RELEASE_FILE_SUFFIXES.join(' ')})`); + } + + // Every installer must land on a name Redrob Launcher reads. Judged on the final names, so a + // bundler rename upstream, or a new leg whose arch is not in the table, fails here instead of + // shipping a release the launcher cannot see. + const unreadable = [...staged.keys()].filter( + (name) => /\.(AppImage|deb|dmg|msi)$/.test(name) && !isLauncherInstallable(name), + ); + if (unreadable.length > 0) { + throw new Error(`installers Redrob Launcher cannot read:\n- ${unreadable.join('\n- ')}`); + } + + await mkdir(outDir, { recursive: true }); + for (const [target, source] of staged) await copyFile(source, join(outDir, target)); + return [...staged.keys()].sort(); +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { + const [, , bundleDir, outDir, version, target] = process.argv; + if (!bundleDir || !outDir || !version || !target) { + console.error('usage: stage-release-assets.mjs '); + process.exit(2); + } + try { + const names = await stageReleaseAssets(bundleDir, outDir, version, target); + console.log(`Staged ${names.length} file(s) into ${outDir}:`); + for (const name of names) console.log(` ${name}`); + } catch (error) { + console.error(error.message); + process.exit(1); + } +} diff --git a/scripts/stage-release-assets.test.ts b/scripts/stage-release-assets.test.ts new file mode 100644 index 0000000..23342f9 --- /dev/null +++ b/scripts/stage-release-assets.test.ts @@ -0,0 +1,168 @@ +import { mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { afterEach, describe, expect, it } from 'vitest'; + +import { isReleaseFile, PAYLOAD_ARCH, stagedName, stageReleaseAssets } from './stage-release-assets.mjs'; + +const LINUX_TARGET = 'x86_64-unknown-linux-gnu'; +const ARM_MAC = 'aarch64-apple-darwin'; +const INTEL_MAC = 'x86_64-apple-darwin'; +const WINDOWS_TARGET = 'x86_64-pc-windows-msvc'; + +// What tauri's bundler leaves under target//release/bundle on each leg, by directory. +const LINUX = { + appimage: ['Redrob Query_0.2.4_amd64.AppImage', 'Redrob Query_0.2.4_amd64.AppImage.sig'], + deb: ['Redrob Query_0.2.4_amd64.deb', 'Redrob Query_0.2.4_amd64.deb.sig', 'Redrob Query_0.2.4_amd64/control.tar.gz'], +}; +const MACOS = { + dmg: ['Redrob Query_0.2.4_aarch64.dmg', 'bundle_dmg.sh'], + macos: [ + // tauri names the updater payload with neither version nor architecture. + 'Redrob Query.app.tar.gz', + 'Redrob Query.app.tar.gz.sig', + 'Redrob Query.app/Contents/MacOS/Redrob Query', + 'Redrob Query.app/Contents/Info.plist', + ], +}; +const WINDOWS = { + nsis: ['Redrob Query_0.2.4_x64-setup.exe', 'Redrob Query_0.2.4_x64-setup.exe.sig', 'Redrob Query_0.2.4_x64-setup.nsi'], + msi: ['Redrob Query_0.2.4_x64_en-US.msi', 'Redrob Query_0.2.4_x64_en-US.msi.sig', 'main.wixobj'], +}; + +const dirs: string[] = []; +afterEach(() => { + for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); +}); + +function bundle(tree: Record) { + const root = mkdtempSync(join(tmpdir(), 'redrob-query-bundle-')); + dirs.push(root); + for (const [dir, files] of Object.entries(tree)) { + for (const file of files) { + const path = join(root, dir, file); + mkdirSync(join(path, '..'), { recursive: true }); + writeFileSync(path, `bytes of ${file}`); + } + } + return { bundleDir: root, outDir: join(root, 'out') }; +} + +describe('isReleaseFile', () => { + it('takes installers, updater payloads and their signatures', () => { + for (const name of [ + 'Redrob Query_0.2.4_amd64.AppImage', + 'Redrob Query_0.2.4_amd64.deb.sig', + 'Redrob Query_0.2.4_aarch64.dmg', + 'Redrob Query.app.tar.gz', + 'Redrob Query_0.2.4_x64-setup.exe.sig', + 'Redrob Query_0.2.4_x64_en-US.msi', + ]) { + expect(isReleaseFile(name), name).toBe(true); + } + }); + + it('leaves the bundler scaffolding behind', () => { + for (const name of ['bundle_dmg.sh', 'control.tar.gz', 'Redrob Query_0.2.4_x64-setup.nsi', 'main.wixobj', 'Info.plist', 'latest.json']) { + expect(isReleaseFile(name), name).toBe(false); + } + }); +}); + +describe('stagedName', () => { + it('renames installers and their .sig files to the launcher-readable name', () => { + expect(stagedName('Redrob Query_0.2.4_amd64.AppImage', '0.2.4', LINUX_TARGET)).toBe('redrob-query-0.2.4-linux-x86_64.AppImage'); + expect(stagedName('Redrob Query_0.2.4_x64_en-US.msi.sig', '0.2.4', WINDOWS_TARGET)).toBe('redrob-query-0.2.4-windows-x64.msi.sig'); + expect(stagedName('Redrob Query_0.2.4_x64-setup.exe', '0.2.4', WINDOWS_TARGET)).toBe('redrob-query-0.2.4-windows-x64-setup.exe'); + }); + + it('gives the macOS updater payload the version and arch tauri leaves out, as tauri-action did', () => { + expect(stagedName('Redrob Query.app.tar.gz', '0.2.4', ARM_MAC)).toBe('Redrob.Query_0.2.4_aarch64.app.tar.gz'); + expect(stagedName('Redrob Query.app.tar.gz.sig', '0.2.4', INTEL_MAC)).toBe('Redrob.Query_0.2.4_x64.app.tar.gz.sig'); + expect(PAYLOAD_ARCH).toEqual({ 'aarch64-apple-darwin': 'aarch64', 'x86_64-apple-darwin': 'x64' }); + }); + + it('refuses an updater payload from a target it has no architecture token for', () => { + expect(() => stagedName('Redrob Query.app.tar.gz', '0.2.4', 'universal-apple-darwin')).toThrow(/universal-apple-darwin/); + }); + + it('gives any other versioned bundler file the name GitHub would store it under: spaces become dots', () => { + expect(stagedName('Redrob Query_0.2.4_aarch64.app.tar.gz', '0.2.4', ARM_MAC)).toBe('Redrob.Query_0.2.4_aarch64.app.tar.gz'); + }); + + it('refuses a file built for another version', () => { + expect(() => stagedName('Redrob Query_0.2.3_amd64.deb', '0.2.4', LINUX_TARGET)).toThrow(/0\.2\.3/); + expect(() => stagedName('Redrob Query_0.2.3_x64.app.tar.gz', '0.2.4', INTEL_MAC)).toThrow(/0\.2\.3/); + }); + + it('keeps a numeric pre-release identifier, the form the dry run uses', () => { + expect(stagedName('Redrob Query_0.3.0-1_x64_en-US.msi', '0.3.0-1', WINDOWS_TARGET)).toBe('redrob-query-0.3.0-1-windows-x64.msi'); + expect(stagedName('Redrob Query.app.tar.gz', '0.3.0-1', ARM_MAC)).toBe('Redrob.Query_0.3.0-1_aarch64.app.tar.gz'); + }); +}); + +describe('stageReleaseAssets', () => { + it('stages the Linux leg: installers renamed, scaffolding left behind', async () => { + const { bundleDir, outDir } = bundle(LINUX); + const names = await stageReleaseAssets(bundleDir, outDir, '0.2.4', LINUX_TARGET); + expect(names).toEqual([ + 'redrob-query-0.2.4-linux-x86_64.AppImage', + 'redrob-query-0.2.4-linux-x86_64.AppImage.sig', + 'redrob-query-0.2.4-linux-x86_64.deb', + 'redrob-query-0.2.4-linux-x86_64.deb.sig', + ]); + expect(readdirSync(outDir).sort()).toEqual(names); + }); + + it('stages the macOS leg without descending into the unpacked .app', async () => { + const { bundleDir, outDir } = bundle(MACOS); + expect(await stageReleaseAssets(bundleDir, outDir, '0.2.4', ARM_MAC)).toEqual([ + 'Redrob.Query_0.2.4_aarch64.app.tar.gz', + 'Redrob.Query_0.2.4_aarch64.app.tar.gz.sig', + 'redrob-query-0.2.4-macos-aarch64.dmg', + ]); + }); + + it('stages the two macOS legs under names that do not collide when the artifacts are merged', async () => { + const arm = bundle(MACOS); + const intel = bundle({ ...MACOS, dmg: ['Redrob Query_0.2.4_x64.dmg'] }); + const armNames = await stageReleaseAssets(arm.bundleDir, arm.outDir, '0.2.4', ARM_MAC); + const intelNames = await stageReleaseAssets(intel.bundleDir, intel.outDir, '0.2.4', INTEL_MAC); + expect(intelNames).toEqual([ + 'Redrob.Query_0.2.4_x64.app.tar.gz', + 'Redrob.Query_0.2.4_x64.app.tar.gz.sig', + 'redrob-query-0.2.4-macos-x86_64.dmg', + ]); + expect(armNames.filter((name) => intelNames.includes(name))).toEqual([]); + }); + + it('stages the Windows leg: both installers and both signatures', async () => { + const { bundleDir, outDir } = bundle(WINDOWS); + expect(await stageReleaseAssets(bundleDir, outDir, '0.2.4', WINDOWS_TARGET)).toEqual([ + 'redrob-query-0.2.4-windows-x64-setup.exe', + 'redrob-query-0.2.4-windows-x64-setup.exe.sig', + 'redrob-query-0.2.4-windows-x64.msi', + 'redrob-query-0.2.4-windows-x64.msi.sig', + ]); + }); + + it('fails a bundle directory with nothing to release', async () => { + const { bundleDir, outDir } = bundle({ msi: ['main.wixobj'] }); + await expect(stageReleaseAssets(bundleDir, outDir, '0.2.4', WINDOWS_TARGET)).rejects.toThrow(/no release file/); + }); + + it('fails an installer the launcher could not read instead of shipping it', async () => { + // A leg whose arch is not in the rename table: the file keeps a bundler name. + const { bundleDir, outDir } = bundle({ dmg: ['Redrob Query_0.2.4_universal.dmg'] }); + await expect(stageReleaseAssets(bundleDir, outDir, '0.2.4', ARM_MAC)).rejects.toThrow(/Launcher cannot read/); + }); + + it('fails when two files would publish under one name', async () => { + const { bundleDir, outDir } = bundle({ + a: ['Redrob Query_0.2.4_amd64.AppImage'], + b: ['Redrob.Query_0.2.4_amd64.AppImage'], + }); + await expect(stageReleaseAssets(bundleDir, outDir, '0.2.4', LINUX_TARGET)).rejects.toThrow(/both be published/); + }); +}); diff --git a/scripts/write-updater-manifest.mjs b/scripts/write-updater-manifest.mjs new file mode 100644 index 0000000..e3b1195 --- /dev/null +++ b/scripts/write-updater-manifest.mjs @@ -0,0 +1,155 @@ +#!/usr/bin/env node +// Write latest.json -- the Tauri updater feed -- from the staged release files, and refuse to +// write one the released clients could not use. +// +// tauri-action used to write this file while uploading to a draft release, and two more steps then +// corrected it (draft asset URLs are REST endpoints the updater cannot fetch; renamed assets had to +// be re-resolved). With the build legs staging their files under their final names (see +// stage-release-assets.mjs) and the shared publish job uploading them unchanged, the published URL +// of every asset is known before the release exists: +// +// https://github.com//releases/download/v/ +// +// so the manifest is written once, here, and verified against the bytes beside it: +// +// - every platform key the build matrix produced is present (REQUIRED_PLATFORMS), and every +// asset suffix a built bundle type leaves is present (REQUIRED_ASSET_SUFFIXES) -- both come +// from scripts/release-matrix.mjs, the same single list that built the legs; +// - every signature verifies against the file it names (verify-updater-signature.mjs), because a +// signature over different bytes still LOOKS signed and fails on every user's machine at the +// first update check. The AppImage is repacked after tauri signs it, so this is a live risk. +// +// The platform keys mirror what tauri-action wrote for this app, so an installed client keeps +// resolving the key it already looks up: the plain target key (`linux-x86_64`, `darwin-aarch64`, +// `darwin-x86_64`, `windows-x86_64`) and the per-bundle aliases beside it. Windows prefers the +// NSIS installer, as `updaterJsonPreferNsis` did. +// +// Usage: node scripts/write-updater-manifest.mjs +// Env: TAURI_UPDATER_PUBLIC_KEY (required), REQUIRED_PLATFORMS, REQUIRED_ASSET_SUFFIXES +// (space-separated; empty means "whatever is there") + +import { execFileSync } from 'node:child_process'; +import { readdir, readFile, writeFile } from 'node:fs/promises'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Staged name suffix -> the updater platform keys that name it. Order matters only for the +// manifest's readability. +export const UPDATER_KEYS = [ + { suffix: '-linux-x86_64.AppImage', keys: ['linux-x86_64', 'linux-x86_64-appimage'] }, + { suffix: '-linux-x86_64.deb', keys: ['linux-x86_64-deb'] }, + { suffix: '_aarch64.app.tar.gz', keys: ['darwin-aarch64', 'darwin-aarch64-app'] }, + { suffix: '_x64.app.tar.gz', keys: ['darwin-x86_64', 'darwin-x86_64-app'] }, + { suffix: '-windows-x64-setup.exe', keys: ['windows-x86_64', 'windows-x86_64-nsis'] }, + { suffix: '-windows-x64.msi', keys: ['windows-x86_64-msi'] }, +]; + +export function assetUrl(repository, version, name) { + return `https://github.com/${repository}/releases/download/v${version}/${encodeURIComponent(name)}`; +} + +/** + * Build the manifest from `[{ name, signature }]` -- one entry per signed asset. + * Throws when a signed asset maps to no platform key, or two assets claim the same key. + */ +export function buildUpdaterManifest({ signed, version, repository, pubDate = new Date() }) { + const platforms = {}; + for (const { name, signature } of signed) { + const mapping = UPDATER_KEYS.find((entry) => name.endsWith(entry.suffix)); + if (!mapping) throw new Error(`${name}.sig: no updater platform key is defined for this asset`); + for (const key of mapping.keys) { + if (platforms[key]) throw new Error(`${name} and ${platforms[key].name} both claim ${key}`); + platforms[key] = { name, signature, url: assetUrl(repository, version, name) }; + } + } + return { + version, + notes: `See https://github.com/${repository}/blob/main/CHANGELOG.md for release notes.`, + pub_date: pubDate.toISOString(), + platforms: Object.fromEntries( + Object.entries(platforms).map(([key, { signature, url }]) => [key, { signature, url }]), + ), + }; +} + +/** + * Throw unless the manifest covers every required platform, every URL is this repository's own + * release asset for this version, and `names` carries every required suffix. + */ +export function assertUpdaterContract(manifest, { names, version, repository, requiredPlatforms, requiredSuffixes }) { + const problems = []; + if (manifest.version !== version) problems.push(`manifest version ${manifest.version} is not ${version}`); + const entries = Object.entries(manifest.platforms ?? {}); + if (entries.length === 0) problems.push('manifest lists no platforms at all'); + const prefix = `https://github.com/${repository}/releases/download/v${version}/`; + for (const [key, entry] of entries) { + if (!entry.signature) problems.push(`${key} has no signature`); + if (typeof entry.url !== 'string' || !entry.url.startsWith(prefix)) { + problems.push(`${key} url is not a release asset of ${repository} v${version}: ${entry.url}`); + } else if (!names.includes(decodeURIComponent(entry.url.slice(prefix.length)))) { + problems.push(`${key} names ${entry.url.slice(prefix.length)}, which is not among the staged files`); + } + } + for (const platform of requiredPlatforms) { + if (!manifest.platforms?.[platform]) problems.push(`missing a signed entry for ${platform}`); + } + for (const suffix of requiredSuffixes) { + if (!names.some((name) => name.endsWith(suffix))) { + problems.push(`no staged file ends in ${suffix} -- a built platform produced no installer`); + } + } + if (problems.length > 0) throw new Error(`latest.json would not serve the released clients:\n- ${problems.join('\n- ')}`); +} + +const splitList = (value) => (value ?? '').split(/\s+/).filter(Boolean); + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { + const [, , assetsDir, version, repository, output] = process.argv; + if (!assetsDir || !version || !repository || !output) { + console.error('usage: write-updater-manifest.mjs '); + process.exit(2); + } + const publicKey = process.env.TAURI_UPDATER_PUBLIC_KEY?.trim(); + if (!publicKey) { + console.error('TAURI_UPDATER_PUBLIC_KEY is required to verify the updater signatures'); + process.exit(2); + } + const verifier = resolve(dirname(fileURLToPath(import.meta.url)), 'verify-updater-signature.mjs'); + + try { + const names = (await readdir(assetsDir)).filter((name) => name !== 'latest.json').sort(); + const signed = []; + for (const sig of names.filter((name) => name.endsWith('.sig'))) { + const name = sig.slice(0, -'.sig'.length); + if (!names.includes(name)) throw new Error(`${sig} has no ${name} beside it`); + const signature = (await readFile(join(assetsDir, sig), 'utf8')).trim(); + let result; + try { + result = execFileSync(process.execPath, [verifier, publicKey, signature, join(assetsDir, name)], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'inherit'], + }).trim(); + } catch (error) { + throw new Error(`${name}: ${error.stdout?.toString().trim() || error.message}`); + } + console.log(` ${name}: ${result}`); + signed.push({ name, signature }); + } + if (signed.length === 0) throw new Error(`no .sig file under ${assetsDir}; nothing to publish to the updater`); + + const manifest = buildUpdaterManifest({ signed, version, repository }); + assertUpdaterContract(manifest, { + names, + version, + repository, + requiredPlatforms: splitList(process.env.REQUIRED_PLATFORMS), + requiredSuffixes: splitList(process.env.REQUIRED_ASSET_SUFFIXES), + }); + await writeFile(output, `${JSON.stringify(manifest, null, 2)}\n`); + const keys = Object.keys(manifest.platforms); + console.log(`Wrote ${output} for v${version}: ${keys.length} platform keys (${keys.join(' ')}), every signature verified.`); + } catch (error) { + console.error(error.message); + process.exit(1); + } +} diff --git a/scripts/write-updater-manifest.test.ts b/scripts/write-updater-manifest.test.ts new file mode 100644 index 0000000..2aa43ed --- /dev/null +++ b/scripts/write-updater-manifest.test.ts @@ -0,0 +1,178 @@ +import { execFileSync } from 'node:child_process'; +import { createHash, generateKeyPairSync, randomBytes, sign } from 'node:crypto'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { afterEach, describe, expect, it } from 'vitest'; + +import { assertUpdaterContract, assetUrl, buildUpdaterManifest, UPDATER_KEYS } from './write-updater-manifest.mjs'; + +const REPO = 'redrob-labs/redrob-query'; +const V = '0.2.4'; + +// The staged names every leg produces for one release (stage-release-assets.mjs). +const STAGED = [ + 'redrob-query-0.2.4-linux-x86_64.AppImage', + 'redrob-query-0.2.4-linux-x86_64.deb', + 'Redrob.Query_0.2.4_aarch64.app.tar.gz', + 'redrob-query-0.2.4-macos-aarch64.dmg', + 'Redrob.Query_0.2.4_x64.app.tar.gz', + 'redrob-query-0.2.4-macos-x86_64.dmg', + 'redrob-query-0.2.4-windows-x64-setup.exe', + 'redrob-query-0.2.4-windows-x64.msi', +]; +const SIGNED = STAGED.filter((name) => !name.endsWith('.dmg')); +const PLATFORMS = ['linux-x86_64', 'darwin-aarch64', 'darwin-x86_64', 'windows-x86_64']; +const SUFFIXES = ['.AppImage', '.deb', '.dmg', '.exe', '.msi']; + +const signed = (names: string[]) => names.map((name) => ({ name, signature: `sig-of-${name}` })); + +describe('buildUpdaterManifest', () => { + it('writes the keys tauri-action wrote for this app, so installed clients keep resolving theirs', () => { + const manifest = buildUpdaterManifest({ signed: signed(SIGNED), version: V, repository: REPO, pubDate: new Date(0) }); + expect(manifest.version).toBe(V); + expect(manifest.pub_date).toBe('1970-01-01T00:00:00.000Z'); + expect(Object.keys(manifest.platforms).sort()).toEqual( + [ + 'linux-x86_64', 'linux-x86_64-appimage', 'linux-x86_64-deb', + 'darwin-aarch64', 'darwin-aarch64-app', 'darwin-x86_64', 'darwin-x86_64-app', + 'windows-x86_64', 'windows-x86_64-nsis', 'windows-x86_64-msi', + ].sort(), + ); + }); + + it('points every URL at this repository release of this version, under the staged name', () => { + const manifest = buildUpdaterManifest({ signed: signed(SIGNED), version: V, repository: REPO }); + expect(manifest.platforms['linux-x86_64']).toEqual({ + signature: 'sig-of-redrob-query-0.2.4-linux-x86_64.AppImage', + url: `https://github.com/${REPO}/releases/download/v0.2.4/redrob-query-0.2.4-linux-x86_64.AppImage`, + }); + expect(manifest.platforms['darwin-x86_64'].url).toBe(assetUrl(REPO, V, 'Redrob.Query_0.2.4_x64.app.tar.gz')); + // Windows prefers the NSIS installer, as updaterJsonPreferNsis did. + expect(manifest.platforms['windows-x86_64'].url).toMatch(/-windows-x64-setup\.exe$/); + expect(manifest.platforms['windows-x86_64-msi'].url).toMatch(/-windows-x64\.msi$/); + }); + + it('refuses a signed file no platform key names, instead of dropping it silently', () => { + expect(() => buildUpdaterManifest({ signed: signed(['redrob-query-0.2.4-linux-aarch64.AppImage']), version: V, repository: REPO })).toThrow( + /no updater platform key/, + ); + }); + + it('refuses two files claiming one key', () => { + const twice = signed(['redrob-query-0.2.4-linux-x86_64.AppImage', 'Redrob.Query_0.2.4_amd64.AppImage'.replace('Redrob.Query_0.2.4_amd64', 'other-0.2.4-linux-x86_64')]); + expect(() => buildUpdaterManifest({ signed: twice, version: V, repository: REPO })).toThrow(/both claim linux-x86_64/); + }); + + it('has a suffix table that only names launcher-style or dotted bundler names', () => { + for (const { suffix } of UPDATER_KEYS) expect(suffix).not.toMatch(/ /); + }); +}); + +describe('assertUpdaterContract', () => { + const full = () => buildUpdaterManifest({ signed: signed(SIGNED), version: V, repository: REPO }); + const check = (manifest: ReturnType, names = STAGED) => + assertUpdaterContract(manifest, { names, version: V, repository: REPO, requiredPlatforms: PLATFORMS, requiredSuffixes: SUFFIXES }); + + it('passes a complete release', () => { + expect(() => check(full())).not.toThrow(); + }); + + it('fails when a built platform is missing from the manifest', () => { + const manifest = buildUpdaterManifest({ signed: signed(SIGNED.filter((n) => !n.includes('windows'))), version: V, repository: REPO }); + expect(() => check(manifest)).toThrow(/missing a signed entry for windows-x86_64/); + }); + + it('fails when a built bundle type left no installer', () => { + expect(() => check(full(), STAGED.filter((n) => !n.endsWith('.msi')))).toThrow(/no staged file ends in \.msi/); + }); + + it('fails a URL outside this repository, or one that names a file that is not staged', () => { + const foreign = full(); + foreign.platforms['linux-x86_64'].url = 'https://cdn.example.com/redrob-query.AppImage'; + expect(() => check(foreign)).toThrow(/not a release asset of/); + const unstaged = full(); + unstaged.platforms['linux-x86_64'].url = assetUrl(REPO, V, 'redrob-query-0.2.4-linux-x86_64.rpm'); + expect(() => check(unstaged)).toThrow(/not among the staged files/); + }); + + it('fails a manifest for another version', () => { + expect(() => check({ ...full(), version: '0.2.3' })).toThrow(/0\.2\.3 is not 0\.2\.4/); + }); +}); + +// minisign as Tauri writes it: "untrusted comment" line + base64 payload, the whole text wrapped in +// base64 once more. The pre-hashed ("ED") algorithm the updater uses: Ed25519 over BLAKE2b-512. +function minisignKeypair() { + const { publicKey, privateKey } = generateKeyPairSync('ed25519'); + const raw = publicKey.export({ format: 'der', type: 'spki' }).subarray(-32); + const keyId = randomBytes(8); + const pubText = `untrusted comment: minisign public key\n${Buffer.concat([Buffer.from('Ed'), keyId, raw]).toString('base64')}\n`; + const signFile = (bytes: Buffer) => { + const sig = sign(null, createHash('blake2b512').update(bytes).digest(), privateKey); + const trusted = 'trusted comment: timestamp:0'; + const global = sign(null, Buffer.concat([sig, Buffer.from(trusted.slice('trusted comment: '.length))]), privateKey); + const text = `untrusted comment: signature\n${Buffer.concat([Buffer.from('ED'), keyId, sig]).toString('base64')}\n${trusted}\n${global.toString('base64')}\n`; + return Buffer.from(text).toString('base64'); + }; + return { publicKey: Buffer.from(pubText).toString('base64'), signFile }; +} + +describe('write-updater-manifest.mjs as the workflow runs it', () => { + const dirs: string[] = []; + afterEach(() => { + for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); + }); + + function stagedRelease() { + const dir = mkdtempSync(join(tmpdir(), 'redrob-query-assets-')); + dirs.push(dir); + const { publicKey, signFile } = minisignKeypair(); + for (const name of STAGED) { + const bytes = Buffer.from(`bytes of ${name}`); + writeFileSync(join(dir, name), bytes); + if (SIGNED.includes(name)) writeFileSync(join(dir, `${name}.sig`), signFile(bytes)); + } + return { dir, publicKey, signFile }; + } + + const run = (dir: string, publicKey: string) => + execFileSync( + process.execPath, + ['scripts/write-updater-manifest.mjs', dir, V, REPO, join(dir, 'latest.json')], + { + encoding: 'utf8', + stdio: 'pipe', + env: { ...process.env, TAURI_UPDATER_PUBLIC_KEY: publicKey, REQUIRED_PLATFORMS: PLATFORMS.join(' '), REQUIRED_ASSET_SUFFIXES: SUFFIXES.join(' ') }, + }, + ); + + it('writes latest.json once every signature verifies against its file', () => { + const { dir, publicKey } = stagedRelease(); + const out = run(dir, publicKey); + expect(out).toMatch(/every signature verified/); + const manifest = JSON.parse(readFileSync(join(dir, 'latest.json'), 'utf8')); + expect(manifest.version).toBe(V); + expect(Object.keys(manifest.platforms)).toHaveLength(10); + expect(manifest.platforms['linux-x86_64'].signature).toBe(readFileSync(join(dir, 'redrob-query-0.2.4-linux-x86_64.AppImage.sig'), 'utf8')); + }); + + it('refuses a signature over different bytes -- the repacked-AppImage failure', () => { + const { dir, publicKey } = stagedRelease(); + writeFileSync(join(dir, 'redrob-query-0.2.4-linux-x86_64.AppImage'), 'repacked bytes'); + expect(() => run(dir, publicKey)).toThrow(/does not match these bytes/); + }); + + it('refuses a signature by another key', () => { + const { dir } = stagedRelease(); + const other = minisignKeypair(); + expect(() => run(dir, other.publicKey)).toThrow(/signed by key/); + }); + + it('refuses a .sig with no file beside it', () => { + const { dir, publicKey, signFile } = stagedRelease(); + writeFileSync(join(dir, 'redrob-query-0.2.4-linux-aarch64.AppImage.sig'), signFile(Buffer.from('x'))); + expect(() => run(dir, publicKey)).toThrow(/has no redrob-query-0.2.4-linux-aarch64.AppImage beside it/); + }); +}); From 48d6250d8ec4da5580698a2efb13d51d0c431c4f Mon Sep 17 00:00:00 2001 From: Janghoon Lee <44862514+its-janghoon@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:18:45 +0900 Subject: [PATCH 2/3] Name macOS updater archives for Redrob Launcher (#69) redrob-query--macos-.app.tar.gz (+.sig), latest.json rewritten to match. --- .github/workflows/release.yml | 6 +++--- scripts/launcher-asset-name.mjs | 11 ++++++++--- scripts/launcher-asset-name.test.ts | 16 +++++++--------- scripts/stage-release-assets.mjs | 24 ++++++++++++------------ scripts/stage-release-assets.test.ts | 20 ++++++++++---------- scripts/write-updater-manifest.mjs | 4 ++-- scripts/write-updater-manifest.test.ts | 6 +++--- 7 files changed, 45 insertions(+), 42 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df3c956..67e68dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -383,9 +383,9 @@ jobs: Write-Host "$($installer.Name) signed by CN=$cn status=$($signature.Status)" } - # Installers and their .sig files take the names Redrob Launcher installs - # (`redrob-query---.`); the macOS updater payload gets the version - # and architecture tauri leaves out of its name. + # Installers, the macOS updater payloads and their .sig files take the names Redrob Launcher + # reads (`redrob-query---.`); the payload gets the version and + # os-arch token tauri leaves out of its name. # Decided here, before upload, so latest.json can name every asset before the release exists. - name: Stage the release files under their published names shell: bash diff --git a/scripts/launcher-asset-name.mjs b/scripts/launcher-asset-name.mjs index 2114b69..369e4ef 100644 --- a/scripts/launcher-asset-name.mjs +++ b/scripts/launcher-asset-name.mjs @@ -12,9 +12,11 @@ // latest.json is then written from the staged names (write-updater-manifest.mjs), so the updater // points at exactly the names the publish job uploads. // -// Only installers and their detached .sig files are renamed. The `.app.tar.gz` updater payloads -// and latest.json keep the bundler's names: nothing installs them by hand, and the launcher has -// no use for them. +// Installers, the macOS `.app.tar.gz` updater payloads and their detached .sig files are all +// renamed, so every file on a release reads `redrob-query---` (the +// launcher skips the payloads -- `.app.tar.gz` is not a kind it installs -- but one convention +// across the asset list is what redrob-recall and the other repositories ship). latest.json keeps +// its name: it is the updater's fixed entry point. export const STEM = 'redrob-query'; @@ -28,6 +30,9 @@ const TAILS = [ // Not something the launcher installs (it takes the .msi), renamed so the two Windows // files sit next to each other under one stem. { arch: 'x64', suffix: '-setup.exe', token: 'windows-x64', ext: '-setup.exe' }, + // The macOS updater payloads, under the arch tauri-action used to put in their names. + { arch: 'aarch64', suffix: '.app.tar.gz', token: 'macos-aarch64', ext: '.app.tar.gz' }, + { arch: 'x64', suffix: '.app.tar.gz', token: 'macos-x86_64', ext: '.app.tar.gz' }, ]; const TAURI_NAME = /^Redrob[. ]Query_(?[^_]+)_(?.+?)(?\.sig)?$/; diff --git a/scripts/launcher-asset-name.test.ts b/scripts/launcher-asset-name.test.ts index 2da7565..dee4415 100644 --- a/scripts/launcher-asset-name.test.ts +++ b/scripts/launcher-asset-name.test.ts @@ -20,16 +20,14 @@ const V020 = { 'Redrob.Query_0.2.0_x64_en-US.msi.sig': 'redrob-query-0.2.0-windows-x64.msi.sig', 'Redrob.Query_0.2.0_x64-setup.exe': 'redrob-query-0.2.0-windows-x64-setup.exe', 'Redrob.Query_0.2.0_x64-setup.exe.sig': 'redrob-query-0.2.0-windows-x64-setup.exe.sig', + 'Redrob.Query_0.2.0_aarch64.app.tar.gz': 'redrob-query-0.2.0-macos-aarch64.app.tar.gz', + 'Redrob.Query_0.2.0_aarch64.app.tar.gz.sig': 'redrob-query-0.2.0-macos-aarch64.app.tar.gz.sig', + 'Redrob.Query_0.2.0_x64.app.tar.gz': 'redrob-query-0.2.0-macos-x86_64.app.tar.gz', + 'Redrob.Query_0.2.0_x64.app.tar.gz.sig': 'redrob-query-0.2.0-macos-x86_64.app.tar.gz.sig', }; -// Updater payloads and the manifest keep the bundler's names. -const UNTOUCHED = [ - 'Redrob.Query_0.2.0_aarch64.app.tar.gz', - 'Redrob.Query_0.2.0_aarch64.app.tar.gz.sig', - 'Redrob.Query_0.2.0_x64.app.tar.gz', - 'Redrob.Query_0.2.0_x64.app.tar.gz.sig', - 'latest.json', -]; +// The manifest keeps its name: it is the updater's fixed entry point. +const UNTOUCHED = ['latest.json']; describe('launcherAssetName', () => { it.each(Object.entries(V020))('renames %s', (from, to) => { @@ -64,7 +62,7 @@ describe('launcherAssetName', () => { describe('isLauncherInstallable', () => { it('accepts every renamed installer', () => { const installers = Object.values(V020).filter( - (name) => !name.endsWith('.sig') && !name.endsWith('-setup.exe'), + (name) => !name.endsWith('.sig') && !name.endsWith('-setup.exe') && !name.endsWith('.app.tar.gz'), ); expect(installers).toHaveLength(5); for (const name of installers) expect(isLauncherInstallable(name)).toBe(true); diff --git a/scripts/stage-release-assets.mjs b/scripts/stage-release-assets.mjs index d742830..95be319 100644 --- a/scripts/stage-release-assets.mjs +++ b/scripts/stage-release-assets.mjs @@ -7,13 +7,13 @@ // runner that built the files, instead of being patched on the release afterwards by asset id as // an earlier version of this pipeline did: // -// - installers and their detached `.sig` files take the launcher-readable name -// `redrob-query---.` (launcher-asset-name.mjs); -// - the macOS updater payload, which tauri writes as `Redrob Query.app.tar.gz` with neither -// version nor architecture, becomes `Redrob.Query__.app.tar.gz` -- the name -// tauri-action gave it, so an installed client's platform key keeps resolving, and so the two -// macOS legs do not overwrite each other in the merged artifact. comes from the build -// target (`aarch64-apple-darwin` -> `aarch64`, `x86_64-apple-darwin` -> `x64`); +// - installers, the macOS updater payloads and their detached `.sig` files take the +// launcher-readable name `redrob-query---.` (launcher-asset-name.mjs); +// - tauri writes the macOS updater payload as `Redrob Query.app.tar.gz` with neither version nor +// architecture; it becomes `redrob-query--macos-.app.tar.gz`, the same +// convention as every other asset (and what redrob-recall ships), so the two macOS legs do not +// overwrite each other in the merged artifact. comes from the build target +// (`aarch64-apple-darwin` -> `macos-aarch64`, `x86_64-apple-darwin` -> `macos-x86_64`); // - anything else keeps the bundler's name with every space replaced by a dot, which is the // name GitHub would store it under anyway -- written out explicitly so latest.json can name // the asset before it exists on the release. @@ -28,7 +28,7 @@ import { copyFile, mkdir, readdir } from 'node:fs/promises'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { isLauncherInstallable, launcherAssetName } from './launcher-asset-name.mjs'; +import { isLauncherInstallable, launcherAssetName, STEM } from './launcher-asset-name.mjs'; // What leaves the bundle directory. Anything else tauri writes (the unpacked .app, the .deb's // working tree, NSIS temp files) stays behind. @@ -36,10 +36,10 @@ export const RELEASE_FILE_SUFFIXES = ['.AppImage', '.deb', '.dmg', '.msi', '-set const BUNDLER_NAME = /^Redrob[. ]Query_(?[^_]+)_/; -// Rust target triple -> the architecture token tauri-action put in the updater payload's name. +// Rust target triple -> the launcher os-arch token the updater payload is published under. export const PAYLOAD_ARCH = { - 'aarch64-apple-darwin': 'aarch64', - 'x86_64-apple-darwin': 'x64', + 'aarch64-apple-darwin': 'macos-aarch64', + 'x86_64-apple-darwin': 'macos-x86_64', }; const UNVERSIONED_PAYLOAD = /^Redrob[. ]Query(?\.app\.tar\.gz(\.sig)?)$/; @@ -63,7 +63,7 @@ export function stagedName(name, version, target) { if (payload) { const arch = PAYLOAD_ARCH[target]; if (!arch) throw new Error(`${name}: no updater payload architecture is defined for target ${target}`); - return `Redrob.Query_${version}_${arch}${payload.groups.suffix}`; + return `${STEM}-${version}-${arch}${payload.groups.suffix}`; } const match = BUNDLER_NAME.exec(name); if (match && match.groups.version !== version) { diff --git a/scripts/stage-release-assets.test.ts b/scripts/stage-release-assets.test.ts index 23342f9..494c2ea 100644 --- a/scripts/stage-release-assets.test.ts +++ b/scripts/stage-release-assets.test.ts @@ -77,10 +77,10 @@ describe('stagedName', () => { expect(stagedName('Redrob Query_0.2.4_x64-setup.exe', '0.2.4', WINDOWS_TARGET)).toBe('redrob-query-0.2.4-windows-x64-setup.exe'); }); - it('gives the macOS updater payload the version and arch tauri leaves out, as tauri-action did', () => { - expect(stagedName('Redrob Query.app.tar.gz', '0.2.4', ARM_MAC)).toBe('Redrob.Query_0.2.4_aarch64.app.tar.gz'); - expect(stagedName('Redrob Query.app.tar.gz.sig', '0.2.4', INTEL_MAC)).toBe('Redrob.Query_0.2.4_x64.app.tar.gz.sig'); - expect(PAYLOAD_ARCH).toEqual({ 'aarch64-apple-darwin': 'aarch64', 'x86_64-apple-darwin': 'x64' }); + it('gives the macOS updater payload the version and launcher os-arch token tauri leaves out', () => { + expect(stagedName('Redrob Query.app.tar.gz', '0.2.4', ARM_MAC)).toBe('redrob-query-0.2.4-macos-aarch64.app.tar.gz'); + expect(stagedName('Redrob Query.app.tar.gz.sig', '0.2.4', INTEL_MAC)).toBe('redrob-query-0.2.4-macos-x86_64.app.tar.gz.sig'); + expect(PAYLOAD_ARCH).toEqual({ 'aarch64-apple-darwin': 'macos-aarch64', 'x86_64-apple-darwin': 'macos-x86_64' }); }); it('refuses an updater payload from a target it has no architecture token for', () => { @@ -88,7 +88,7 @@ describe('stagedName', () => { }); it('gives any other versioned bundler file the name GitHub would store it under: spaces become dots', () => { - expect(stagedName('Redrob Query_0.2.4_aarch64.app.tar.gz', '0.2.4', ARM_MAC)).toBe('Redrob.Query_0.2.4_aarch64.app.tar.gz'); + expect(stagedName('Redrob Query_0.2.4_amd64.rpm', '0.2.4', LINUX_TARGET)).toBe('Redrob.Query_0.2.4_amd64.rpm'); }); it('refuses a file built for another version', () => { @@ -98,7 +98,7 @@ describe('stagedName', () => { it('keeps a numeric pre-release identifier, the form the dry run uses', () => { expect(stagedName('Redrob Query_0.3.0-1_x64_en-US.msi', '0.3.0-1', WINDOWS_TARGET)).toBe('redrob-query-0.3.0-1-windows-x64.msi'); - expect(stagedName('Redrob Query.app.tar.gz', '0.3.0-1', ARM_MAC)).toBe('Redrob.Query_0.3.0-1_aarch64.app.tar.gz'); + expect(stagedName('Redrob Query.app.tar.gz', '0.3.0-1', ARM_MAC)).toBe('redrob-query-0.3.0-1-macos-aarch64.app.tar.gz'); }); }); @@ -118,8 +118,8 @@ describe('stageReleaseAssets', () => { it('stages the macOS leg without descending into the unpacked .app', async () => { const { bundleDir, outDir } = bundle(MACOS); expect(await stageReleaseAssets(bundleDir, outDir, '0.2.4', ARM_MAC)).toEqual([ - 'Redrob.Query_0.2.4_aarch64.app.tar.gz', - 'Redrob.Query_0.2.4_aarch64.app.tar.gz.sig', + 'redrob-query-0.2.4-macos-aarch64.app.tar.gz', + 'redrob-query-0.2.4-macos-aarch64.app.tar.gz.sig', 'redrob-query-0.2.4-macos-aarch64.dmg', ]); }); @@ -130,8 +130,8 @@ describe('stageReleaseAssets', () => { const armNames = await stageReleaseAssets(arm.bundleDir, arm.outDir, '0.2.4', ARM_MAC); const intelNames = await stageReleaseAssets(intel.bundleDir, intel.outDir, '0.2.4', INTEL_MAC); expect(intelNames).toEqual([ - 'Redrob.Query_0.2.4_x64.app.tar.gz', - 'Redrob.Query_0.2.4_x64.app.tar.gz.sig', + 'redrob-query-0.2.4-macos-x86_64.app.tar.gz', + 'redrob-query-0.2.4-macos-x86_64.app.tar.gz.sig', 'redrob-query-0.2.4-macos-x86_64.dmg', ]); expect(armNames.filter((name) => intelNames.includes(name))).toEqual([]); diff --git a/scripts/write-updater-manifest.mjs b/scripts/write-updater-manifest.mjs index e3b1195..8fb0c61 100644 --- a/scripts/write-updater-manifest.mjs +++ b/scripts/write-updater-manifest.mjs @@ -38,8 +38,8 @@ import { fileURLToPath } from 'node:url'; export const UPDATER_KEYS = [ { suffix: '-linux-x86_64.AppImage', keys: ['linux-x86_64', 'linux-x86_64-appimage'] }, { suffix: '-linux-x86_64.deb', keys: ['linux-x86_64-deb'] }, - { suffix: '_aarch64.app.tar.gz', keys: ['darwin-aarch64', 'darwin-aarch64-app'] }, - { suffix: '_x64.app.tar.gz', keys: ['darwin-x86_64', 'darwin-x86_64-app'] }, + { suffix: '-macos-aarch64.app.tar.gz', keys: ['darwin-aarch64', 'darwin-aarch64-app'] }, + { suffix: '-macos-x86_64.app.tar.gz', keys: ['darwin-x86_64', 'darwin-x86_64-app'] }, { suffix: '-windows-x64-setup.exe', keys: ['windows-x86_64', 'windows-x86_64-nsis'] }, { suffix: '-windows-x64.msi', keys: ['windows-x86_64-msi'] }, ]; diff --git a/scripts/write-updater-manifest.test.ts b/scripts/write-updater-manifest.test.ts index 2aa43ed..5681c53 100644 --- a/scripts/write-updater-manifest.test.ts +++ b/scripts/write-updater-manifest.test.ts @@ -15,9 +15,9 @@ const V = '0.2.4'; const STAGED = [ 'redrob-query-0.2.4-linux-x86_64.AppImage', 'redrob-query-0.2.4-linux-x86_64.deb', - 'Redrob.Query_0.2.4_aarch64.app.tar.gz', + 'redrob-query-0.2.4-macos-aarch64.app.tar.gz', 'redrob-query-0.2.4-macos-aarch64.dmg', - 'Redrob.Query_0.2.4_x64.app.tar.gz', + 'redrob-query-0.2.4-macos-x86_64.app.tar.gz', 'redrob-query-0.2.4-macos-x86_64.dmg', 'redrob-query-0.2.4-windows-x64-setup.exe', 'redrob-query-0.2.4-windows-x64.msi', @@ -48,7 +48,7 @@ describe('buildUpdaterManifest', () => { signature: 'sig-of-redrob-query-0.2.4-linux-x86_64.AppImage', url: `https://github.com/${REPO}/releases/download/v0.2.4/redrob-query-0.2.4-linux-x86_64.AppImage`, }); - expect(manifest.platforms['darwin-x86_64'].url).toBe(assetUrl(REPO, V, 'Redrob.Query_0.2.4_x64.app.tar.gz')); + expect(manifest.platforms['darwin-x86_64'].url).toBe(assetUrl(REPO, V, 'redrob-query-0.2.4-macos-x86_64.app.tar.gz')); // Windows prefers the NSIS installer, as updaterJsonPreferNsis did. expect(manifest.platforms['windows-x86_64'].url).toMatch(/-windows-x64-setup\.exe$/); expect(manifest.platforms['windows-x86_64-msi'].url).toMatch(/-windows-x64\.msi$/); From 698a30e025415ffa0ff54ea7141fa5ef100c69df Mon Sep 17 00:00:00 2001 From: Janghoon Lee <44862514+its-janghoon@users.noreply.github.com> Date: Fri, 16 Oct 2026 16:52:41 +0900 Subject: [PATCH 3/3] Rewrite Redrob Query as a Rust egui/wgpu desktop app (#71) Replaces the Tauri + React shell with a native egui 0.36/wgpu app over the engine crate (now redrob-query-core), in the redrob-canvas repository shape: brand pipeline pinned against the design system, bilingual Windows setup, macOS disk image, tools/release, a thin PR-only CI and a release workflow with the three targets, SHA256SUMS and redrob-launcher.json. The engine gains a Workspace facade (one method per former Tauri command), key/index metadata readers, the launcher's shared account.json reader and a console-direct AI transport that surfaces the console's own error message. The shell has profiles, a schema browser, a tabbed SQL/MQL editor, a virtualised result grid with reviewed edits, a structure panel with DDL forms, an Ask dock and bounded workspace persistence. Version 0.3.0. --- .env.example | 15 - .github/PULL_REQUEST_TEMPLATE.md | 12 +- .github/workflows/ci.yml | 197 +- .github/workflows/e2e.yml | 94 - .github/workflows/gitflow.yml | 21 - .github/workflows/release.yml | 803 +-- .gitignore | 32 +- AGENTS.md | 200 +- CHANGELOG.md | 35 +- CONTRIBUTING.ko.md | 70 +- CONTRIBUTING.md | 67 +- Cargo.lock | 4735 +++++++++-------- Cargo.toml | 62 +- DESIGN_SYSTEM_PIN.json | 42 - README.ko.md | 211 +- README.md | 217 +- apps/redrob-query/Cargo.toml | 38 + apps/redrob-query/build.rs | 33 + apps/redrob-query/src/account.rs | 64 + apps/redrob-query/src/ai_dock.rs | 259 + apps/redrob-query/src/app.rs | 663 +++ apps/redrob-query/src/changes.rs | 225 + apps/redrob-query/src/editor.rs | 502 ++ apps/redrob-query/src/grid.rs | 630 +++ apps/redrob-query/src/highlight.rs | 498 ++ apps/redrob-query/src/main.rs | 96 + apps/redrob-query/src/mongo.rs | 97 + apps/redrob-query/src/profiles.rs | 750 +++ apps/redrob-query/src/schema.rs | 321 ++ apps/redrob-query/src/services.rs | 65 + apps/redrob-query/src/structure.rs | 535 ++ apps/redrob-query/src/theme.rs | 54 + apps/redrob-query/src/workspace.rs | 125 + brand/SOURCES.json | 54 + crates/redrob-core/Cargo.toml | 38 - crates/redrob-core/src/ai.rs | 1277 ----- crates/redrob-query-core/Cargo.toml | 34 + crates/redrob-query-core/src/account.rs | 209 + crates/redrob-query-core/src/ai.rs | 953 ++++ .../src/cell_edits.rs | 0 .../src/connection.rs | 47 +- .../src/ddl.rs | 0 .../src/error.rs | 4 - crates/redrob-query-core/src/keys.rs | 405 ++ .../src/lib.rs | 7 +- .../src/models.rs | 0 .../src/profile_store.rs | 0 .../src/query.rs | 0 .../src/secret.rs | 0 crates/redrob-query-core/src/workspace.rs | 294 + docs/ARCHITECTURE.md | 9 + docs/DEMO.md | 67 - e2e/run.mjs | 213 - e2e/seed.sql | 6 - index.html | 14 - installer/macos/dmg_settings.py | 42 + installer/macos/make-dmg.sh | 86 + installer/macos/res/background.html | 55 + installer/macos/res/background.png | Bin 0 -> 18186 bytes installer/macos/res/background@2x.png | Bin 0 -> 42728 bytes installer/macos/res/icon.png | Bin 0 -> 12129 bytes installer/macos/uninstaller/uninstall.sh | 109 + installer/windows/README.md | 72 + installer/windows/build.py | 215 + installer/windows/redrob_query_setup.cpp | 1494 ++++++ installer/windows/redrob_query_setup.manifest | 26 + installer/windows/redrob_query_setup.rc | 50 + installer/windows/res/background.html | 106 + installer/windows/res/background.png | Bin 0 -> 452013 bytes installer/windows/res/icon.png | Bin 0 -> 12129 bytes installer/windows/res/lockup.png | Bin 0 -> 17000 bytes installer/windows/resource.h | 14 + installer/windows/verify-install.ps1 | 51 + package-lock.json | 3920 -------------- package.json | 77 - resources/brand/redrob-query-lockup-white.png | Bin 0 -> 150544 bytes resources/brand/redrob-query-lockup.png | Bin 0 -> 166580 bytes resources/brand/redrob-query-splash.png | Bin 0 -> 17539 bytes .../hicolor/128x128/apps/redrob-query.png | Bin 0 -> 5022 bytes .../icons/hicolor/16x16/apps/redrob-query.png | Bin 0 -> 653 bytes .../hicolor/256x256/apps/redrob-query.png | Bin 0 -> 12129 bytes .../icons/hicolor/32x32/apps/redrob-query.png | Bin 0 -> 1250 bytes .../icons/hicolor/48x48/apps/redrob-query.png | Bin 0 -> 1957 bytes .../hicolor/512x512/apps/redrob-query.png | Bin 0 -> 26851 bytes .../icons/hicolor/64x64/apps/redrob-query.png | Bin 0 -> 2572 bytes resources/icons/redrob-query-small.svg | 1 + resources/icons/redrob-query.icns | Bin 0 -> 193743 bytes resources/icons/redrob-query.ico | Bin 0 -> 18647 bytes resources/icons/redrob-query.svg | 1 + rust-toolchain.toml | 2 +- rustfmt.toml | 3 + scripts/build-app-icon.py | 103 - scripts/build-brand-assets.py | 308 ++ scripts/check-brand.py | 74 + scripts/check-compatibility-matrix.mjs | 219 - scripts/check-design-system.mjs | 173 - scripts/check-design-system.verify.py | 50 - scripts/check-upstream-paths.mjs | 193 - scripts/fix-danger-vs-brand.py | 70 - scripts/ink-on-ground.mjs | 87 - scripts/install-plugin-assets.mjs | 153 - scripts/launcher-asset-name.mjs | 82 - scripts/launcher-asset-name.test.ts | 93 - scripts/prepare-release-config.mjs | 86 - scripts/release-matrix.mjs | 114 - scripts/resign-linux-appimage.sh | 40 - scripts/stage-release-assets.mjs | 136 - scripts/stage-release-assets.test.ts | 168 - scripts/strip-appimage-wayland.sh | 58 - scripts/swap-icons.py | 93 - scripts/swap-loader.py | 107 - scripts/tokenize-css.py | 233 - scripts/validate-release.mjs | 109 - scripts/verify-dialect-sql.mjs | 194 - scripts/verify-updater-signature.mjs | 83 - scripts/verify-upstream-sql.mjs | 244 - scripts/verify-upstream.mjs | 170 - scripts/write-updater-manifest.mjs | 155 - scripts/write-updater-manifest.test.ts | 178 - src-tauri/Cargo.toml | 32 - src-tauri/build.rs | 3 - src-tauri/capabilities/default.json | 6 - src-tauri/gen/schemas/acl-manifests.json | 1 - src-tauri/gen/schemas/capabilities.json | 1 - src-tauri/gen/schemas/desktop-schema.json | 2292 -------- src-tauri/gen/schemas/linux-schema.json | 2292 -------- src-tauri/icons/128x128.png | Bin 6513 -> 0 bytes src-tauri/icons/128x128@2x.png | Bin 12178 -> 0 bytes src-tauri/icons/32x32.png | Bin 3395 -> 0 bytes src-tauri/icons/64x64.png | Bin 3544 -> 0 bytes src-tauri/icons/Square107x107Logo.png | Bin 5621 -> 0 bytes src-tauri/icons/Square142x142Logo.png | Bin 7334 -> 0 bytes src-tauri/icons/Square150x150Logo.png | Bin 7563 -> 0 bytes src-tauri/icons/Square284x284Logo.png | Bin 16730 -> 0 bytes src-tauri/icons/Square30x30Logo.png | Bin 3139 -> 0 bytes src-tauri/icons/Square310x310Logo.png | Bin 18961 -> 0 bytes src-tauri/icons/Square44x44Logo.png | Bin 4423 -> 0 bytes src-tauri/icons/Square71x71Logo.png | Bin 3975 -> 0 bytes src-tauri/icons/Square89x89Logo.png | Bin 4743 -> 0 bytes src-tauri/icons/StoreLogo.png | Bin 2758 -> 0 bytes .../android/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../icons/android/mipmap-hdpi/ic_launcher.png | Bin 2897 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 8295 -> 0 bytes .../android/mipmap-hdpi/ic_launcher_round.png | Bin 2916 -> 0 bytes .../icons/android/mipmap-mdpi/ic_launcher.png | Bin 2734 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 5770 -> 0 bytes .../android/mipmap-mdpi/ic_launcher_round.png | Bin 2832 -> 0 bytes .../android/mipmap-xhdpi/ic_launcher.png | Bin 5833 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 11156 -> 0 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 5545 -> 0 bytes .../android/mipmap-xxhdpi/ic_launcher.png | Bin 8936 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 19599 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 8435 -> 0 bytes .../android/mipmap-xxxhdpi/ic_launcher.png | Bin 12034 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 27814 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 11109 -> 0 bytes .../android/values/ic_launcher_background.xml | 4 - src-tauri/icons/app-icon-small.svg | 1 - src-tauri/icons/app-icon.png | Bin 198519 -> 0 bytes src-tauri/icons/app-icon.svg | 1 - src-tauri/icons/icon.icns | Bin 169385 -> 0 bytes src-tauri/icons/icon.ico | Bin 372526 -> 0 bytes src-tauri/icons/icon.png | Bin 26203 -> 0 bytes src-tauri/icons/ios/AppIcon-20x20@1x.png | Bin 1060 -> 0 bytes src-tauri/icons/ios/AppIcon-20x20@2x-1.png | Bin 2357 -> 0 bytes src-tauri/icons/ios/AppIcon-20x20@2x.png | Bin 2357 -> 0 bytes src-tauri/icons/ios/AppIcon-20x20@3x.png | Bin 3453 -> 0 bytes src-tauri/icons/ios/AppIcon-29x29@1x.png | Bin 1692 -> 0 bytes src-tauri/icons/ios/AppIcon-29x29@2x-1.png | Bin 3377 -> 0 bytes src-tauri/icons/ios/AppIcon-29x29@2x.png | Bin 3377 -> 0 bytes src-tauri/icons/ios/AppIcon-29x29@3x.png | Bin 4575 -> 0 bytes src-tauri/icons/ios/AppIcon-40x40@1x.png | Bin 2357 -> 0 bytes src-tauri/icons/ios/AppIcon-40x40@2x-1.png | Bin 4481 -> 0 bytes src-tauri/icons/ios/AppIcon-40x40@2x.png | Bin 4481 -> 0 bytes src-tauri/icons/ios/AppIcon-40x40@3x.png | Bin 6259 -> 0 bytes src-tauri/icons/ios/AppIcon-512@2x.png | Bin 76458 -> 0 bytes src-tauri/icons/ios/AppIcon-60x60@2x.png | Bin 6259 -> 0 bytes src-tauri/icons/ios/AppIcon-60x60@3x.png | Bin 9091 -> 0 bytes src-tauri/icons/ios/AppIcon-76x76@1x.png | Bin 4141 -> 0 bytes src-tauri/icons/ios/AppIcon-76x76@2x.png | Bin 7824 -> 0 bytes src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png | Bin 8183 -> 0 bytes src-tauri/src/commands.rs | 163 - src-tauri/src/lib.rs | 39 - src-tauri/src/main.rs | 5 - src-tauri/tauri.conf.json | 46 - src/App.test.tsx | 413 -- src/App.tsx | 103 - src/api/bridge.test.ts | 546 -- src/api/bridge.ts | 756 --- src/api/editSource.test.ts | 38 - src/api/editSource.ts | 37 - .../brand/redrob-symbol-solid-white.png | Bin 4233 -> 0 bytes src/assets/brand/redrob-symbol.png | Bin 30279 -> 0 bytes src/components/ActivityRail.tsx | 36 - src/components/AiPanel.tsx | 65 - src/components/AiSettingsModal.tsx | 187 - src/components/ChangesPanel.tsx | 39 - src/components/CommandPalette.tsx | 44 - src/components/ConnectionModal.tsx | 109 - src/components/IconButton.tsx | 17 - src/components/Navigator.select.test.ts | 18 - src/components/Navigator.tsx | 112 - src/components/QueryEditor.tsx | 81 - src/components/ResultGrid.test.ts | 29 - src/components/ResultGrid.tsx | 164 - src/components/StatusBar.tsx | 13 - src/components/StructurePanel.test.tsx | 195 - src/components/StructurePanel.tsx | 230 - src/components/TableChangeModal.tsx | 75 - src/components/rowExport.test.ts | 20 - src/components/rowExport.ts | 21 - src/domain/types.ts | 164 - src/main.tsx | 9 - src/plugins/PluginView.test.tsx | 132 - src/plugins/PluginView.tsx | 94 - src/plugins/PluginWorkspace.test.tsx | 98 - src/plugins/PluginWorkspace.tsx | 140 - src/plugins/host.test.ts | 282 - src/plugins/host.ts | 314 -- src/plugins/hostKeys.test.ts | 167 - src/plugins/manifest.test.ts | 124 - src/plugins/manifest.ts | 150 - src/plugins/protocol.ts | 133 - src/plugins/registry.ts | 122 - src/plugins/shell.test.tsx | 100 - src/plugins/storageShim.test.ts | 113 - src/schema/mysqlKeys.ts | 123 - src/schema/postgresKeys.ts | 118 - src/schema/schemaKeys.test.ts | 279 - src/schema/schemaKeys.ts | 312 -- src/schema/sqlIdentifiers.ts | 14 - src/shortcuts.test.ts | 31 - src/shortcuts.ts | 39 - src/store/WorkspaceProvider.tsx | 18 - src/store/workspacePersistence.ts | 195 - src/store/workspaceStore.test.ts | 725 --- src/store/workspaceStore.ts | 657 --- src/styles/app.css | 685 --- src/test/design-system-styles.stub.css | 11 - src/test/setup.ts | 27 - src/ui/Icon.tsx | 26 - src/ui/useGround.ts | 30 - src/vite-env.d.ts | 5 - tools/release/make_deb.py | 165 + tools/release/stage.py | 135 + tools/release_matrix.py | 139 + tsconfig.app.json | 21 - tsconfig.json | 7 - tsconfig.node.json | 10 - vite.config.ts | 37 - 250 files changed, 13631 insertions(+), 26055 deletions(-) delete mode 100644 .env.example delete mode 100644 .github/workflows/e2e.yml delete mode 100644 .github/workflows/gitflow.yml delete mode 100644 DESIGN_SYSTEM_PIN.json create mode 100644 apps/redrob-query/Cargo.toml create mode 100644 apps/redrob-query/build.rs create mode 100644 apps/redrob-query/src/account.rs create mode 100644 apps/redrob-query/src/ai_dock.rs create mode 100644 apps/redrob-query/src/app.rs create mode 100644 apps/redrob-query/src/changes.rs create mode 100644 apps/redrob-query/src/editor.rs create mode 100644 apps/redrob-query/src/grid.rs create mode 100644 apps/redrob-query/src/highlight.rs create mode 100644 apps/redrob-query/src/main.rs create mode 100644 apps/redrob-query/src/mongo.rs create mode 100644 apps/redrob-query/src/profiles.rs create mode 100644 apps/redrob-query/src/schema.rs create mode 100644 apps/redrob-query/src/services.rs create mode 100644 apps/redrob-query/src/structure.rs create mode 100644 apps/redrob-query/src/theme.rs create mode 100644 apps/redrob-query/src/workspace.rs create mode 100644 brand/SOURCES.json delete mode 100644 crates/redrob-core/Cargo.toml delete mode 100644 crates/redrob-core/src/ai.rs create mode 100644 crates/redrob-query-core/Cargo.toml create mode 100644 crates/redrob-query-core/src/account.rs create mode 100644 crates/redrob-query-core/src/ai.rs rename crates/{redrob-core => redrob-query-core}/src/cell_edits.rs (100%) rename crates/{redrob-core => redrob-query-core}/src/connection.rs (99%) rename crates/{redrob-core => redrob-query-core}/src/ddl.rs (100%) rename crates/{redrob-core => redrob-query-core}/src/error.rs (93%) create mode 100644 crates/redrob-query-core/src/keys.rs rename crates/{redrob-core => redrob-query-core}/src/lib.rs (50%) rename crates/{redrob-core => redrob-query-core}/src/models.rs (100%) rename crates/{redrob-core => redrob-query-core}/src/profile_store.rs (100%) rename crates/{redrob-core => redrob-query-core}/src/query.rs (100%) rename crates/{redrob-core => redrob-query-core}/src/secret.rs (100%) create mode 100644 crates/redrob-query-core/src/workspace.rs delete mode 100644 docs/DEMO.md delete mode 100644 e2e/run.mjs delete mode 100644 e2e/seed.sql delete mode 100644 index.html create mode 100644 installer/macos/dmg_settings.py create mode 100644 installer/macos/make-dmg.sh create mode 100644 installer/macos/res/background.html create mode 100644 installer/macos/res/background.png create mode 100644 installer/macos/res/background@2x.png create mode 100644 installer/macos/res/icon.png create mode 100644 installer/macos/uninstaller/uninstall.sh create mode 100644 installer/windows/README.md create mode 100644 installer/windows/build.py create mode 100644 installer/windows/redrob_query_setup.cpp create mode 100644 installer/windows/redrob_query_setup.manifest create mode 100644 installer/windows/redrob_query_setup.rc create mode 100644 installer/windows/res/background.html create mode 100644 installer/windows/res/background.png create mode 100644 installer/windows/res/icon.png create mode 100644 installer/windows/res/lockup.png create mode 100644 installer/windows/resource.h create mode 100644 installer/windows/verify-install.ps1 delete mode 100644 package-lock.json delete mode 100644 package.json create mode 100644 resources/brand/redrob-query-lockup-white.png create mode 100644 resources/brand/redrob-query-lockup.png create mode 100644 resources/brand/redrob-query-splash.png create mode 100644 resources/icons/hicolor/128x128/apps/redrob-query.png create mode 100644 resources/icons/hicolor/16x16/apps/redrob-query.png create mode 100644 resources/icons/hicolor/256x256/apps/redrob-query.png create mode 100644 resources/icons/hicolor/32x32/apps/redrob-query.png create mode 100644 resources/icons/hicolor/48x48/apps/redrob-query.png create mode 100644 resources/icons/hicolor/512x512/apps/redrob-query.png create mode 100644 resources/icons/hicolor/64x64/apps/redrob-query.png create mode 100644 resources/icons/redrob-query-small.svg create mode 100644 resources/icons/redrob-query.icns create mode 100644 resources/icons/redrob-query.ico create mode 100644 resources/icons/redrob-query.svg create mode 100644 rustfmt.toml delete mode 100644 scripts/build-app-icon.py create mode 100755 scripts/build-brand-assets.py create mode 100755 scripts/check-brand.py delete mode 100644 scripts/check-compatibility-matrix.mjs delete mode 100644 scripts/check-design-system.mjs delete mode 100644 scripts/check-design-system.verify.py delete mode 100644 scripts/check-upstream-paths.mjs delete mode 100644 scripts/fix-danger-vs-brand.py delete mode 100644 scripts/ink-on-ground.mjs delete mode 100644 scripts/install-plugin-assets.mjs delete mode 100644 scripts/launcher-asset-name.mjs delete mode 100644 scripts/launcher-asset-name.test.ts delete mode 100644 scripts/prepare-release-config.mjs delete mode 100644 scripts/release-matrix.mjs delete mode 100644 scripts/resign-linux-appimage.sh delete mode 100644 scripts/stage-release-assets.mjs delete mode 100644 scripts/stage-release-assets.test.ts delete mode 100644 scripts/strip-appimage-wayland.sh delete mode 100644 scripts/swap-icons.py delete mode 100644 scripts/swap-loader.py delete mode 100644 scripts/tokenize-css.py delete mode 100644 scripts/validate-release.mjs delete mode 100644 scripts/verify-dialect-sql.mjs delete mode 100644 scripts/verify-updater-signature.mjs delete mode 100644 scripts/verify-upstream-sql.mjs delete mode 100644 scripts/verify-upstream.mjs delete mode 100644 scripts/write-updater-manifest.mjs delete mode 100644 scripts/write-updater-manifest.test.ts delete mode 100644 src-tauri/Cargo.toml delete mode 100644 src-tauri/build.rs delete mode 100644 src-tauri/capabilities/default.json delete mode 100644 src-tauri/gen/schemas/acl-manifests.json delete mode 100644 src-tauri/gen/schemas/capabilities.json delete mode 100644 src-tauri/gen/schemas/desktop-schema.json delete mode 100644 src-tauri/gen/schemas/linux-schema.json delete mode 100644 src-tauri/icons/128x128.png delete mode 100644 src-tauri/icons/128x128@2x.png delete mode 100644 src-tauri/icons/32x32.png delete mode 100644 src-tauri/icons/64x64.png delete mode 100644 src-tauri/icons/Square107x107Logo.png delete mode 100644 src-tauri/icons/Square142x142Logo.png delete mode 100644 src-tauri/icons/Square150x150Logo.png delete mode 100644 src-tauri/icons/Square284x284Logo.png delete mode 100644 src-tauri/icons/Square30x30Logo.png delete mode 100644 src-tauri/icons/Square310x310Logo.png delete mode 100644 src-tauri/icons/Square44x44Logo.png delete mode 100644 src-tauri/icons/Square71x71Logo.png delete mode 100644 src-tauri/icons/Square89x89Logo.png delete mode 100644 src-tauri/icons/StoreLogo.png delete mode 100644 src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 src-tauri/icons/android/mipmap-hdpi/ic_launcher.png delete mode 100644 src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png delete mode 100644 src-tauri/icons/android/mipmap-mdpi/ic_launcher.png delete mode 100644 src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png delete mode 100644 src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png delete mode 100644 src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png delete mode 100644 src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 src-tauri/icons/android/values/ic_launcher_background.xml delete mode 100644 src-tauri/icons/app-icon-small.svg delete mode 100644 src-tauri/icons/app-icon.png delete mode 100644 src-tauri/icons/app-icon.svg delete mode 100644 src-tauri/icons/icon.icns delete mode 100644 src-tauri/icons/icon.ico delete mode 100644 src-tauri/icons/icon.png delete mode 100644 src-tauri/icons/ios/AppIcon-20x20@1x.png delete mode 100644 src-tauri/icons/ios/AppIcon-20x20@2x-1.png delete mode 100644 src-tauri/icons/ios/AppIcon-20x20@2x.png delete mode 100644 src-tauri/icons/ios/AppIcon-20x20@3x.png delete mode 100644 src-tauri/icons/ios/AppIcon-29x29@1x.png delete mode 100644 src-tauri/icons/ios/AppIcon-29x29@2x-1.png delete mode 100644 src-tauri/icons/ios/AppIcon-29x29@2x.png delete mode 100644 src-tauri/icons/ios/AppIcon-29x29@3x.png delete mode 100644 src-tauri/icons/ios/AppIcon-40x40@1x.png delete mode 100644 src-tauri/icons/ios/AppIcon-40x40@2x-1.png delete mode 100644 src-tauri/icons/ios/AppIcon-40x40@2x.png delete mode 100644 src-tauri/icons/ios/AppIcon-40x40@3x.png delete mode 100644 src-tauri/icons/ios/AppIcon-512@2x.png delete mode 100644 src-tauri/icons/ios/AppIcon-60x60@2x.png delete mode 100644 src-tauri/icons/ios/AppIcon-60x60@3x.png delete mode 100644 src-tauri/icons/ios/AppIcon-76x76@1x.png delete mode 100644 src-tauri/icons/ios/AppIcon-76x76@2x.png delete mode 100644 src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png delete mode 100644 src-tauri/src/commands.rs delete mode 100644 src-tauri/src/lib.rs delete mode 100644 src-tauri/src/main.rs delete mode 100644 src-tauri/tauri.conf.json delete mode 100644 src/App.test.tsx delete mode 100644 src/App.tsx delete mode 100644 src/api/bridge.test.ts delete mode 100644 src/api/bridge.ts delete mode 100644 src/api/editSource.test.ts delete mode 100644 src/api/editSource.ts delete mode 100644 src/assets/brand/redrob-symbol-solid-white.png delete mode 100644 src/assets/brand/redrob-symbol.png delete mode 100644 src/components/ActivityRail.tsx delete mode 100644 src/components/AiPanel.tsx delete mode 100644 src/components/AiSettingsModal.tsx delete mode 100644 src/components/ChangesPanel.tsx delete mode 100644 src/components/CommandPalette.tsx delete mode 100644 src/components/ConnectionModal.tsx delete mode 100644 src/components/IconButton.tsx delete mode 100644 src/components/Navigator.select.test.ts delete mode 100644 src/components/Navigator.tsx delete mode 100644 src/components/QueryEditor.tsx delete mode 100644 src/components/ResultGrid.test.ts delete mode 100644 src/components/ResultGrid.tsx delete mode 100644 src/components/StatusBar.tsx delete mode 100644 src/components/StructurePanel.test.tsx delete mode 100644 src/components/StructurePanel.tsx delete mode 100644 src/components/TableChangeModal.tsx delete mode 100644 src/components/rowExport.test.ts delete mode 100644 src/components/rowExport.ts delete mode 100644 src/domain/types.ts delete mode 100644 src/main.tsx delete mode 100644 src/plugins/PluginView.test.tsx delete mode 100644 src/plugins/PluginView.tsx delete mode 100644 src/plugins/PluginWorkspace.test.tsx delete mode 100644 src/plugins/PluginWorkspace.tsx delete mode 100644 src/plugins/host.test.ts delete mode 100644 src/plugins/host.ts delete mode 100644 src/plugins/hostKeys.test.ts delete mode 100644 src/plugins/manifest.test.ts delete mode 100644 src/plugins/manifest.ts delete mode 100644 src/plugins/protocol.ts delete mode 100644 src/plugins/registry.ts delete mode 100644 src/plugins/shell.test.tsx delete mode 100644 src/plugins/storageShim.test.ts delete mode 100644 src/schema/mysqlKeys.ts delete mode 100644 src/schema/postgresKeys.ts delete mode 100644 src/schema/schemaKeys.test.ts delete mode 100644 src/schema/schemaKeys.ts delete mode 100644 src/schema/sqlIdentifiers.ts delete mode 100644 src/shortcuts.test.ts delete mode 100644 src/shortcuts.ts delete mode 100644 src/store/WorkspaceProvider.tsx delete mode 100644 src/store/workspacePersistence.ts delete mode 100644 src/store/workspaceStore.test.ts delete mode 100644 src/store/workspaceStore.ts delete mode 100644 src/styles/app.css delete mode 100644 src/test/design-system-styles.stub.css delete mode 100644 src/test/setup.ts delete mode 100644 src/ui/Icon.tsx delete mode 100644 src/ui/useGround.ts delete mode 100644 src/vite-env.d.ts create mode 100644 tools/release/make_deb.py create mode 100644 tools/release/stage.py create mode 100644 tools/release_matrix.py delete mode 100644 tsconfig.app.json delete mode 100644 tsconfig.json delete mode 100644 tsconfig.node.json delete mode 100644 vite.config.ts diff --git a/.env.example b/.env.example deleted file mode 100644 index 0812b70..0000000 --- a/.env.example +++ /dev/null @@ -1,15 +0,0 @@ -# There is no Redrob console key here: the shared Redrob Code daemon holds it and -# desktop AI reaches the console only through that daemon (see README, "Redrob AI -# configuration"). REDROB_RUN_DIR and REDROB_CODE_BIN point tests at a private daemon. - -# Three live connector tests are #[ignore] by default. Cargo does not load this -# file automatically: export disposable URLs in the shell, then run tests with -# `cargo test --locked -p redrob-core --all-features -- --ignored`. -# An explicitly run gate fails when its required URL is absent. -REDROB_TEST_POSTGRES_URL= -REDROB_TEST_MYSQL_URL= - -# The Mongo gate creates and drops only one UUID-named collection. Its database -# defaults to redrob_test when REDROB_TEST_MONGO_DATABASE is unset. -REDROB_TEST_MONGO_URL= -REDROB_TEST_MONGO_DATABASE=redrob_test diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 38f1022..d493011 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,18 +13,18 @@ ``` -npm run release:check -npm test -npm run build -npm run rust:fmt && npm run rust:clippy && npm run rust:test +python3 scripts/check-brand.py +cargo fmt --all -- --check +cargo clippy -p redrob-query -p redrob-query-core --all-targets -- -D warnings +cargo test -p redrob-query-core ``` ## Checklist - [ ] No credentials, connection strings, or real database contents in the diff, the tests, or the commit messages. -- [ ] Read-only guarantees preserved: no new desktop/native mutation command is - registered, and saved desktop profiles stay read-only. +- [ ] Read-only guarantees preserved: typed queries still go through the engine's + read-only gate, and writes only through reviewed edits and the structure forms. - [ ] If local-storage behaviour changed, `docs/SECURITY.md` says what is now stored. - [ ] If user-visible strings changed, they name Redrob Query — not DBeaver. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b797290..415068f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,159 +1,78 @@ +# The one pull-request workflow. `checks` runs the repository scripts: the brand-asset pins. `rust` +# runs formatting, clippy and the tests of OUR crates (the shell `redrob-query` and the engine +# `redrob-query-core`) with `--profile ci` (root Cargo.toml: every dependency at opt-level 0, no +# incremental), and is skipped when no Rust input changed or when the pull request targets main (that +# code already passed here on its way into develop). The release workflow does not repeat any of this; +# PR policy is the org-wide workflow. Adapted from redrob-canvas. name: CI -# Plain pull_request (never pull_request_target) and no secrets are consumed, -# so a pull request from a fork receives no credential of any kind. on: pull_request: - push: - branches: [main, develop] workflow_dispatch: permissions: contents: read concurrency: - group: ci-${{ github.event.pull_request.number || github.ref }} + group: ci-${{ github.ref }} cancel-in-progress: true +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + OUR_CRATES: -p redrob-query -p redrob-query-core + jobs: - frontend: - name: Release metadata, types, tests, build - runs-on: ubuntu-24.04 - timeout-minutes: 20 + checks: + name: brand assets + runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Set up Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: 22 - cache: npm - - - name: Install locked JavaScript dependencies - run: npm ci - - # Asserts productName, identifier, licence and the version fan-out across - # package.json, Cargo.toml, tauri.conf.json and package-lock.json. - - name: Validate release metadata - run: npm run release:check - - # This job runs the checks individually rather than `npm run check`, so a guard wired only into - # that script would never run where the change is reviewed. Fails on a raw colour literal, a - # lucide import in source or in the manifest, a brand file whose hash moved, and a font-family - # that is not a system token. - - name: Check the design system - run: npm run check:design - - - name: Typecheck - run: npm run typecheck - - - name: Test - run: npm test - - - name: Build - run: npm run build + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.3.1 + - name: Brand assets match the design system pins + run: python3 scripts/check-brand.py rust: - name: Format, clippy, tests - runs-on: ubuntu-24.04 - timeout-minutes: 30 + name: fmt, clippy, test (profile ci) + # A release PR into main carries code that already passed here on its way into develop. + if: github.base_ref != 'main' + runs-on: ubuntu-latest + timeout-minutes: 90 steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - # The Tauri shell links WebKitGTK; the redrob-core crate deliberately does not. - - name: Install Linux desktop dependencies - run: >- - sudo apt-get update && sudo apt-get install -y - libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev - libsoup-3.0-dev librsvg2-dev patchelf libssl-dev - - # Channel and components must match rust-toolchain.toml, which - # scripts/validate-release.mjs pins against Cargo rust-version. - - name: Set up Rust - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.3.1 + - name: Did any Rust input change? + id: rs + run: | + git fetch -q origin "${{ github.base_ref }}" --depth=1 + if git diff --quiet "origin/${{ github.base_ref }}"...HEAD -- '*.rs' '*.toml' Cargo.lock '*.json' .github/workflows/ci.yml; then + echo changed=false >> "$GITHUB_OUTPUT"; else echo changed=true >> "$GITHUB_OUTPUT"; fi + - name: System libraries for the desktop app + if: steps.rs.outputs.changed == 'true' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev \ + libgl1-mesa-dev libgtk-3-dev libdbus-1-dev + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + if: steps.rs.outputs.changed == 'true' with: - toolchain: 1.94.0 + toolchain: "1.95.0" components: rustfmt, clippy - - - name: Format - run: npm run rust:fmt - - - name: Clippy - run: npm run rust:clippy - - - name: Test - run: npm run rust:test - - # The live PostgreSQL and MySQL tests in redrob-core are #[ignore]d because they need a real - # server, and until this job nothing ever ran them: a write path checked only against SQLite and a - # hand-copied query is the gap the plan calls out. Service containers give each run fresh servers; - # only the core crate is built, so the WebKitGTK packages the shell needs are not installed here. - rust-live-databases: - name: Live PostgreSQL and MySQL tests - runs-on: ubuntu-24.04 - timeout-minutes: 30 - services: - postgres: - image: postgres:16.4 - env: - POSTGRES_PASSWORD: redrob - POSTGRES_DB: redrob - ports: ["5432:5432"] - options: >- - --health-cmd "pg_isready -U postgres" --health-interval 5s --health-timeout 5s --health-retries 20 - mysql: - image: mysql:8.4.2 - env: - MYSQL_ROOT_PASSWORD: redrob - MYSQL_DATABASE: redrob - ports: ["3306:3306"] - options: >- - --health-cmd "mysqladmin ping -h 127.0.0.1 -predrob" --health-interval 5s --health-timeout 5s --health-retries 30 - env: - # Throwaway credentials for the per-run service containers above; nothing else accepts them. - REDROB_TEST_POSTGRES_URL: postgres://postgres:redrob@127.0.0.1:5432/redrob - REDROB_TEST_MYSQL_URL: mysql://root:redrob@127.0.0.1:3306/redrob - steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Set up Rust - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + - uses: Swatinem/rust-cache@v2 + if: steps.rs.outputs.changed == 'true' with: - toolchain: 1.94.0 - - - name: Live database tests - run: cargo test --locked -p redrob-core -- --ignored --exact --nocapture - connection::tests::live_postgres_native_scalars_when_configured - connection::tests::live_mysql_native_scalars_when_configured - connection::tests::live_postgres_cell_edits_commit_together_or_not_at_all - connection::tests::live_mysql_cell_edits_commit_together_or_not_at_all - connection::tests::live_postgres_table_changes_create_and_extend_a_table - connection::tests::live_mysql_table_changes_create_and_extend_a_table - - # This job exists because its absence cost a failed release. The rustls advisory - # RUSTSEC-2026-0285 sat in Cargo.lock through every green pull request, because the - # only place cargo audit ran was the release workflow -- so the first thing that - # noticed was a tag. A dependency advisory should fail the pull request that could - # have fixed it, not the release that cannot. - rust-advisories: - name: Rust security advisories - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Set up Rust for security audit - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - with: - toolchain: stable - - - name: Install pinned cargo-audit - run: cargo +stable install cargo-audit --version 0.22.2 --locked - - # The Cargo workspace is at the repository root, not under src-tauri. - - name: Audit locked Rust dependencies - run: cargo +stable audit --file Cargo.lock + key: ci-profile + - name: Formatting + if: steps.rs.outputs.changed == 'true' + run: cargo fmt --all -- --check + - name: Clippy + if: steps.rs.outputs.changed == 'true' + run: | + # shellcheck disable=SC2086 # OUR_CRATES is a list of flags on purpose + cargo clippy $OUR_CRATES --all-targets --locked --profile ci -- -D warnings + - name: Tests + if: steps.rs.outputs.changed == 'true' + run: | + # shellcheck disable=SC2086 + cargo test $OUR_CRATES --locked --profile ci diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 3a52134..0000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: E2E - -# The desktop app, driven the way a user drives it: add a SQLite connection, query a table, edit a -# cell, apply, run again. Unit and live-server tests run the core; this is the only check that the -# window, the bridge and the core write a change together -- and the last step reads the change -# back from the database file itself, not from the screen. - -on: - pull_request: - push: - branches: [develop, main] - -permissions: - contents: read - -jobs: - desktop: - name: Desktop edit and apply over WebDriver - runs-on: ubuntu-24.04 - timeout-minutes: 45 - steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Install Linux desktop and WebDriver dependencies - run: >- - sudo apt-get update && sudo apt-get install -y - libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev librsvg2-dev - patchelf libssl-dev webkit2gtk-driver xvfb sqlite3 - - - name: Set up Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: 22 - cache: npm - - - name: Set up Rust - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - with: - toolchain: 1.94.0 - - - name: Install pinned tauri-driver - run: cargo install tauri-driver --version 2.1.0 --locked - - - name: Install locked JavaScript dependencies - run: npm ci - - - name: Build the debug app - run: npx tauri build --debug --no-bundle - - - name: Seed the database - run: sqlite3 "$RUNNER_TEMP/people.db" < e2e/seed.sql - - - name: Drive the desktop app - env: - APP: ${{ github.workspace }}/target/debug/redrob-query - REDROB_E2E_DB: ${{ runner.temp }}/people.db - SHOTS: ${{ github.workspace }}/e2e/screenshots - # A home of its own, so saved connections and the keyring fallback start empty. - HOME: ${{ runner.temp }}/e2e-home - # Software rendering on a virtual display; the DMA-BUF path needs a GPU the runner lacks. - WEBKIT_DISABLE_DMABUF_RENDERER: "1" - run: | - mkdir -p "$HOME" - xvfb-run --auto-servernum --server-args="-screen 0 1280x800x24" bash -c ' - tauri-driver --port 4444 > e2e/tauri-driver.log 2>&1 & - driver=$! - for _ in $(seq 1 50); do curl -fs http://127.0.0.1:4444/status >/dev/null && break; sleep 0.2; done - node e2e/run.mjs - status=$? - kill $driver - exit $status - ' - - - name: The changes are in the database file - run: | - name=$(sqlite3 "$RUNNER_TEMP/people.db" "SELECT name FROM people WHERE id = 1") - added=$(sqlite3 "$RUNNER_TEMP/people.db" "SELECT id || ':' || name || ':' || plan FROM people WHERE id <> 1") - rows=$(sqlite3 "$RUNNER_TEMP/people.db" "SELECT count(*) FROM people") - echo "row 1: $name / added: $added / rows: $rows" - test "$name" = "Ann Lee-Kim" && test "$rows" = "2" && test "$added" = "2:Cy:Free" - pets=$(sqlite3 "$RUNNER_TEMP/people.db" "SELECT group_concat(name || ' ' || type || ' ' || pk, ', ') FROM pragma_table_info('pets')") - echo "pets: $pets" - test "$pets" = "id INTEGER 1, name TEXT 0, born DATE 0" - - - name: Keep the screenshots and driver log - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: e2e-desktop - path: | - e2e/screenshots - e2e/tauri-driver.log - if-no-files-found: warn diff --git a/.github/workflows/gitflow.yml b/.github/workflows/gitflow.yml deleted file mode 100644 index 8c75c9c..0000000 --- a/.github/workflows/gitflow.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Gitflow rules shared by every redrob-labs repository: branch names are / from the -# org-wide type list, and main is contained in develop (a back-merge pull request is opened when -# it is not). The rules live in redrob-labs/.github/.github/workflows/gitflow.yml; this file only -# calls them. Do not add a type list here -- there is one, and it is there. - -name: gitflow - -on: - pull_request: - types: [opened, reopened, edited, synchronize] - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - gitflow: - uses: redrob-labs/.github/.github/workflows/gitflow.yml@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67e68dd..3092a38 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,163 +1,185 @@ -name: Signed desktop release +name: Release -# The redrob-labs release shape (redrob-labs/.github workflow-templates/release.yml): +# The ONE release path: every platform is built on GitHub runners from the single cargo binary +# `redrob-query` (apps/redrob-query), Windows is signed through Azure Artifact Signing with the +# job's OpenID Connect login, macOS is signed and notarized with the organization's Developer ID, +# and the shared redrob-labs publish job creates the GitHub Release. # -# tag v on main -> version -> build matrix (build, then sign) -> updater manifest -> publish +# gh workflow run release.yml --ref main -f tag=vX.Y.Z # the release: PUBLISHED as latest +# gh workflow run release.yml --ref -f dry_run=true # builds and signs; publishes nothing # -# The parts every repository shares are not in this file; they are called: -# - redrob-labs/.github/actions/sign-windows/sign.ps1 Authenticode through Azure Artifact Signing, -# run by tauri as `bundle.windows.signCommand` -# - redrob-labs/.github/.github/workflows/release-publish.yml checksums, launcher manifest, the release +# The parts every redrob-labs repository shares are not in this file; they are called: +# - redrob-labs/.github/actions/sign-windows Authenticode through Azure Artifact Signing +# - redrob-labs/.github/.github/workflows/release-publish.yml checksums, launcher manifest, release # -# Rules the shape encodes: -# - the trigger is the tag push; `workflow_dispatch` with a version is a DRY RUN -- it builds, signs and -# verifies every leg and publishes nothing; -# - Windows: compile, THEN `azure/login` (OIDC), THEN bundle -- tauri signs the executable and both -# installers during bundling, through the shared script. The login token is short-lived and the compile -# is long; never the other way round, never a client secret or a certificate file; -# - the jobs that sign run in the `release` environment with `id-token: write`; -# - the publish job runs only on a tag and only when every build job succeeded, and publishes the -# release as latest (as a prerelease when the version carries a `-`). +# Rules this file encodes: +# - Windows: build, THEN `azure/login` (OIDC), THEN sign -- the login token is short-lived and the +# build is long; there is no certificate file and no client secret anywhere. The executable is +# signed BEFORE it is zipped and before the setup embeds that zip; the uninstaller and the setup +# are signed in their turn. `release` is the environment the organization's federated credential +# names; any other name is refused. +# - validate is the repository script plus `cargo fmt --check`; clippy and the tests ran on the +# pull request (.github/workflows/ci.yml) and are not repeated here; +# - the publish job runs only for a real tag, and only when every job before it succeeded; +# - the release is published as latest unless the version carries a pre-release suffix. # -# Everything a leg uploads is a `release-*` artifact under its FINAL name; the publish job ships those -# bytes unchanged. latest.json, the Tauri updater feed, is one of them (the updater-manifest job). - +# There is no Node, no Tauri and no WebKitGTK here any more: 0.3.0 replaced the Tauri + React shell with an +# egui shell, so a leg is `cargo build --release -p redrob-query --target ` and a packaging +# script. The Linux leg needs the X11/Wayland/GL development headers egui, wgpu and rfd link against. +# +# Exactly THREE targets (tools/release_matrix.py is the list): +# macOS arm64 redrob-query--macos-arm64.zip + .dmg (Developer ID, notarized, stapled) +# Linux x86_64 redrob-query--linux-x86_64.tar.gz + .deb +# Windows x64 redrob-query--windows-x64-portable.zip + -setup.exe (Azure Artifact Signing) on: - push: - tags: ["v*.*.*"] workflow_dispatch: inputs: - version: - description: Version for a dry run, e.g. 0.3.0-1 (builds and signs every leg, publishes nothing) + tag: + description: "Release tag, vMAJOR.MINOR.PATCH, already pushed and on main (empty for a dry run)" + required: false type: string - required: true + default: "" + dry_run: + description: "Dry run: build and sign the dispatched ref, publish nothing" + required: false + type: boolean + default: false permissions: contents: read concurrency: - group: release-${{ github.ref }} + group: release-${{ inputs.tag != '' && inputs.tag || github.ref }} cancel-in-progress: false env: PRODUCT: Redrob Query PRODUCT_URL: https://github.com/${{ github.repository }} + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 jobs: - version: - name: Resolve the version and verify the tree + validate: + name: Validate release tag runs-on: ubuntu-24.04 - timeout-minutes: 45 + timeout-minutes: 60 outputs: - version: ${{ steps.v.outputs.version }} - # The build matrix and the updater platform keys the manifest must carry, both derived from - # the single list in scripts/release-matrix.mjs. + # The version without the leading v, the commit every later job builds, and whether this is a + # dry run. For a tag the commit is the tag's; for a dry run it is the dispatched ref's. + version: ${{ steps.version.outputs.version }} + sha: ${{ steps.version.outputs.sha }} + dry_run: ${{ steps.resolve.outputs.dry_run }} + # The build matrix, the platform assets the release must carry, and the platform list the + # release notes promise -- all from the single list in tools/release_matrix.py. matrix: ${{ steps.release-matrix.outputs.matrix }} - platforms: ${{ steps.release-matrix.outputs.platforms }} - asset_suffixes: ${{ steps.release-matrix.outputs.asset_suffixes }} + archive_suffixes: ${{ steps.release-matrix.outputs.archive_suffixes }} + installer_suffixes: ${{ steps.release-matrix.outputs.installer_suffixes }} + notes_platforms: ${{ steps.release-matrix.outputs.notes_platforms }} windows_signing_ready: ${{ steps.release-matrix.outputs.windows_signing_ready }} steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - # A tag names the version the sources declare, or it is refused: the installers, the updater - # payloads and latest.json all carry package.json's version, and a tag that disagrees would - # publish v0.3.0 whose every file says 0.2.4. A dispatch names any well-formed version for the - # dry run, and scripts/prepare-release-config.mjs stamps it onto the build. - - name: Resolve the version - id: v + # A dry run builds whatever was dispatched; a release builds the tag it names. Resolved + # before the checkout so the checkout can take the result. + - name: Resolve what to build + id: resolve env: - INPUT_VERSION: ${{ inputs.version }} + INPUT_TAG: ${{ inputs.tag }} + INPUT_DRY_RUN: ${{ inputs.dry_run }} run: | set -euo pipefail - declared="$(node -p 'require("./package.json").version')" - if [ "${GITHUB_REF_TYPE:-}" = tag ]; then - v="${GITHUB_REF_NAME#v}" - if [ "$v" != "$declared" ]; then - echo "::error::tag ${GITHUB_REF_NAME} does not match the package.json version ${declared}" - exit 1 - fi - elif [ -n "$INPUT_VERSION" ]; then - v="${INPUT_VERSION#v}" + if [ "$INPUT_DRY_RUN" = "true" ]; then + echo "dry_run=true" >> "$GITHUB_OUTPUT" + echo "ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT" + echo "::notice::dry run of $GITHUB_REF_NAME ($GITHUB_SHA): builds and signs, publishes nothing" + elif [ -n "$INPUT_TAG" ]; then + [[ "$INPUT_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]] \ + || { echo "::error::not a release tag: $INPUT_TAG"; exit 1; } + echo "dry_run=false" >> "$GITHUB_OUTPUT" + echo "ref=refs/tags/$INPUT_TAG" >> "$GITHUB_OUTPUT" else - echo "::error::not a tag push and no version input" - exit 1 - fi - # tauri's MSI bundler maps a pre-release identifier onto the fourth Windows version - # field, so it must be a plain number no greater than 65535 (`0.3.0-1`, not `0.3.0-rc.1`); - # the bundler would refuse `rc.1` two jobs and forty minutes later. - if ! printf '%s' "$v" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]{0,4}))?$'; then - echo "::error::'$v' is not a version like 1.2.3 or 1.2.3-1 (a pre-release identifier must be numeric for the MSI bundler)" - exit 1 - fi - echo "version=$v" >> "$GITHUB_OUTPUT" - { - echo "### ${PRODUCT} v$v (\`${GITHUB_SHA::9}\`)" - [ "${GITHUB_REF_TYPE:-}" = tag ] || echo "Dry run: nothing is published." - } >> "$GITHUB_STEP_SUMMARY" - - - name: Require release tag commit on main - if: github.ref_type == 'tag' - shell: bash - run: | - git fetch --no-tags origin main:refs/remotes/origin/main - if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then - echo "::error::Release tag commit must be reachable from origin/main" + echo "::error::give a tag (the release) or set dry_run (a rehearsal); neither was given" exit 1 fi - - name: Install Linux desktop dependencies - run: >- - sudo apt-get update && sudo apt-get install -y - libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev librsvg2-dev - patchelf libssl-dev - - name: Set up Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.3.1 with: - node-version: 22 - cache: npm + ref: ${{ steps.resolve.outputs.ref }} + fetch-depth: 0 + # Decides which platforms this release builds. Windows is omitted, with a notice in the # run log, until the repository variable WINDOWS_SIGNING_READY is "true" -- see the - # script for why an unsigned Windows installer is not the fallback. + # script for why an unsigned Windows executable is not the fallback. - name: Resolve the release matrix id: release-matrix env: WINDOWS_SIGNING_READY: ${{ vars.WINDOWS_SIGNING_READY }} - run: node scripts/release-matrix.mjs - - name: Set up Rust - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - with: - toolchain: 1.94.0 - components: rustfmt, clippy - - name: Install locked dependencies - run: npm ci - - name: Run complete verification - run: npm run check - - rust-advisories: - name: Rust security advisories - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Set up Rust for security audit + run: python3 tools/release_matrix.py + + # The packaging step derives the version from the TAG, while the binary reports the one in + # Cargo.toml. Nothing made the two agree, so a tag could ship archives named one version + # holding a binary that calls itself another. Checked here, before anything is built. + # A dry run has no tag: it takes the crate version as it is and skips the tag checks. + - name: Require the tag on main, matching the workspace version and the changelog + id: version + env: + RELEASE_TAG: ${{ inputs.tag }} + DRY_RUN: ${{ steps.resolve.outputs.dry_run }} + shell: bash + run: | + set -euo pipefail + crate_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n1)" + test -n "$crate_version" + echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + if [ "$DRY_RUN" = "true" ]; then + echo "version=${crate_version}" >> "$GITHUB_OUTPUT" + echo "dry run: building Cargo.toml version ${crate_version} at $(git rev-parse --short HEAD)" + exit 0 + fi + git fetch --no-tags origin main:refs/remotes/origin/main + if ! git merge-base --is-ancestor HEAD origin/main; then + echo "::error::Release tag commit must be reachable from origin/main" + exit 1 + fi + tag_version="${RELEASE_TAG#v}" + if [ "$tag_version" != "$crate_version" ]; then + echo "::error::Tag v${tag_version} does not match Cargo.toml version ${crate_version}" + exit 1 + fi + # The release notes say "See CHANGELOG", so a release whose version has no entry there + # ships notes that describe nothing. + if ! grep -q "^## ${tag_version} " CHANGELOG.md; then + echo "::error::CHANGELOG.md has no '## ${tag_version}' section" + exit 1 + fi + if grep -q "^## ${tag_version} - unreleased" CHANGELOG.md; then + echo "::error::CHANGELOG.md still marks ${tag_version} as unreleased" + exit 1 + fi + echo "version=${tag_version}" >> "$GITHUB_OUTPUT" + + # The repository script, as the CI `checks` job runs it: the brand-asset pins. + - name: Brand assets match the design system pins + run: python3 scripts/check-brand.py + + - name: Rust toolchain (rust-toolchain.toml) uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable with: - toolchain: stable - - name: Install pinned cargo-audit - run: cargo +stable install cargo-audit --version 0.22.2 --locked - # This repository's Cargo workspace is at the root, not under src-tauri. - - name: Audit locked Rust dependencies - run: cargo +stable audit --file Cargo.lock + toolchain: "1.95.0" + components: rustfmt + + - name: Formatting + run: | + # clippy and the tests already gated the commit on its pull request (ci.yml); a release + # re-running them cost 14 minutes per dispatch for no new information. + cargo fmt --all -- --check build: name: Build ${{ matrix.label }} - needs: [version, rust-advisories] + needs: [validate] + runs-on: ${{ matrix.runner }} + timeout-minutes: 120 # `release` is the environment the organization's federated credential names - # (repo:redrob-labs/:environment:release); the macOS and Linux legs run in it too so one - # set of protection rules covers every leg that produces a release file. + # (repo:redrob-labs/:environment:release); any other name is refused by Azure. environment: release permissions: contents: read @@ -165,92 +187,43 @@ jobs: id-token: write strategy: fail-fast: false - max-parallel: 1 matrix: - # Generated by scripts/release-matrix.mjs in the version job, so the Windows leg can be - # excluded while Authenticode signing is not configured, and so the updater platform - # keys the manifest job requires come from the same single list. - include: ${{ fromJSON(needs.version.outputs.matrix) }} - runs-on: ${{ matrix.platform }} - timeout-minutes: 90 + # Generated by tools/release_matrix.py in the validate job, so the Windows leg can be + # excluded while Authenticode signing is not configured, and so the asset names the + # verify job demands and the platform list the release notes promise come from that + # same single list. + include: ${{ fromJSON(needs.validate.outputs.matrix) }} env: - RELEASE_VERSION: ${{ needs.version.outputs.version }} - RELEASE_TARGET: ${{ matrix.target }} + VERSION: ${{ needs.validate.outputs.version }} + TARGET: ${{ matrix.target }} + # Plain IDs (not credentials); here so the login step can be skipped when signing is not set up. + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - # The one copy of the Windows signing recipe. tauri runs it as `bundle.windows.signCommand` - # (scripts/prepare-release-config.mjs points the bundler at it), so this repository carries - # no signing code of its own. - - name: Check out the shared signing recipe - if: runner.os == 'Windows' - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - repository: redrob-labs/.github - ref: main - path: .redrob-shared - - - name: Install Linux desktop dependencies - if: runner.os == 'Linux' - # desktop-file-utils: the AppImage repack step below runs appimagetool, which refuses to - # pack without `desktop-file-validate` on PATH (measured on the v0.2.1 Linux leg). - run: >- - sudo apt-get update && sudo apt-get install -y - libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev librsvg2-dev - patchelf libssl-dev desktop-file-utils - - - name: Set up Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.3.1 with: - node-version: 22 - cache: npm + ref: ${{ needs.validate.outputs.sha }} - - name: Set up Rust + - name: Rust toolchain (rust-toolchain.toml) uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable with: - toolchain: 1.94.0 + toolchain: "1.95.0" targets: ${{ matrix.target }} - - name: Install locked JavaScript dependencies - run: npm ci - - - name: Require release signing material - shell: bash - env: - TAURI_UPDATER_PUBLIC_KEY: ${{ vars.TAURI_SIGNING_PUBLIC_KEY }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - run: | - test -n "$TAURI_UPDATER_PUBLIC_KEY" - test -n "$TAURI_SIGNING_PRIVATE_KEY" - test -n "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" + - uses: Swatinem/rust-cache@v2 + with: + key: release-${{ matrix.slug }} - - name: Require Apple signing and notarization credentials - if: runner.os == 'macOS' - shell: bash - env: - APPLE_CODESIGN_CERT_P12_BASE64: ${{ secrets.APPLE_CODESIGN_CERT_P12_BASE64 }} - APPLE_CODESIGN_CERT_PASSWORD: ${{ secrets.APPLE_CODESIGN_CERT_PASSWORD }} - APPLE_NOTARY_API_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }} - APPLE_NOTARY_API_KEY_ID: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} - APPLE_NOTARY_API_ISSUER_ID: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }} + - name: Install Linux build dependencies + if: runner.os == 'Linux' run: | - set -euo pipefail - for name in \ - APPLE_CODESIGN_CERT_P12_BASE64 \ - APPLE_CODESIGN_CERT_PASSWORD \ - APPLE_NOTARY_API_KEY_P8_BASE64 \ - APPLE_NOTARY_API_KEY_ID \ - APPLE_NOTARY_API_ISSUER_ID; do - test -n "${!name:-}" - done - printf '%s' "$APPLE_NOTARY_API_KEY_P8_BASE64" | base64 --decode > "$RUNNER_TEMP/AuthKey.p8" - chmod 600 "$RUNNER_TEMP/AuthKey.p8" - printf 'APPLE_API_KEY_PATH=%s\n' "$RUNNER_TEMP/AuthKey.p8" >> "$GITHUB_ENV" + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev \ + libgl1-mesa-dev libgtk-3-dev libdbus-1-dev dpkg-dev + # macOS: import the Developer ID certificate into a throwaway keychain and read the + # identity back out, rather than assuming its name. - name: Import Apple Developer ID certificate if: runner.os == 'macOS' shell: bash @@ -259,6 +232,8 @@ jobs: APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CODESIGN_CERT_PASSWORD }} run: | set -euo pipefail + test -n "$APPLE_CERTIFICATE" + test -n "$APPLE_CERTIFICATE_PASSWORD" KEYCHAIN_PASSWORD="$(openssl rand -hex 24)" printf '%s' "$APPLE_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/redrob-certificate.p12" security create-keychain -p "$KEYCHAIN_PASSWORD" "$RUNNER_TEMP/redrob-build.keychain" @@ -270,187 +245,361 @@ jobs: CERT_ID="$(security find-identity -v -p codesigning "$RUNNER_TEMP/redrob-build.keychain" | awk -F'"' 'NR==1 {print $2}')" test -n "$CERT_ID" printf 'APPLE_SIGNING_IDENTITY=%s\n' "$CERT_ID" >> "$GITHUB_ENV" + rm -f "$RUNNER_TEMP/redrob-certificate.p12" - # Writes src-tauri/tauri.release.conf.json: the updater key and feed, the version this run - # builds, and on Windows the shared sign.ps1 as the bundler's sign command. - - name: Prepare signed updater configuration + - name: Prepare Apple notarization key + if: runner.os == 'macOS' + shell: bash env: - TAURI_UPDATER_PUBLIC_KEY: ${{ vars.TAURI_SIGNING_PUBLIC_KEY }} - REDROB_SHARED_DIR: ${{ github.workspace }}/.redrob-shared - run: npm run release:prepare - - # Windows signs mid-bundling, so the login has to sit between the compile and the bundle: - # compile everything first (no installer yet), log in, then bundle. The second `tauri build` - # finds cargo's output up to date and goes straight to packaging and signing. - - name: Compile before signing (Windows) - if: runner.os == 'Windows' + APPLE_NOTARY_API_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }} + APPLE_NOTARY_API_KEY_ID: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} + APPLE_NOTARY_API_ISSUER_ID: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }} + run: | + set -euo pipefail + for name in APPLE_NOTARY_API_KEY_P8_BASE64 APPLE_NOTARY_API_KEY_ID APPLE_NOTARY_API_ISSUER_ID; do + test -n "${!name:-}" + done + printf '%s' "$APPLE_NOTARY_API_KEY_P8_BASE64" | base64 --decode > "$RUNNER_TEMP/AuthKey.p8" + chmod 600 "$RUNNER_TEMP/AuthKey.p8" + + - name: Build + shell: bash + env: + MACOSX_DEPLOYMENT_TARGET: "12.0" + # The Windows exe must carry its icon and version info (build.rs); a silent fallback is refused. + REDROB_QUERY_REQUIRE_WINRES: "1" + run: cargo build --release --locked -p redrob-query --target "$TARGET" + + # The tests ran on the pull request; here the binary has to at least answer --version, which + # proves it links and starts without a display. + - name: Smoke test the binary + shell: bash + run: | + set -euo pipefail + bin="target/$TARGET/release/redrob-query" + [ "$RUNNER_OS" = "Windows" ] && bin="$bin.exe" + test -x "$bin" + "$bin" --version + + # One staging tree per platform: bin/ and the licence set (LICENSE, NOTICE, + # UPSTREAM_NOTICES.md). The licence texts travel with every archive (GPL-3.0-or-later, section 4). + - name: Stage + shell: bash + run: | + set -euo pipefail + stage="$RUNNER_TEMP/stage" + python3 tools/release/stage.py --target "$TARGET" --version "$VERSION" --out "$stage" + ls -R "$stage" | head -50 + + # The version Finder, the About box and Gatekeeper show is read from the bundle after it is + # built, not trusted from the script that wrote it. + - name: Require the bundle version to match the workspace version (macOS) + if: runner.os == 'macOS' + shell: bash + run: | + set -euo pipefail + plist="$RUNNER_TEMP/stage/Redrob Query.app/Contents/Info.plist" + crate_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n1)" + for key in CFBundleShortVersionString CFBundleVersion; do + bundle_version="$(plutil -extract "$key" raw -o - "$plist")" + if [ "$bundle_version" != "$crate_version" ]; then + echo "::error::Info.plist $key is $bundle_version, Cargo.toml is $crate_version" + exit 1 + fi + done + + - name: Sign, notarize and staple (macOS) + if: runner.os == 'macOS' + shell: bash env: - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - run: npm run tauri build -- --target ${{ matrix.target }} --no-bundle --config src-tauri/tauri.release.conf.json + APPLE_NOTARY_API_KEY_ID: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} + APPLE_NOTARY_API_ISSUER_ID: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }} + run: | + set -euo pipefail + app="$RUNNER_TEMP/stage/Redrob Query.app" + + codesign --force --deep --options runtime --timestamp \ + --sign "$APPLE_SIGNING_IDENTITY" "$app" + codesign --verify --deep --strict --verbose=2 "$app" + + # notarytool takes an archive, not a bundle; ditto --keepParent preserves the + # .app directory structure that a plain zip mangles. + ditto -c -k --keepParent "$app" "$RUNNER_TEMP/notarize.zip" + xcrun notarytool submit "$RUNNER_TEMP/notarize.zip" \ + --key "$RUNNER_TEMP/AuthKey.p8" \ + --key-id "$APPLE_NOTARY_API_KEY_ID" \ + --issuer "$APPLE_NOTARY_API_ISSUER_ID" \ + --wait + + # Stapling and validating both look the ticket up in Apple's CloudKit service, and that + # lookup drops ("The network connection was lost", error 68) after notarization was + # Accepted. Each network step is retried with backoff; a real refusal fails every attempt. + retry() { + local i + for i in 1 2 3 4 5 6; do + if "$@"; then return 0; fi + [ "$i" -lt 6 ] || { echo "::error::failed after 6 attempts: $*"; return 1; } + echo "::warning::attempt $i of 6 failed: $*; retrying in $((i * 20))s" + sleep $((i * 20)) + done + } + retry xcrun stapler staple "$app" + retry xcrun stapler validate "$app" + # Gatekeeper's own verdict, which is what a user's machine actually applies. + retry spctl --assess --type execute --verbose=4 "$app" + rm -f "$RUNNER_TEMP/notarize.zip" + # The login comes after the build on purpose: the OpenID Connect token is short-lived and a + # long build left signtool without a usable credential (it then waited on a browser). - name: Log in to Azure (OIDC) - if: runner.os == 'Windows' - uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0 + if: runner.os == 'Windows' && env.AZURE_CLIENT_ID != '' + uses: azure/login@v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - # On Windows, tauri calls the shared sign.ps1 on the executable, the NSIS installer and the - # MSI as it packages them, with ARTIFACT_SIGNING_* from this environment and the login above. - # On macOS it signs with the imported Developer ID and notarizes with the API key. - - name: Build, bundle and sign - env: - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} - APPLE_API_KEY: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }} - APPLE_API_KEY_PATH: ${{ env.APPLE_API_KEY_PATH }} - ARTIFACT_SIGNING_ENDPOINT: ${{ runner.os == 'Windows' && secrets.ARTIFACT_SIGNING_ENDPOINT || '' }} - ARTIFACT_SIGNING_ACCOUNT: ${{ runner.os == 'Windows' && secrets.ARTIFACT_SIGNING_ACCOUNT || '' }} - ARTIFACT_SIGNING_CERT_PROFILE: ${{ runner.os == 'Windows' && secrets.ARTIFACT_SIGNING_CERT_PROFILE || '' }} - SIGN_DESCRIPTION: ${{ env.PRODUCT }} - SIGN_URL: ${{ env.PRODUCT_URL }} - run: npm run tauri build -- --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config src-tauri/tauri.release.conf.json - - - name: Locate the bundle directory - id: bundle + # The executable is signed BEFORE it is zipped: the portable zip and the setup both carry it + # as is. + - name: Sign the executable (Windows) + if: runner.os == 'Windows' + uses: redrob-labs/.github/actions/sign-windows@main + with: + files: ${{ runner.temp }}/stage/bin/redrob-query.exe + description: ${{ env.PRODUCT }} + url: ${{ env.PRODUCT_URL }} + endpoint: ${{ secrets.ARTIFACT_SIGNING_ENDPOINT }} + account: ${{ secrets.ARTIFACT_SIGNING_ACCOUNT }} + cert-profile: ${{ secrets.ARTIFACT_SIGNING_CERT_PROFILE }} + + - name: Package + id: package shell: bash run: | set -euo pipefail - # This is a Cargo workspace rooted at the repo, so the bundle is NOT under src-tauri/; - # ask cargo instead of hardcoding either layout. - target_root="$(cargo metadata --format-version 1 --no-deps | jq -r .target_directory)" - echo "dir=${target_root}/${RELEASE_TARGET}/release/bundle" >> "$GITHUB_OUTPUT" - - # The default bundler ships the build host's libwayland in the AppImage, and on Mesa 25+ that - # makes WebKitWebProcess abort with EGL_BAD_PARAMETER: the window opens and stays blank - # (tauri-apps/tauri#15665). Measured on v0.2.0 before this step existed -- strace on the web - # process, then the same AppImage with only those four libraries removed rendered with no - # environment overrides. Repacking changes the bytes, so the script also re-signs; the manifest - # job picks the new .sig up from beside the staged file and verifies it against the bytes. - - name: Repack the AppImage without bundled Wayland, and re-sign it - if: runner.os == 'Linux' + out="$RUNNER_TEMP/artifacts" + mkdir -p "$out" + base="redrob-query-${VERSION}-${{ matrix.slug }}" + + case "$RUNNER_OS" in + macOS) + # Archive the whole staging tree, not just the .app, so the licences and notices + # travel with the binary. ditto (not zip/cp) because it preserves the code + # signature's extended attributes and the stapled notarization ticket. + mv "$RUNNER_TEMP/stage" "$RUNNER_TEMP/${base}" + ( cd "$RUNNER_TEMP" && ditto -c -k --sequesterRsrc --keepParent "$base" "$out/${base}.zip" ) + ;; + Windows) + # `-portable`: the unpacked, signed app, which Redrob Launcher unzips and runs in place. + powershell -NoProfile -Command \ + "Compress-Archive -Path '$RUNNER_TEMP/stage/*' -DestinationPath '$out/${base}-portable.zip' -Force" + ;; + *) + tar -C "$RUNNER_TEMP/stage" -czf "$out/${base}.tar.gz" . + # The same tree as a package for apt; the system's own uninstall removes it. + python3 tools/release/make_deb.py "$RUNNER_TEMP/stage" "$VERSION" "$out/${base}.deb" + # The licence set once, from Linux, rather than three times under the same name. + cp LICENSE NOTICE UPSTREAM_NOTICES.md "$out/" + ;; + esac + + printf 'base=%s\n' "$base" >> "$GITHUB_OUTPUT" + printf 'dir=%s\n' "$out" >> "$GITHUB_OUTPUT" + ls -la "$out" + + # The disk image most people download: the stapled app, an Applications link, the uninstaller + # and the licences, on the branded window (installer/macos/). The image is signed, notarized and + # stapled in its own right, so Gatekeeper accepts it before anyone opens the app inside. + - name: Disk image (macOS) + if: runner.os == 'macOS' shell: bash env: - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - BUNDLE_DIR: ${{ steps.bundle.outputs.dir }} - # Pinned by digest: this binary rewrites a signed release artifact. - APPIMAGETOOL_URL: https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage - APPIMAGETOOL_SHA256: 46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1 + BASE: ${{ steps.package.outputs.base }} + APPLE_NOTARY_API_KEY_ID: ${{ secrets.APPLE_NOTARY_API_KEY_ID }} + APPLE_NOTARY_API_ISSUER_ID: ${{ secrets.APPLE_NOTARY_API_ISSUER_ID }} run: | set -euo pipefail - tool="$RUNNER_TEMP/appimagetool" - curl -fsSL -o "$tool" "$APPIMAGETOOL_URL" - echo "${APPIMAGETOOL_SHA256} ${tool}" | sha256sum -c - - chmod +x "$tool" - # Runners have no FUSE; both the tool and the AppImage run through their own extractor. - export APPIMAGE_EXTRACT_AND_RUN=1 - bash scripts/resign-linux-appimage.sh "$BUNDLE_DIR" "$tool" - - # sign.ps1 already verified each file it signed (`signtool verify /pa`, Status Valid, signer - # CN). This asserts the OUTCOME over the installers that leave the runner: both are present - # and each carries a Valid, timestamped Authenticode signature by the organisation's - # certificate -- so a bundler that quietly skipped the sign command cannot ship unsigned. - - name: Verify Windows installer signatures + trap 'rm -f "$RUNNER_TEMP/AuthKey.p8"' EXIT + # A venv: the runner's Homebrew Python is "externally managed" (PEP 668) and refuses + # `pip install --user`. + python3 -m venv "$RUNNER_TEMP/dmgbuild-venv" + "$RUNNER_TEMP/dmgbuild-venv/bin/python" -m pip install --quiet "dmgbuild==1.6.5" + export PATH="$RUNNER_TEMP/dmgbuild-venv/bin:$PATH" # make-dmg.sh runs `python3 -m dmgbuild` + dmg="$RUNNER_TEMP/artifacts/${BASE}.dmg" + bash installer/macos/make-dmg.sh \ + --app "$RUNNER_TEMP/$BASE/Redrob Query.app" \ + --licenses "$RUNNER_TEMP/$BASE/share/redrob-query/licenses" \ + --version "$VERSION" --sign-identity "$APPLE_SIGNING_IDENTITY" --out "$dmg" + xcrun notarytool submit "$dmg" \ + --key "$RUNNER_TEMP/AuthKey.p8" \ + --key-id "$APPLE_NOTARY_API_KEY_ID" \ + --issuer "$APPLE_NOTARY_API_ISSUER_ID" \ + --wait + # The same retry as the app's staple, above (each step is its own shell). + retry() { + local i + for i in 1 2 3 4 5 6; do + if "$@"; then return 0; fi + [ "$i" -lt 6 ] || { echo "::error::failed after 6 attempts: $*"; return 1; } + echo "::warning::attempt $i of 6 failed: $*; retrying in $((i * 20))s" + sleep $((i * 20)) + done + } + retry xcrun stapler staple "$dmg" + retry xcrun stapler validate "$dmg" + retry spctl --assess --type open --context context:primary-signature --verbose=4 "$dmg" + + # The setup carries the signed zip above and the uninstaller, so it is built after signing: + # uninstaller first, signed, then embedded; the setup last, signed. installer/windows/README.md. + - name: Build the uninstaller (Windows) if: runner.os == 'Windows' shell: pwsh - env: - BUNDLE_DIR: ${{ steps.bundle.outputs.dir }} - EXPECTED_CN: McKinley Rice, Inc. run: | $ErrorActionPreference = "Stop" - if (-not (Test-Path -LiteralPath $env:BUNDLE_DIR)) { throw "No bundle directory at $env:BUNDLE_DIR" } - $installers = @(Get-ChildItem -LiteralPath $env:BUNDLE_DIR -Recurse -File | Where-Object { $_.Extension -in '.exe', '.msi' }) - if (-not ($installers.Extension -contains '.exe') -or -not ($installers.Extension -contains '.msi')) { - throw "Expected both signed NSIS and MSI installers" - } - foreach ($installer in $installers) { - $signature = Get-AuthenticodeSignature -LiteralPath $installer.FullName - if ($signature.Status -ne 'Valid') { - throw "$($installer.Name) signature status $($signature.Status): $($signature.StatusMessage)" - } - $cn = $signature.SignerCertificate.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) - if ($cn -ne $env:EXPECTED_CN) { - throw "$($installer.Name) signed by CN=$cn, expected CN=$($env:EXPECTED_CN)" - } - if (-not $signature.TimeStamperCertificate) { - throw "$($installer.Name) signature carries no timestamp" - } - Write-Host "$($installer.Name) signed by CN=$cn status=$($signature.Status)" + $work = Join-Path $env:RUNNER_TEMP "setup" + python installer/windows/build.py --out $work --arch x64 --version $env:VERSION --uninstaller-only + if ($LASTEXITCODE -ne 0) { throw "uninstaller build failed ($LASTEXITCODE)" } + if (-not (Test-Path -LiteralPath (Join-Path $work "uninstall/Uninstall Redrob Query.exe"))) { + throw "uninstaller was not built" } - # Installers, the macOS updater payloads and their .sig files take the names Redrob Launcher - # reads (`redrob-query---.`); the payload gets the version and - # os-arch token tauri leaves out of its name. - # Decided here, before upload, so latest.json can name every asset before the release exists. - - name: Stage the release files under their published names - shell: bash + - name: Sign the uninstaller (Windows) + if: runner.os == 'Windows' + uses: redrob-labs/.github/actions/sign-windows@main + with: + files: ${{ runner.temp }}/setup/uninstall/*.exe + description: ${{ env.PRODUCT }} + url: ${{ env.PRODUCT_URL }} + endpoint: ${{ secrets.ARTIFACT_SIGNING_ENDPOINT }} + account: ${{ secrets.ARTIFACT_SIGNING_ACCOUNT }} + cert-profile: ${{ secrets.ARTIFACT_SIGNING_CERT_PROFILE }} + + - name: Build the setup (Windows) + if: runner.os == 'Windows' + shell: pwsh env: - BUNDLE_DIR: ${{ steps.bundle.outputs.dir }} - run: node scripts/stage-release-assets.mjs "$BUNDLE_DIR" "$RUNNER_TEMP/release-assets" "$RELEASE_VERSION" "$RELEASE_TARGET" + BASE: ${{ steps.package.outputs.base }} + run: | + $ErrorActionPreference = "Stop" + $work = Join-Path $env:RUNNER_TEMP "setup" + $zip = Join-Path $env:RUNNER_TEMP "artifacts/$env:BASE-portable.zip" + $setupName = "$env:BASE-setup.exe" + $uninstaller = Join-Path $work "uninstall/Uninstall Redrob Query.exe" + python installer/windows/build.py --out $work --arch x64 --version $env:VERSION --payload $zip ` + --uninstaller $uninstaller --name $setupName + if ($LASTEXITCODE -ne 0) { throw "setup build failed ($LASTEXITCODE)" } + $setup = Join-Path $work $setupName + # The setup must carry exactly the zip that ships beside it; a stale payload would install + # a different build than the one its checksum file describes. + if ((Get-Item -LiteralPath $setup).Length -le (Get-Item -LiteralPath $zip).Length) { + throw "setup is not larger than the zip it should embed" + } + Copy-Item -LiteralPath $setup -Destination (Join-Path $env:RUNNER_TEMP "artifacts/$setupName") - - name: Upload the release files - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - name: Sign the setup (Windows) + if: runner.os == 'Windows' + uses: redrob-labs/.github/actions/sign-windows@main + with: + files: ${{ runner.temp }}/artifacts/*-setup.exe + description: ${{ env.PRODUCT }} + url: ${{ env.PRODUCT_URL }} + endpoint: ${{ secrets.ARTIFACT_SIGNING_ENDPOINT }} + account: ${{ secrets.ARTIFACT_SIGNING_ACCOUNT }} + cert-profile: ${{ secrets.ARTIFACT_SIGNING_CERT_PROFILE }} + + # `release-*` is the prefix the shared publish job collects; the checksums and the launcher + # manifest are written there, over every platform's files at once. + - uses: actions/upload-artifact@v4 with: - name: release-${{ matrix.target }} - path: ${{ runner.temp }}/release-assets/* + name: release-${{ matrix.slug }} + path: ${{ steps.package.outputs.dir }}/* if-no-files-found: error - # latest.json IS the feed on GitHub Releases. Missing it does not fail an install, it silently - # freezes every existing client on its current version; a signature over the wrong bytes passes - # "has a signature" and fails on every user's machine at the first update check. So the manifest - # is written from the staged files, checked against the platforms the matrix built, and every - # signature is verified against the file it names -- on a dry run as well as on a tag. - updater-manifest: - name: Updater manifest - needs: [version, build] + - name: Remove the temporary keychain (macOS) + if: always() && runner.os == 'macOS' + run: | + if [ -f "$RUNNER_TEMP/redrob-build.keychain" ]; then security delete-keychain "$RUNNER_TEMP/redrob-build.keychain" || true; fi + + verify-release-assets: + name: Signed builds and licence files accompany the release + needs: [validate, build] runs-on: ubuntu-24.04 timeout-minutes: 15 + permissions: + contents: read steps: - - name: Check out source - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Set up Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: 22 - - name: Download every leg's release files - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + # A missing platform is a release that silently ships less than its notes claim, and a + # binary without its licence texts breaks the one thing MIT and Apache ask for. Both fail + # here, BEFORE the release is published. + - uses: actions/download-artifact@v4 with: pattern: release-* - path: release-assets + path: artifacts merge-multiple: true - - name: Write latest.json and verify every updater signature against its bytes + + - name: Assert every platform and every licence artifact is present env: - RELEASE_VERSION: ${{ needs.version.outputs.version }} - TAURI_UPDATER_PUBLIC_KEY: ${{ vars.TAURI_SIGNING_PUBLIC_KEY }} - REQUIRED_PLATFORMS: ${{ needs.version.outputs.platforms }} - REQUIRED_ASSET_SUFFIXES: ${{ needs.version.outputs.asset_suffixes }} + REQUIRED_ARCHIVES: ${{ needs.validate.outputs.archive_suffixes }} ${{ needs.validate.outputs.installer_suffixes }} + shell: bash run: | set -euo pipefail - echo "Staged release files:" - ls -1 release-assets | sed 's/^/ /' - mkdir -p manifest - node scripts/write-updater-manifest.mjs release-assets "$RELEASE_VERSION" "$GITHUB_REPOSITORY" manifest/latest.json - cat manifest/latest.json - - name: Upload latest.json - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: release-updater-manifest - path: manifest/latest.json - if-no-files-found: error + names="$(ls -1 artifacts)" + echo "Assets built:" + printf ' %s\n' $names + + # Platform archives and installers come from the same generator that built the matrix, + # so a leg that was deliberately not built is not demanded here, and one that WAS built + # cannot be quietly missing. + for suffix in ${REQUIRED_ARCHIVES}; do + found=0 + for name in $names; do + case "$name" in + *"$suffix") found=1; break ;; + esac + done + if [ "$found" -ne 1 ]; then + echo "::error::no asset ending in ${suffix}; a platform this run built produced no archive" + exit 1 + fi + done + + # The licence artifacts are unconditional. The checksums and redrob-launcher.json are + # the publish job's: it writes both over this same set of files. + for pattern in \ + '^LICENSE$' \ + '^NOTICE$' \ + '^UPSTREAM_NOTICES\.md$'; do + if ! printf '%s\n' $names | grep -qE "$pattern"; then + echo "::error::no asset matching ${pattern}; the licence texts and notices must accompany every binary" + exit 1 + fi + done + + if printf '%s\n' $names | grep -q '\.unsigned\.'; then + echo "::error::an unsigned archive is among the assets" + exit 1 + fi + + echo "Every built platform asset (${REQUIRED_ARCHIVES}) and the licence files are present." + # The shared redrob-labs publish job: SHA256SUMS.txt over every asset, redrob-launcher.json (this + # app reads the launcher's shared account file directly), and the release v created + # and PUBLISHED as latest (prerelease when the version carries a `-`). Only for a real tag, never + # for a dry run, and only when every job before it succeeded. `tag` and `target` are passed + # explicitly because this workflow is dispatched from a branch: github.sha is the branch head, not + # the tag's commit. The assets reach it as the `release-*` artifacts every job above uploaded + # (release-linux-x86_64, release-macos-arm64, release-windows-x64), which the shared + # job merges into one directory before writing the sums -- the same hand-over redrob-canvas uses. publish: - needs: [version, rust-advisories, build, updater-manifest] - if: always() && github.ref_type == 'tag' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'skipped') + needs: [validate, build, verify-release-assets] + if: always() && needs.validate.outputs.dry_run != 'true' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'skipped') uses: redrob-labs/.github/.github/workflows/release-publish.yml@main permissions: contents: write with: - version: ${{ needs.version.outputs.version }} + version: ${{ needs.validate.outputs.version }} title: Redrob Query launcher_manifest: true publish: true + tag: ${{ inputs.tag }} + target: ${{ needs.validate.outputs.sha }} diff --git a/.gitignore b/.gitignore index 6366a8b..1d2849e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,14 @@ -# Dependencies -node_modules/ -target/ - # Build outputs -dist/ -# Third-party plugin files, placed by scripts/install-plugin-assets.mjs from the pinned npm -# packages. Generated, not authored -- committing 1.8 MB of build output would put a megabyte-scale -# binary diff in history on every plugin version bump. -public/plugins/ -src-tauri/target/ +target/ *.log # Local environment and secrets .env .env.* -!.env.example *.db *.db-shm *.db-wal -# Generated only inside a release runner by scripts/prepare-release-config.mjs -src-tauri/tauri.release.conf.json - # Editors and OS .DS_Store .idea/ @@ -32,19 +19,6 @@ src-tauri/tauri.release.conf.json .agents/tasks/ .kiro/ -# Demo and generated review artifacts -.demo-recorder/ -semantic-review/ - -# Test outputs -coverage/ -test-results/ -playwright-report/ - -# A Beekeeper Studio checkout to port from. 284 MB of GPLv3 source plus 544 KB -# under a COMMERCIAL licence that forbids use here, so the whole tree stays out of -# git and scripts/check-upstream-paths.mjs asserts that separately. Written with a -# trailing slash to match a DIRECTORY, and the probe in that script uses the -# trailing slash too -- `git check-ignore upstream` misses it when the directory -# does not exist locally, which is exactly the state a fresh clone is in. +# A Beekeeper Studio checkout to port from. 284 MB of GPLv3 source plus 544 KB under a COMMERCIAL +# licence that forbids use here, so the whole tree stays out of git. /upstream/ diff --git a/AGENTS.md b/AGENTS.md index b9592f5..070b2a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,155 +1,103 @@ # AGENTS.md -Operating notes for an agent working in `redrob-labs/redrob-query`. Everything here was read -from the tree or measured against the live repository, not assumed. +Operating notes for an agent working in `redrob-labs/redrob-query`. Everything here was read from +the tree or measured against the live repository, not assumed. ## What this repository is -Redrob Query is a JVM-free desktop database client for PostgreSQL, MySQL, SQLite, and MongoDB, -built as a Tauri 2 shell around a React 19 front end and a Rust core crate. Version 0.1 ships a -read-only desktop workspace: mutation commands are not registered with Tauri at all, so -"read-only" is enforced by the absence of an IPC command rather than by a runtime check. The same -front end also runs in a plain browser against deterministic in-memory fixtures, which is how the -product demos without a database or anyone's credentials. +Redrob Query is a native desktop database client for PostgreSQL, MySQL, SQLite and MongoDB: a Rust +egui/wgpu shell (`apps/redrob-query`) over a Rust engine crate (`crates/redrob-query-core`). Version +0.3.0 replaced the Tauri 2 + React 19 shell outright; there is no TypeScript, no Node, no browser +demo and no plugin host any more. The shape of the repository follows `redrob-labs/redrob-canvas`. -The workflow is informed by DBeaver Community, but there is no DBeaver, Eclipse, OSGi, JDBC, or -Java code here. Keep it that way: user-visible strings name Redrob Query. +The workflow is informed by DBeaver Community and parts of the engine were ported from Beekeeper +Studio Community Edition (GPL-3.0-or-later, which is why this tree is GPL). There is no DBeaver, +Eclipse, OSGi, JDBC or Java code here. Keep it that way: user-visible strings name Redrob Query. ## Layout | Path | What lives there | | --- | --- | -| `src/` | The React 19 front end, 26 files. `components/` holds the panels (Navigator, QueryEditor, ResultGrid, AiPanel, CommandPalette), `store/` holds the Zustand workspace store and its localStorage persistence, `api/bridge.ts` is the single seam between the UI and either Tauri IPC or the browser DemoBridge, `domain/types.ts` is the shared type surface, `test/setup.ts` is the vitest jsdom setup. | -| `crates/redrob-core/` | The Rust core: `connection.rs`, `query.rs`, `profile_store.rs`, `secret.rs`, `ai.rs`, `models.rs`, `error.rs`. SQLx drives PostgreSQL/MySQL/SQLite, the `mongodb` crate drives Mongo, `reqwest` with rustls carries AI traffic. `publish = false`. | -| `src-tauri/` | The desktop shell. `src/commands.rs` holds 11 `#[tauri::command]` functions and `src/lib.rs` registers exactly those 11 in `generate_handler!`. `capabilities/default.json` grants only `core:default` to the `main` window. | -| `src-tauri/gen/schemas/` | Tauri-generated ACL and capability schemas, four JSON files, and they are committed. Do not hand-edit them; they are regenerated by the Tauri build. | -| `scripts/` | Node ESM release scripts: `validate-release.mjs` (the release-metadata validator wired to `npm run release:check`), `prepare-release-config.mjs` (the CI-only tauri config override: updater key, version, the shared Windows sign command), `release-matrix.mjs`, `stage-release-assets.mjs` (renames one leg's bundles into its `release-*` artifact), `write-updater-manifest.mjs` (writes and verifies `latest.json`), plus the Linux AppImage repack pair. | -| `docs/` | `ARCHITECTURE.md`, `SECURITY.md`, `DEMO.md`, `DBEAVER-PORTING-MAP.md`. | -| `dist/`, `target/`, `node_modules/` | Build output and dependencies, all three ignored by `.gitignore` and untracked. `git ls-files dist` returns nothing even though the directory exists locally. | - -Documentation is bilingual: `README.md` and `CONTRIBUTING.md` have `.ko.md` siblings. Change both -or neither. +| `apps/redrob-query/` | The shell. `main.rs` (window, icon, `--version`), `app.rs` (panels and event routing), `profiles.rs` (tree + connection form), `schema.rs` (lazy schema browser), `editor.rs` (tabs, run / format / save), `highlight.rs` (SQL/MQL lexer, formatter, starters), `grid.rs` (virtualised result grid, staging, exports), `changes.rs` (staged edits -> `CellEditSet`), `structure.rs` (keys/indexes + DDL forms), `mongo.rs` (request strip), `ai_dock.rs` (Ask), `account.rs` (shared account watcher), `workspace.rs` (tabs/history persistence), `services.rs` (config dir), `theme.rs` (the only colour literals). `build.rs` embeds the Windows icon and version info. | +| `crates/redrob-query-core/` | The engine: `connection.rs` (`DataService`), `query.rs` (read-only classification), `models.rs`, `profile_store.rs`, `secret.rs` (OS keyring), `cell_edits.rs`, `ddl.rs`, `keys.rs` (PK/FK/index readers), `account.rs` (the launcher's shared `account.json`), `ai.rs` (console-direct chat), `workspace.rs` (the `Workspace` facade the shell drives, one method per former Tauri command, answered through `Pending`). `publish = false`. | +| `brand/SOURCES.json` | sha256 pins of every brand asset against the design system; written by `scripts/build-brand-assets.py`, checked by `scripts/check-brand.py`. Never edit by hand. | +| `resources/icons`, `resources/brand` | Generated brand assets (hicolor PNGs, `.ico`, `.icns`, SVGs, lockups, splash). Owned by the brand pipeline. | +| `installer/windows/` | The bilingual (English / 한국어) Win32 setup and uninstaller, ported from redrob-launcher; `build.py` compiles it on a Windows runner. `installer/macos/` holds the dmg settings and the uninstaller script. | +| `tools/release/` | `stage.py` (per-platform tree: `bin/`, licences, the `.app`), `make_deb.py`; `tools/release_matrix.py` is the one list of the three targets. | +| `docs/` | `ARCHITECTURE.md`, `SECURITY.md`, `DBEAVER-PORTING-MAP.md`, `compatibility.md`, `dialect-verification.md`, `beekeeper-porting-boundary.md`, `upstream-sources.toml`. Some of these still describe the Tauri shell's paths; the engine descriptions hold. | +| `NOTICE`, `UPSTREAM_NOTICES.md`, `LICENSE` | Attribution. Upstream lines are never replaced, only added to. | + +Documentation is bilingual: `README.md` and `CONTRIBUTING.md` have `.ko.md` siblings. Change both or +neither. ## Toolchain and commands -Node with npm is the JavaScript package manager, pinned by `"packageManager": "npm@11.4.2"` with -`engines` requiring Node >= 22.12.0 and npm >= 11.0.0. Rust comes from rustup, and -`rust-toolchain.toml` pins channel `1.94.0` with `clippy` and `rustfmt`. The Cargo workspace root is -the repository root, not `src-tauri`; its members are `crates/redrob-core` and `src-tauri`. Install -JavaScript dependencies with `npm ci`, never `npm install`, so the lockfile decides. - -Commands run and their exit codes, on this checkout at `origin/develop` (`8c1dbb5`): +Rust comes from rustup; `rust-toolchain.toml` pins channel `1.95.0` with `clippy` and `rustfmt`, and +`rust-version` in `Cargo.toml` matches it. The workspace members are `crates/*` and `apps/*`. On Linux +the shell needs `libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev +libgtk-3-dev libdbus-1-dev`. Run desktop binaries on a display; `redrob-query --version` needs none. -| Command | Exit code | Note | -| --- | --- | --- | -| `npm run release:check` | 0 | 0.12s. Printed "Release metadata is synchronized for Redrob Query 0.2.0." | -| `npm run typecheck` | 0 | `tsc --noEmit -p tsconfig.app.json`. | -| `npm test` | 0 | vitest, 4 files, 74 tests passed, 11.8s. | -| `npm run build` | 0 | Runs typecheck then `vite build`, 1.5s. | -| `npm run rust:fmt` | 127 | `cargo: not found`. No rustup, cargo, or rustc on this host, so nothing Rust was verified locally. | +The gate, as CI runs it (`.github/workflows/ci.yml`, pull requests only): -`npm run check` is the whole gate in one command: `release:check`, `test`, `build`, `rust:fmt`, -`rust:clippy`, `rust:test`. Run it before asking for review. If Rust is not installed, say so in the -pull request rather than implying the Rust half passed. +```bash +python3 scripts/check-brand.py +cargo fmt --all -- --check +cargo clippy -p redrob-query -p redrob-query-core --all-targets --locked --profile ci -- -D warnings +cargo test -p redrob-query -p redrob-query-core --locked --profile ci +``` -Three connector tests in `redrob-core` are `#[ignore]` and need live databases. Cargo does not read -`.env`, so export the URLs from `.env.example` in your shell and run -`cargo test --locked -p redrob-core --all-features -- --ignored`. Skipping them is normal; claiming -they passed without exporting the URLs is not. +`--profile ci` compiles every dependency at opt-level 0 (root `Cargo.toml`); the `dev` profile +optimises dependencies at level 2, which makes a cold test run take far longer. Clippy runs with +`all` and `pedantic` warned and `-D warnings`, so a pedantic lint fails the build. `unsafe_code` is +forbidden workspace-wide. The release workflow repeats only `cargo fmt --check` and the brand check. -## Branch model and gitflow +Three connector tests in the core are `#[ignore]` and need live databases +(`REDROB_TEST_POSTGRES_URL`, `REDROB_TEST_MYSQL_URL`, `REDROB_TEST_MONGO_URL`). Skipping them is +normal; claiming they passed without exporting the URLs is not. -`develop` is the default branch and the base of every pull request. The exception open right now is -`release/v0.2.0`, which targets `main` -- that is what a release promotion -should do. `main` is released state and moves -only by merging `develop` (through a `release/*` or `hotfix/*` branch) into it. Release tags are cut -from `main`. +## Branch model -A hotfix branches from `main`, merges into `main`, releases, and THEN `main` is merged back into -`develop`. That last step is the one people drop, and dropping it is a real failure: the sibling -repository redrob-code spent a month with a lockfile its own default branch could not install from -because the back-merge never happened. Check the current state before you trust it: +`develop` is the default branch and the base of every pull request. `main` is released state and +moves only by a pull request from `develop` (a `release/` bump lands on `develop` first). +Release tags are cut from `main`; the release workflow refuses a tag that is not reachable from +`origin/main`, that does not equal the workspace version, or whose version has no `## ` +section in `CHANGELOG.md`. After a release, `main` is merged back into `develop`. Check before you +trust it: ```bash git rev-list --count origin/develop..origin/main # commits on main that develop lacks git rev-list --count origin/main..origin/develop # commits on develop that main lacks ``` -Both printed `0` when this file was written, so the two branches were identical and nothing was -owed in either direction. A nonzero first number means a release or hotfix has not been merged back -into `develop` and every branch cut from `develop` is missing it. - -One part of the convention is machine-checked: the release workflow fetches `origin/main` and fails -the tag unless `git merge-base --is-ancestor "$GITHUB_SHA" origin/main` holds, so tagging straight -off `develop` fails instead of shipping. Which branch a pull request comes FROM is not checkable by -a ruleset, so "only `release/*` and `hotfix/*` merge into `main`" rests on reviewers. - -Working branches are `/` off `develop`, types `feat`, `fix`, `chore`, `docs`, `test`, -`refactor`, `perf`. Merges are squash-only and the branch is deleted, so the squash body is the -pull request body. - -`.github/workflows/gitflow.yml` checks both of those conventions instead of leaving them to -reviewers, and it is the organisation's copy: the file only calls the reusable workflow in -`redrob-labs/.github/.github/workflows/gitflow.yml`, which holds the one org-wide type list (`feat -fix chore docs test refactor perf ci build sync hotfix release`). A pull request from any other -prefix, an agent or tool name for instance, fails the `gitflow / branch name follows the convention` -check, and `develop` and `main` are exempt because a promotion or back-merge branch is not named -after a type. On every push to `main` the `gitflow / main is contained in develop` check counts -`origin/develop..origin/main`, fails when it is nonzero -- the missing back-merge that cost the -sibling repository a month of a broken default branch -- and opens the back-merge pull request -itself (`sync/back-merge-`). Neither check name is a required check, so nothing new gates a -merge until someone adds it to branch protection. - -## Protection, and what is actually enforced - -Both `develop` and `main` are protected the same way, read from the branch-protection API: - -- pull requests only, no direct pushes; -- force pushes blocked, deletion blocked; -- `required_approving_review_count` is 0, so a pull request can merge with no review; -- `enforce_admins` is off, so an admin is not bound by any of the above; -- required checks are `Format, clippy, tests` and `Release metadata, types, tests, build`. - -`Rust security advisories` is deliberately NOT required. An advisory check goes red when someone -else publishes a CVE, which has nothing to do with the change under review, and a required check -that a third party can turn red blocks every unrelated merge. Read it, do not gate on it. - -CI runs from `.github/workflows/ci.yml`, on `pull_request` and on pushes to `main` and `develop`. -Note what protection still does NOT give you: `required_approving_review_count` is 0 and -`enforce_admins` is off, so a pull request whose required checks pass can merge with no review, and -an admin is not bound by any of it. Read the checks yourself rather than waiting for GitHub to stop -you. - -`CONTRIBUTING.md` says both CI jobs "are required before a merge". The workflow now has three jobs, -not two, and only two of them are required, so correct that sentence when you next touch it. Trust -the branch-protection API over the prose either way. - -The CI workflow is deliberately `pull_request`, never `pull_request_target`, and consumes no -secrets, so a fork's pull request gets an identical run with no credential. Do not "fix" that by -switching the trigger. +Working branches are `/` off `develop`. Merges into `develop` are squash merges with an +explicit commit message; the branch is deleted afterwards. PR policy is the organisation's reusable +workflow (`.github/workflows/pr-policy.yml`). ## What an agent gets wrong first -- **`npm run release:check` is a real gate, not a formality.** `scripts/validate-release.mjs` - asserts the version is identical across `package.json`, the Cargo workspace, `tauri.conf.json`, - and both `package-lock.json` version fields; that `rust-version` in `Cargo.toml` matches the - `rust-toolchain.toml` channel; that `productName` is `Redrob Query` and the identifier is - `ai.redrob.query`; that the window label is `main` and `devUrl` is `http://127.0.0.1:1420`. Bump - a version in one file and this fails. -- **Every icon in `src-tauri/icons` must be RGBA.** The validator reads byte 25 of each PNG (the - IHDR colour type) and demands 6, because `tauri::generate_context!` panics at COMPILE TIME on a - non-RGBA icon. One RGB PNG means the desktop app cannot be built at all, and that shipped once. -- **Adding a Tauri command is a security change.** The read-only guarantee is that no mutation - command exists in the 11 registered in `src-tauri/src/lib.rs`. Registering one silently removes - the guarantee the README states. -- **GitHub Actions are SHA-pinned** in both workflows and updated by Dependabot's - `github-actions` ecosystem. Keep the `# vX.Y.Z` comment next to the SHA when you touch one. -- **`unsafe_code = "forbid"`** at the workspace level, with clippy `all` and `pedantic` warned. - CI runs clippy with `-D warnings`, so a pedantic lint fails the build. -- **`src-tauri/tauri.release.conf.json` is gitignored on purpose**: it is generated inside a - release runner by `scripts/prepare-release-config.mjs`. Do not create it locally and commit it. -- **Do not commit anything from `.env`.** `.env.example` is the only env file tracked, and database - secrets in the desktop app live in the OS keyring. This app holds no Redrob console key: the shared - Redrob Code daemon does (`PUT /auth/redrob`), and `crates/redrob-core/src/ai.rs` reaches the console - only through it (`redrob-engine-client`). There is no `REDROB_API_KEY` any more. Do not add a direct - console path for chat, do not store a key, and do not print one. +- **Brand assets are generated and pinned.** Editing anything under `resources/icons`, + `resources/brand` or `installer/*/res/icon.png|lockup.png` by hand fails `check-brand.py`. Rebuild + with `scripts/build-brand-assets.py --design-system ` (needs + ImageMagick 7) and commit `brand/SOURCES.json` with the outputs. +- **The read-only guarantee is the engine's gate, not the shell's.** `DataService::execute_query` + accepts read-only statements only; writes go through `apply_cell_edits` (one transaction, every + statement must change exactly one row) and `apply_table_change` (a fixed type list). Do not add a + write path that bypasses them. +- **No sign-in UI.** The account is Redrob Launcher's shared `account.json` + (`crates/redrob-query-core/src/account.rs`): read on every request, never written, never logged or + shown. `REDROB_API_KEY` and this app's own `account.json` win over it. Do not store a key in the + keyring or print one. The one allowed action is opening the launcher's page. +- **The AI path is console-direct.** `ai.rs` posts to `{apiBaseUrl}/chat/completions` with a bearer + key and model `auto` and surfaces the console's `error.message`. There is no Redrob Code daemon in + between any more; do not reintroduce `redrob-engine-client`. +- **Colour literals live in `theme.rs` only**, named after their design-system tokens. The shell + otherwise paints from egui's theme. +- **Workspace persistence is bounded.** 30 tabs, 50 history entries, query text only, written + atomically to `workspace.json`; `REDROB_QUERY_NO_PREFS` turns it off. Never persist result rows, + passwords or prompts. +- **Release assets must keep the launcher's names**: `redrob-query---[-portable|-setup].` + from `tools/release_matrix.py`. The Windows installed product is ProductName `Redrob Query`, + executable `bin\redrob-query.exe`, Settings > Apps key `RedrobQuery`. +- **GitHub Actions are SHA-pinned** in the workflows. Keep the `# vX.Y.Z` comment next to the SHA. +- **Do not commit secrets.** Database passwords live in the OS keyring; nothing in the tree should + hold one. diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a5954..5664472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,20 +4,33 @@ The release workflow's notes point here, so this file is what a download's notes follow the `vMAJOR.MINOR.PATCH` tags that trigger a release; while the major is 0 a minor bump is where behaviour may change. -## Unreleased +## 0.3.0 - 2026-10-16 ### Changed -- The release pipeline is the redrob-labs shared shape. Windows installers are Authenticode-signed - through Azure Artifact Signing with the job's OpenID Connect login and the organisation's one - signing script (`redrob-labs/.github/actions/sign-windows`), run by tauri as its sign command; the - self-signed certificate, its secrets and the `NotTrusted` allowance are gone, and every installer - must verify as `Valid`. Each platform leg uploads its files as a `release-*` artifact under the - launcher-readable names, `latest.json` is written from those files with every updater signature - checked against its bytes, and the shared `release-publish` job publishes the release as latest - with `SHA256SUMS.txt` and `redrob-launcher.json`. There is no draft any more; a manual run with a - `version` input is a dry run that publishes nothing. The gitflow check calls the organisation's - reusable workflow, whose back-merge job opens the back-merge pull request itself. +- **Redrob Query is a native Rust app.** The Tauri 2 + React 19 shell, the browser demo, the + Beekeeper plugin host and the ER-diagram plugin are gone; the window is egui 0.36 on wgpu + (`apps/redrob-query`) over the same engine, renamed `redrob-query-core`. Profiles, schema browser, + tabbed SQL / Mongo JSON editor (colouring, Ctrl+Enter, Ctrl+Shift+F, Ctrl+S, starter queries, + read-only batches), virtualised result grid (page sort / filter, column visibility, TSV / JSON + export, JSON document view), reviewed cell edits and row deletes through the Changes panel, + structure panel with primary and foreign keys and indexes, create-table / add-column forms, and + the Ask dock are all native. Workspace persistence keeps 30 tabs and 50 history entries in + `workspace.json` (off with `REDROB_QUERY_NO_PREFS`). +- **Ask talks to the Redrob console directly** with the account Redrob Launcher signed this + computer in with (`account.json`, read on every request and watched while the app is open), as + the launcher's shared-account contract asks. The Redrob Code daemon path, the pasted-key dialog + and the keyring key migration are gone; the console's own error message is shown when it refuses. +- **Key and index metadata moved into the engine** (`keys.rs`): PostgreSQL catalog, MySQL + `information_schema`, SQLite table-valued pragmas, MongoDB index list. +- **Release and repository shape follow redrob-canvas.** One `Release` workflow builds the three + targets from the cargo binary -- Linux tar.gz + deb, macOS arm64 dmg + zip (signed, notarized, + stapled), Windows x64 portable zip + a bilingual (English / 한국어) setup (Authenticode-signed) -- + with `SHA256SUMS.txt` and `redrob-launcher.json` from the shared publish job. Brand assets are + built from the design system and pinned in `brand/SOURCES.json`. CI is one thin pull-request + workflow: the brand check plus fmt, clippy and the tests of our crates with `--profile ci`. +- The Tauri updater feed (`latest.json`) is no longer produced; Redrob Launcher installs from the + release assets. ## 0.2.4 - 2026-10-14 diff --git a/CONTRIBUTING.ko.md b/CONTRIBUTING.ko.md index a734e6f..d2b9d01 100644 --- a/CONTRIBUTING.ko.md +++ b/CONTRIBUTING.ko.md @@ -54,20 +54,20 @@ ruleset은 pull request가 *어느* 브랜치에서 왔는지를 표현할 수 ```bash git switch develop && git pull -git switch -c release/v0.2.0 -# bump the version, update the changelog, run the release check -# open a pull request into main and merge it, then tag main: +git switch -c release/0.3.0 +# Cargo.toml의 버전을 올리고 CHANGELOG 절을 쓰고 게이트를 돌립니다 +# develop으로 풀 리퀘스트를 열어 머지하고, develop -> main 풀 리퀘스트(머지 커밋)를 머지한 뒤 main에 태그를 뗍니다: git switch main && git pull -git tag -a v0.2.0 -m "Redrob Query v0.2.0" -git push origin v0.2.0 -# the tag publishes the GitHub Release itself; the gitflow check then opens the -# back-merge pull request (sync/back-merge-) into develop -- merge it +git tag -a v0.3.0 -m "Redrob Query v0.3.0" +git push origin refs/tags/v0.3.0:refs/tags/v0.3.0 +gh workflow run release.yml --ref main -f tag=v0.3.0 +# 그다음 main을 develop으로 다시 머지합니다 ``` -태그는 `package.json`의 버전과 같아야 하고, 모든 플랫폼 레그가 빌드·서명·검증을 마치면 릴리스는 -바로 **발행**됩니다 — 승인할 초안이 없습니다. 릴리스 없이 파이프라인만 돌려 보려면 **Signed -desktop release** 워크플로를 `version` 입력(예: `0.3.0-1`, MSI 번들러 때문에 pre-release 식별자는 -숫자여야 합니다)으로 수동 실행하세요. 모든 레그를 빌드·서명하고 아무것도 발행하지 않습니다. +태그는 `Cargo.toml`의 워크스페이스 버전과 같아야 하고 `CHANGELOG.md`에 `## ` 절이 있어야 +합니다. 모든 플랫폼 레그가 빌드·서명·검증을 마치면 릴리스는 바로 **발행**됩니다 — 승인할 초안이 +없습니다. 릴리스 없이 파이프라인만 돌려 보려면 **Release** 워크플로를 `dry_run=true`로 실행하세요. +지정한 ref를 빌드·서명하고 아무것도 발행하지 않습니다. hotfix도 같은 형태이며, `hotfix/*`를 `develop`이 아니라 `main`에서 떼고 양쪽으로 머지합니다. @@ -80,52 +80,44 @@ hotfix도 같은 형태이며, `hotfix/*`를 `develop`이 아니라 `main`에서 git switch develop && git pull git switch -c fix/short-description -npm install -npm run typecheck -npm run test -npm run build - cargo fmt --all -cargo clippy --all-targets -- -D warnings +cargo clippy -p redrob-query -p redrob-query-core --all-targets -- -D warnings +cargo test -p redrob-query-core +python3 scripts/check-brand.py -npm run release:check # the release validator, before tagging +cargo run -p redrob-query # 앱 실행. 내장 Demo SQLite 프로필은 서버가 필요 없습니다 -# open a pull request into develop +# develop으로 풀 리퀘스트를 엽니다 ``` -브라우저 데모는 의도적으로 결정적인 인메모리 샘플 데이터를 씁니다. 데이터베이스 없이, 누구의 -자격증명도 없이 제품을 보여주기 위해서입니다. 오늘 찍은 스크린샷이 다음 달에 찍은 것과 같도록 -결정적으로 유지하십시오. +내장 Demo SQLite 프로필은 결정적인 샘플 데이터가 든 인메모리 데이터베이스입니다. 의도적인 것입니다. +서버도, 누구의 자격 증명도 없이 제품을 보여 주기 위해서입니다. 오늘 찍은 스크린샷이 다음 달에 찍은 것과 +같도록 결정적으로 유지하세요. ### 커밋 -제목은 명령형으로 씁니다. 본문에는 _왜_ 를 적고, 로컬에 저장되는 것이나 앱이 쓸 수 있는 범위를 -건드리는 변경이면 그 사실을 본문에 밝히십시오. +제목은 명령형으로. 본문에는 _왜_ 바꿨는지를 쓰고, 로컬에 저장되는 것이나 앱이 쓸 수 있는 범위가 바뀌면 +그 사실을 거기에 적습니다. ## 브랜딩 -`src/assets/`의 마크는 Redrob Cowork가 함께 배포하는 것과 같은 Redrob 마크이며, 앱 아이콘은 -그것으로 생성됩니다. 비슷하게 보이는 그림으로 대체하는 것이 제품에 로고가 두 개 생기는 경로입니다. +모든 브랜드 파일(`resources/icons`, `resources/brand`, 설치 프로그램의 `res/`)은 +`scripts/build-brand-assets.py`가 디자인 시스템 배포물에서 만들고 `brand/SOURCES.json`에 sha256으로 +고정합니다. 비슷하게 그린 그림으로 바꾸는 것이 제품에 로고가 둘 생기는 경로이고, +`scripts/check-brand.py`가 그것을 거부합니다. ## CI가 검사하는 것 -`.github/workflows/ci.yml`은 모든 pull request와 `main`, `develop`으로의 push에서 돕니다. 시크릿을 -전혀 쓰지 않으므로 포크에서 온 pull request도 여기 브랜치와 완전히 같은 실행을 받습니다. 잡은 둘이고, -둘 다 머지 전에 필수입니다. +`.github/workflows/ci.yml`은 모든 풀 리퀘스트에서 돕니다. 시크릿을 쓰지 않으므로 포크에서 온 풀 리퀘스트도 +여기 브랜치와 똑같이 실행됩니다. 작업은 둘입니다. | 검사 | 실행하는 것 | | --- | --- | -| **릴리스 메타데이터, 타입, 테스트, 빌드** | `npm run release:check`, `npm run typecheck`, `npm test`, `npm run build` | -| **포맷, clippy, 테스트** | `npm run rust:fmt`, `npm run rust:clippy` (`-D warnings`), `npm run rust:test` | - -알아둘 만한 것은 `npm run release:check`입니다. 여러 파일로 퍼져 있어 조용히 틀어지는 것들을 -고정합니다. `package.json`, `Cargo.toml`, `tauri.conf.json`, `package-lock.json`의 버전, Cargo의 -`rust-version`과 `rust-toolchain.toml`의 일치, Tauri의 `productName`과 `identifier`, 그리고 -`src-tauri/icons/` 아래 모든 PNG가 RGBA인지입니다. 마지막 항목은 겉모습 문제가 아닙니다. -`tauri::generate_context!`는 RGBA가 아닌 아이콘에서 컴파일 타임에 panic하므로, RGB 아이콘 하나면 -데스크톱 앱이 빌드되지 않습니다. +| **brand assets** | `python3 scripts/check-brand.py` | +| **fmt, clippy, test (profile ci)** | `cargo fmt --all -- --check`, `cargo clippy -p redrob-query -p redrob-query-core --all-targets --locked --profile ci -- -D warnings`, 같은 크레이트의 `cargo test`. Rust 입력이 바뀌지 않았거나 풀 리퀘스트가 `main`을 향하면 건너뜁니다 | -`npm run check`는 이 전체를 한 명령으로 로컬에서 돌립니다. +릴리스 워크플로(`.github/workflows/release.yml`)는 브랜드 검사와 `cargo fmt --check`만 다시 돌리고 세 +타깃을 빌드·서명·게시합니다. ## 의존성 업데이트 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1de9ed..4a2bf5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,21 +53,21 @@ from `origin/main`, so tagging straight off `develop` fails instead of shipping. ```bash git switch develop && git pull -git switch -c release/v0.2.0 -# bump the version, update the changelog, run the release check -# open a pull request into main and merge it, then tag main: +git switch -c release/0.3.0 +# bump the version in Cargo.toml, write the CHANGELOG section, run the gate +# open a pull request into develop and merge it; then a pull request develop -> main +# (merge commit) and tag main: git switch main && git pull -git tag -a v0.2.0 -m "Redrob Query v0.2.0" -git push origin v0.2.0 -# the tag publishes the GitHub Release itself; the gitflow check then opens the -# back-merge pull request (sync/back-merge-) into develop -- merge it +git tag -a v0.3.0 -m "Redrob Query v0.3.0" +git push origin refs/tags/v0.3.0:refs/tags/v0.3.0 +gh workflow run release.yml --ref main -f tag=v0.3.0 +# then merge main back into develop ``` -The tag must equal the version in `package.json`, and the release is **published** when every -platform leg has built, signed and verified -- there is no draft to approve. To exercise the -pipeline without releasing, run the **Signed desktop release** workflow by hand with a `version` -such as `0.3.0-1` (a pre-release identifier must be numeric for the MSI bundler): it builds and -signs every leg and publishes nothing. +The tag must equal the workspace version in `Cargo.toml` and `CHANGELOG.md` must carry a +`## ` section; the release is **published** when every platform leg has built, signed and +verified -- there is no draft to approve. To exercise the pipeline without releasing, run the +**Release** workflow with `dry_run=true`: it builds and signs the dispatched ref and publishes nothing. A hotfix is the same shape with `hotfix/*` cut from `main` rather than `develop`, and it merges into both. @@ -81,22 +81,19 @@ need write access to contribute, and pull requests from forks run CI with no rep git switch develop && git pull git switch -c fix/short-description -npm install -npm run typecheck -npm run test -npm run build - cargo fmt --all -cargo clippy --all-targets -- -D warnings +cargo clippy -p redrob-query -p redrob-query-core --all-targets -- -D warnings +cargo test -p redrob-query-core +python3 scripts/check-brand.py -npm run release:check # the release validator, before tagging +cargo run -p redrob-query # the app; the built-in Demo SQLite profile needs no server # open a pull request into develop ``` -The browser demo is backed by deterministic in-memory sample data, on purpose: it demonstrates the -product without a database and without anybody's credentials. Keep it deterministic, so a screenshot -taken today matches one taken next month. +The built-in Demo SQLite profile is an in-memory database with deterministic sample data, on +purpose: it demonstrates the product without a server and without anybody's credentials. Keep it +deterministic, so a screenshot taken today matches one taken next month. ### Commits @@ -105,29 +102,23 @@ or what the app is allowed to write, say so there. ## Branding -The mark in `src/assets/` is the Redrob mark, the same one Redrob Cowork ships, and the app icons are -generated from it. Replacing it with a drawing that merely resembles it is how a product ends up with -two logos. +Every brand file (`resources/icons`, `resources/brand`, the installers' `res/`) is built from the +design system's published files by `scripts/build-brand-assets.py` and pinned by sha256 in +`brand/SOURCES.json`. Replacing one with a drawing that merely resembles it is how a product ends up +with two logos, and `scripts/check-brand.py` refuses it. ## What CI checks -`.github/workflows/ci.yml` runs on every pull request and on pushes to `main` and `develop`. It consumes no -secrets, so a pull request from a fork gets exactly the same run as one from a branch here. Two jobs, -and both are required before a merge: +`.github/workflows/ci.yml` runs on every pull request. It consumes no secrets, so a pull request from +a fork gets exactly the same run as one from a branch here. Two jobs: | Check | What it runs | | --- | --- | -| **Release metadata, types, tests, build** | `npm run release:check`, `npm run typecheck`, `npm test`, `npm run build` | -| **Format, clippy, tests** | `npm run rust:fmt`, `npm run rust:clippy` (`-D warnings`), `npm run rust:test` | - -`npm run release:check` is the one worth knowing about. It pins the things that fan out across -several files and go wrong silently: the version in `package.json`, `Cargo.toml`, `tauri.conf.json` -and `package-lock.json`; the Cargo `rust-version` against `rust-toolchain.toml`; the Tauri -`productName` and `identifier`; and that every PNG under `src-tauri/icons/` is RGBA. The last one is -not cosmetic — `tauri::generate_context!` panics at compile time on a non-RGBA icon, so a single RGB -icon means the desktop app does not build. +| **brand assets** | `python3 scripts/check-brand.py` | +| **fmt, clippy, test (profile ci)** | `cargo fmt --all -- --check`, `cargo clippy -p redrob-query -p redrob-query-core --all-targets --locked --profile ci -- -D warnings`, `cargo test` of the same crates. Skipped when no Rust input changed or when the pull request targets `main` | -`npm run check` runs the whole set locally in one command. +The release workflow (`.github/workflows/release.yml`) repeats only the brand check and +`cargo fmt --check`, then builds, signs and publishes the three targets. ## Dependency updates diff --git a/Cargo.lock b/Cargo.lock index 128b74f..e5a8863 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,121 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "serde", + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954" +dependencies = [ + "accesskit", + "accesskit_consumer 0.36.0", + "atspi-common", + "phf", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit", + "accesskit_consumer 0.38.0", + "hashbrown 0.16.1", + "objc2 0.5.3", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" +dependencies = [ + "accesskit", + "accesskit_consumer 0.35.0", + "hashbrown 0.16.1", + "static_assertions", + "windows", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "adler2" version = "2.0.1" @@ -17,6 +132,7 @@ dependencies = [ "cfg-if", "getrandom 0.3.4", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -31,25 +147,35 @@ dependencies = [ ] [[package]] -name = "alloc-no-stdlib" -version = "2.0.4" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "alloc-stdlib" -version = "0.2.4" +name = "android-activity" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ - "alloc-no-stdlib", + "android-properties", + "bitflags 2.13.2", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.21", ] [[package]] -name = "allocator-api2" -version = "0.2.21" +name = "android-properties" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "android_system_properties" @@ -61,43 +187,225 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.104" +name = "anstream" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] [[package]] -name = "async-trait" -version = "0.1.92" +name = "anstyle" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arboard" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb9553e62312c8756a5979254fc6d7da8bbeb5986a1e17037fab932f7037917" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.61.2", + "x11rb", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.5", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.5", +] + +[[package]] +name = "async-recursion" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] -name = "atk" -version = "0.18.2" +name = "async-signal" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ - "atk-sys", - "glib", - "libc", + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.5", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", ] [[package]] -name = "atk-sys" -version = "0.18.2" +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] @@ -116,16 +424,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "autocfg" -version = "1.5.1" +name = "atspi" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] [[package]] -name = "base64" -version = "0.21.7" +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -141,9 +480,9 @@ checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bigdecimal" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +checksum = "8fa3f3d8cbf4dffcfe4991de61d012bef509a409ecbe9dd41049bfe32b4d4653" dependencies = [ "autocfg", "libm", @@ -154,18 +493,18 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" [[package]] name = "bitflags" @@ -175,9 +514,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" dependencies = [ "serde_core", ] @@ -214,41 +553,33 @@ dependencies = [ [[package]] name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "brotli" -version = "8.0.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", + "objc2 0.5.3", ] [[package]] -name = "brotli-decompressor" -version = "5.0.3" +name = "block2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "objc2 0.6.5", ] [[package]] -name = "bs58" -version = "0.5.1" +name = "blocking" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" dependencies = [ - "tinyvec", + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", ] [[package]] @@ -263,7 +594,7 @@ dependencies = [ "getrandom 0.2.17", "getrandom 0.3.4", "hex", - "indexmap 2.14.2", + "indexmap", "js-sys", "once_cell", "rand 0.9.5", @@ -285,6 +616,20 @@ name = "bytemuck" version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] [[package]] name = "byteorder" @@ -292,131 +637,101 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -dependencies = [ - "serde", -] [[package]] -name = "cairo-rs" -version = "0.18.5" +name = "calloop" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.13.1", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", + "bitflags 2.13.2", + "log", + "polling", + "rustix 0.38.44", + "slab", "thiserror 1.0.69", ] [[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.5" +name = "calloop" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e" dependencies = [ - "serde_core", + "bitflags 2.13.2", + "polling", + "rustix 1.1.5", + "tracing", ] [[package]] -name = "cargo-platform" -version = "0.1.9" +name = "calloop-wayland-source" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "serde", + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", ] [[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.20", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" +name = "calloop-wayland-source" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", + "calloop 0.14.5", + "rustix 1.1.5", + "wayland-backend", + "wayland-client", ] [[package]] name = "cc" -version = "1.4.5" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] [[package]] -name = "cesu8" -version = "1.1.0" +name = "cfg-if" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] -name = "cfb" -version = "0.7.3" +name = "cfg_aliases" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] -name = "cfg-expr" -version = "0.15.8" +name = "cgl" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" dependencies = [ - "smallvec", - "target-lexicon", + "libc", ] -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - [[package]] name = "chacha20" version = "0.10.2" @@ -439,7 +754,16 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", ] [[package]] @@ -448,16 +772,51 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "color" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec7c5eb7a16992b1904d76c517d170ab353b0e0b3d5a0c81a8a0cd1037893cf" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" -version = "4.6.8" +version = "4.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" dependencies = [ "bytes", "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.10.2" @@ -493,16 +852,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cookie" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" -dependencies = [ - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -531,12 +880,12 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.25.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", + "bitflags 1.3.2", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -544,12 +893,12 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.2.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", + "bitflags 1.3.2", + "core-foundation 0.9.4", "libc", ] @@ -588,9 +937,9 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] @@ -642,9 +991,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -660,58 +1009,25 @@ dependencies = [ ] [[package]] -name = "cssparser" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "ctor" -version = "0.8.0" +name = "ctutils" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" dependencies = [ - "ctor-proc-macro", - "dtor", + "cmov", ] [[package]] -name = "ctor-proc-macro" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" - -[[package]] -name = "ctutils" -version = "0.4.2" +name = "cursor-icon" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", -] +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "darling" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ "darling_core", "darling_macro", @@ -719,26 +1035,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] @@ -747,22 +1063,11 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" -[[package]] -name = "dbus" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" -dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", -] - [[package]] name = "defmt" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" dependencies = [ "bitflags 1.3.2", "defmt-macros", @@ -770,33 +1075,30 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" dependencies = [ "defmt-parser", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] name = "defmt-parser" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" dependencies = [ - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] name = "deranged" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "serde_core", -] +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" [[package]] name = "derive-syn-parse" @@ -811,13 +1113,13 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] @@ -850,7 +1152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "crypto-common 0.1.7", + "crypto-common 0.1.6", ] [[package]] @@ -866,25 +1168,10 @@ dependencies = [ ] [[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" +name = "dispatch" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dispatch2" @@ -892,10 +1179,10 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.13.1", - "block2", + "bitflags 2.13.2", + "block2 0.6.2", "libc", - "objc2", + "objc2 0.6.5", ] [[package]] @@ -906,149 +1193,322 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] -name = "dlopen2" -version = "0.8.2" +name = "dlib" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "ecolor" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0656a7530b9e2841ee7a5f983965bdd9840c32f4012f759cfd44b912cced6b34" +dependencies = [ + "bytemuck", + "emath", + "serde", +] + +[[package]] +name = "eframe" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ad41fac6e3149abb4866e93df37dbd33baa95db525c3368c944584e95a5dc2" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "pollster 1.0.1", + "profiling", + "raw-window-handle", + "ron", + "serde", + "static_assertions", + "wasm-bindgen", + "web-sys", + "web-time", + "wgpu", + "windows-sys 0.61.2", + "winit", +] + +[[package]] +name = "egui" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc938cc27cd911415e1e4d151bc56ff9df063d8db081374e87aeb805ea74b2ba" +dependencies = [ + "accesskit", + "ahash", + "bitflags 2.13.2", + "emath", + "epaint", + "itertools", + "log", + "nohash-hasher", + "profiling", + "ron", + "serde", + "smallvec", + "unicode-segmentation", + "web-sys", +] + +[[package]] +name = "egui-wgpu" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cef1f6d1bba40b09a4d0d62cb822b81030922cfbf65c5373398c3a3caafc5bd" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 2.0.21", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98466000559d66ba4db786a30448b01483946add4188e7f10f522a27b4fb89cc" +dependencies = [ + "accesskit_winit", + "arboard", + "bytemuck", + "egui", + "log", + "objc2 0.6.5", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", + "profiling", + "raw-window-handle", + "serde", + "smithay-clipboard", + "web-time", + "winit", ] [[package]] -name = "dlopen2_derive" -version = "0.4.3" +name = "egui_extras" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +checksum = "05cbdc785020861553f230f23d330ba1fb72613b9d8e0e457f3bdc8ba6db6dcf" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "ahash", + "egui", + "enum-map", + "itertools", + "log", + "profiling", ] [[package]] -name = "dom_query" -version = "0.27.0" +name = "egui_glow" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "606ed4167daf341e5753d703285c4c1d562324b2c07aa8f405cd7d066fe58f3b" dependencies = [ - "bit-set", - "cssparser", - "foldhash", - "html5ever", - "precomputed-hash", - "selectors", - "tendril", + "bytemuck", + "egui", + "glow", + "log", + "profiling", + "winit", ] [[package]] -name = "dotenvy" -version = "0.15.7" +name = "either" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" +dependencies = [ + "serde", +] [[package]] -name = "dpi" -version = "0.1.2" +name = "emath" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +checksum = "52c8d4141cf0f60fb2f7aefaf352d3ffa5171ef0ab09731c64391801e95e48f1" dependencies = [ + "bytemuck", "serde", ] [[package]] -name = "dtoa" -version = "1.0.11" +name = "endi" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] -name = "dtoa-short" -version = "0.3.5" +name = "enum-map" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" dependencies = [ - "dtoa", + "enum-map-derive", ] [[package]] -name = "dtor" -version = "0.3.0" +name = "enum-map-derive" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ - "dtor-proc-macro", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "dtor-proc-macro" -version = "0.0.6" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] [[package]] -name = "dunce" -version = "1.0.5" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "dyn-clone" -version = "1.0.20" +name = "enumn" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "either" -version = "1.18.0" +name = "env_filter" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ - "serde", + "log", + "regex", ] [[package]] -name = "embed-resource" -version = "3.0.11" +name = "env_logger" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.5+spec-1.1.0", - "vswhom", - "winreg", + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] -name = "embed_plist" -version = "1.2.2" +name = "epaint" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +checksum = "a0ec0308dc23130fc1a7d4623a8534c2ed069c6dc84e258ee4f3de99615aa1a3" +dependencies = [ + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "font-types", + "harfrust", + "log", + "nohash-hasher", + "parking_lot", + "profiling", + "self_cell", + "serde", + "skrifa", + "smallvec", + "unicode-general-category", + "unicode-segmentation", + "vello_cpu", +] [[package]] -name = "equivalent" -version = "1.0.2" +name = "epaint_default_fonts" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "773fa9c96dd0dbef887e39d0ed6177f141cce4d68e6041df77570aa3702dfa13" [[package]] -name = "erased-serde" -version = "0.4.10" +name = "equivalent" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "errno" @@ -1060,6 +1520,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" + [[package]] name = "etcetera" version = "0.11.0" @@ -1070,6 +1536,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "5.4.2" @@ -1080,12 +1555,28 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + [[package]] name = "fdeflate" version = "0.3.7" @@ -1096,20 +1587,16 @@ dependencies = [ ] [[package]] -name = "field-offset" -version = "0.3.6" +name = "fearless_simd" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] +checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693" [[package]] name = "find-msvc-tools" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "flate2" @@ -1133,18 +1620,22 @@ dependencies = [ "spin", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "font-types" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d" +dependencies = [ + "bytemuck", + "serde", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1163,7 +1654,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -1241,6 +1732,19 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.34" @@ -1249,7 +1753,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -1281,112 +1785,23 @@ dependencies = [ ] [[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" +name = "generic-array" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", + "typenum", + "version_check", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "gethostname" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "typenum", - "version_check", + "rustix 1.1.5", + "windows-link", ] [[package]] @@ -1431,158 +1846,156 @@ dependencies = [ ] [[package]] -name = "gio" -version = "0.18.4" +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glifo" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4a1bb24121291d27230c1b1b44e07d6a9b28cefdb32fe1581dfb84e14f940a" +dependencies = [ + "bytemuck", + "foldhash", + "hashbrown 0.17.1", + "log", + "peniko", + "skrifa", "smallvec", - "thiserror 1.0.69", + "vello_common", ] [[package]] -name = "gio-sys" -version = "0.18.1" +name = "glow" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "glib" -version = "0.18.5" +name = "glutin" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ - "bitflags 2.13.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", + "bitflags 2.13.2", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", "once_cell", - "smallvec", - "thiserror 1.0.69", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", ] [[package]] -name = "glib-macros" -version = "0.18.5" +name = "glutin-winit" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", ] [[package]] -name = "glib-sys" -version = "0.18.1" +name = "glutin_egl_sys" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" dependencies = [ - "libc", - "system-deps", + "gl_generator", + "windows-sys 0.52.0", ] [[package]] -name = "glob" -version = "0.3.4" +name = "glutin_glx_sys" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] [[package]] -name = "gobject-sys" -version = "0.18.0" +name = "glutin_wgl_sys" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" dependencies = [ - "glib-sys", - "libc", - "system-deps", + "gl_generator", ] [[package]] -name = "gtk" -version = "0.18.2" +name = "gpu-allocator" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.21", + "windows", ] [[package]] -name = "gtk-sys" -version = "0.18.2" +name = "guillotiere" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +checksum = "6b17e70c989c36bad147b27a58d148c0741c51448aa5653436547323e524d0ab" dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", + "euclid", ] [[package]] -name = "gtk3-macros" -version = "0.18.2" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "harfrust" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "read-fonts", + "smallvec", +] [[package]] name = "hashbrown" @@ -1600,6 +2013,9 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] [[package]] name = "hashlink" @@ -1610,18 +2026,18 @@ dependencies = [ "hashbrown 0.16.1", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + [[package]] name = "hex" version = "0.4.3" @@ -1630,9 +2046,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-net" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084e7bd6a377435d568f652153e571b50970d7ccc1d1eeec0519f834632287e1" +checksum = "c480823ed7c2c5d0f09c41020cb6b7c28029ce60ec42dc942158dcf22f8e0a4d" dependencies = [ "async-trait", "cfg-if", @@ -1643,9 +2059,9 @@ dependencies = [ "hickory-proto", "idna", "ipnet", - "jni 0.22.4", - "rand 0.10.2", - "thiserror 2.0.20", + "jni", + "rand 0.10.3", + "thiserror 2.0.21", "tinyvec", "tokio", "tracing", @@ -1654,19 +2070,19 @@ dependencies = [ [[package]] name = "hickory-proto" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2da0694c15b44c6f68a6b05e0233617008c54080e31d6eb848d858a9c5b38d" +checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d" dependencies = [ "data-encoding", "idna", "ipnet", - "jni 0.22.4", + "jni", "once_cell", "prefix-trie", - "rand 0.10.2", + "rand 0.10.3", "ring", - "thiserror 2.0.20", + "thiserror 2.0.21", "tinyvec", "tracing", "url", @@ -1674,9 +2090,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4f9f4603319422d482738f3f6fe5aac03157fdbfed1cd85a3ff45adb09072f" +checksum = "3f3da5255c95d5a716857d54b5b8f4e8d67c3484d3beaaaae2ce25063b3ba981" dependencies = [ "cfg-if", "futures-util", @@ -1684,16 +2100,16 @@ dependencies = [ "hickory-proto", "ipconfig", "ipnet", - "jni 0.22.4", + "jni", "moka", "ndk-context", "once_cell", "parking_lot", - "rand 0.10.2", + "rand 0.10.3", "resolv-conf", "smallvec", "system-configuration", - "thiserror 2.0.20", + "thiserror 2.0.21", "tokio", "tracing", ] @@ -1716,16 +2132,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "html5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" -dependencies = [ - "log", - "markup5ever", -] - [[package]] name = "http" version = "1.5.0" @@ -1767,22 +2173,21 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ "atomic-waker", "bytes", - "futures-channel", "futures-core", "http", "http-body", @@ -1796,14 +2201,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.9" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ "http", "hyper", "hyper-util", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", @@ -1812,25 +2218,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "futures-channel", "futures-util", "http", "http-body", + "httparse", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1845,7 +2254,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -1857,16 +2266,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png 0.17.16", -] - [[package]] name = "icu_collections" version = "2.3.0" @@ -1978,14 +2377,17 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "image" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", ] [[package]] @@ -1996,17 +2398,6 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", ] [[package]] @@ -2018,7 +2409,7 @@ dependencies = [ "socket2", "widestring", "windows-registry", - "windows-result 0.4.1", + "windows-result", "windows-sys 0.61.2", ] @@ -2032,65 +2423,56 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.18" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "javascriptcore-rs" -version = "1.1.2" +name = "itertools" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", + "either", ] [[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.35" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" dependencies = [ "defmt", "jiff-core", "jiff-static", - "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", - "windows-link 0.2.1", ] [[package]] name = "jiff-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ "defmt", + "log", ] [[package]] name = "jiff-static" -version = "0.2.35" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" dependencies = [ "jiff-core", "proc-macro2", @@ -2098,37 +2480,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "jiff-tzdb" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "jni" version = "0.22.4" @@ -2141,9 +2492,9 @@ dependencies = [ "jni-sys 0.4.1", "log", "simd_cesu8", - "thiserror 2.0.20", + "thiserror 2.0.21", "walkdir", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2188,47 +2539,24 @@ dependencies = [ ] [[package]] -name = "js-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" +name = "jobserver" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "serde", - "serde_json", + "getrandom 0.4.3", + "libc", ] [[package]] -name = "keyboard-types" -version = "0.7.0" +name = "js-sys" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ - "bitflags 2.13.1", - "serde", - "unicode-segmentation", + "cfg-if", + "futures-util", + "wasm-bindgen", ] [[package]] @@ -2247,52 +2575,48 @@ dependencies = [ ] [[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" +name = "khronos-egl" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ - "gtk-sys", + "libc", "libloading", - "once_cell", + "pkg-config", ] [[package]] -name = "libc" -version = "0.2.189" +name = "khronos_api" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] -name = "libdbus-sys" -version = "0.2.7" +name = "kurbo" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" dependencies = [ - "pkg-config", + "arrayvec", + "euclid", + "polycool", + "smallvec", ] +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "winapi", + "windows-link", ] [[package]] @@ -2303,34 +2627,49 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.23" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ + "bitflags 2.13.2", "libc", + "plain", + "redox_syscall 0.9.4", ] [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ "cc", "pkg-config", "vcpkg", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + [[package]] name = "linux-keyutils" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2343,6 +2682,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +[[package]] +name = "litrs" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70" + [[package]] name = "lock_api" version = "0.4.14" @@ -2360,9 +2705,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lru-slab" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] name = "macro_magic" @@ -2412,17 +2757,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "markup5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" -dependencies = [ - "log", - "tendril", - "web_atoms", -] - [[package]] name = "md-5" version = "0.11.0" @@ -2439,6 +2773,15 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -2448,12 +2791,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2476,9 +2813,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "wasi", @@ -2522,12 +2859,12 @@ checksum = "851fac73f7fe22f6a3ab87f720ce509cae7c9fd08e7dd27866cc232dee07ccf4" [[package]] name = "mongodb" -version = "3.9.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd64784a1fdbcf2a334717446edc583061159067a2e7cc326ee567ecf035f07b" +checksum = "af84443ae9878f59a6818686f339064479f42cdf293c4c637d8fc9c0a8c5eed6" dependencies = [ "base64 0.22.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "bson", "derive-where", "derive_more", @@ -2552,12 +2889,12 @@ dependencies = [ "serde_bytes", "serde_with", "sha1", - "sha2 0.11.0", + "sha2 0.11.1", "socket2", "stringprep", "strsim", "take_mut", - "thiserror 2.0.20", + "thiserror 2.0.21", "tokio", "tokio-rustls", "tokio-util", @@ -2568,9 +2905,9 @@ dependencies = [ [[package]] name = "mongodb-internal-macros" -version = "3.9.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f243f9039382cd06378f13980b79700236468b6fb22f24abfa99783e9569f1a3" +checksum = "504024bbb83ab1bf1512007f7288b54d3a6343043f3f619c26a153346773598d" dependencies = [ "macro_magic", "proc-macro2", @@ -2579,24 +2916,51 @@ dependencies = [ ] [[package]] -name = "muda" -version = "0.19.3" +name = "moxcms" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.17.1", + "indexmap", + "libm", + "log", + "naga-types", + "num-traits", "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.20", - "windows-sys 0.61.2", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.21", + "unicode-ident", +] + +[[package]] +name = "naga-types" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49" +dependencies = [ + "hashbrown 0.17.1", + "indexmap", + "rustc-hash 1.1.0", + "thiserror 2.0.21", ] [[package]] @@ -2605,7 +2969,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2630,10 +2994,10 @@ dependencies = [ ] [[package]] -name = "new_debug_unreachable" -version = "1.0.6" +name = "nohash-hasher" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "num-bigint" @@ -2667,6 +3031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2685,20 +3050,51 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", ] +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d5490aaf8f1d7cf7688dfa9b0ce07900e168852c45cd2c03f534dfd27cfd0b" +dependencies = [ + "objc-sys", + "objc2-encode", +] + [[package]] name = "objc2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" dependencies = [ "objc2-encode", - "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "libc", + "objc2 0.5.3", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -2707,32 +3103,48 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.13.1", - "block2", - "objc2", + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.5", "objc2-core-foundation", - "objc2-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-cloud-kit" -version = "0.3.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-foundation", + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", ] [[package]] name = "objc2-core-data" -version = "0.3.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "objc2", - "objc2-foundation", + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", ] [[package]] @@ -2741,9 +3153,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "dispatch2", - "objc2", + "objc2 0.6.5", ] [[package]] @@ -2752,81 +3164,126 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "dispatch2", - "objc2", + "objc2 0.6.5", "objc2-core-foundation", "objc2-io-surface", ] [[package]] name = "objc2-core-image" -version = "0.3.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", ] [[package]] name = "objc2-core-location" -version = "0.3.2" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "objc2", - "objc2-foundation", + "bitflags 2.13.2", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.3", ] [[package]] -name = "objc2-core-text" +name = "objc2-foundation" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.13.1", - "objc2", + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.5", "objc2-core-foundation", - "objc2-core-graphics", ] [[package]] -name = "objc2-encode" -version = "4.1.0" +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.5", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] [[package]] -name = "objc2-exception-helper" -version = "0.1.1" +name = "objc2-metal" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "cc", + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", ] [[package]] -name = "objc2-foundation" +name = "objc2-metal" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ - "bitflags 2.13.1", - "block2", - "objc2", - "objc2-core-foundation", + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.5", + "objc2-foundation 0.3.2", ] [[package]] -name = "objc2-io-surface" -version = "0.3.2" +name = "objc2-quartz-core" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-core-foundation", + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", ] [[package]] @@ -2835,55 +3292,79 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.13.1", - "objc2", + "bitflags 2.13.2", + "objc2 0.6.5", "objc2-core-foundation", - "objc2-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.3", + "objc2-foundation 0.2.2", ] [[package]] name = "objc2-ui-kit" -version = "0.3.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.13.1", - "block2", - "objc2", + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", "objc2-cloud-kit", "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", "objc2-core-image", "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core 0.2.2", + "objc2-symbols", + "objc2-uniform-type-identifiers", "objc2-user-notifications", ] [[package]] -name = "objc2-user-notifications" +name = "objc2-ui-kit" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.5", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", ] [[package]] -name = "objc2-web-kit" -version = "0.3.2" +name = "objc2-user-notifications" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.13.1", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-core-location", + "objc2-foundation 0.2.2", ] [[package]] @@ -2897,34 +3378,44 @@ dependencies = [ ] [[package]] -name = "option-ext" -version = "0.2.0" +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "pango" -version = "0.18.3" +name = "orbclient" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" dependencies = [ - "gio", - "glib", "libc", - "once_cell", - "pango-sys", + "libredox", ] [[package]] -name = "pango-sys" -version = "0.18.0" +name = "ordered-float" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", ] [[package]] @@ -2951,9 +3442,9 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2965,6 +3456,19 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "peniko" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c8299360d2e998bdb106dc0a6cd71dcc5f4df51df1b620361bf50e283cca6" +dependencies = [ + "bytemuck", + "color", + "kurbo", + "linebender_resource_handle", + "smallvec", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2982,16 +3486,6 @@ dependencies = [ "serde", ] -[[package]] -name = "phf_codegen" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" -dependencies = [ - "phf_generator", - "phf_shared", -] - [[package]] name = "phf_generator" version = "0.13.1" @@ -3024,12 +3518,43 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.34" @@ -3037,25 +3562,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] -name = "plist" -version = "1.10.1" +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" -dependencies = [ - "base64 0.23.1", - "indexmap 2.14.2", - "quick-xml", - "serde", - "time", -] +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "png" -version = "0.17.16" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.2", "crc32fast", "fdeflate", "flate2", @@ -3063,16 +3581,38 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.18.1" +name = "polling" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "bitflags 2.13.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.5", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "pollster" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6355899e1c9462875b6757c79f3caa011a1fdae12bbb1a2e72dd1f234f8336" + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", ] [[package]] @@ -3101,9 +3641,9 @@ dependencies = [ [[package]] name = "powerfmt" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" [[package]] name = "ppv-lite86" @@ -3114,12 +3654,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "prefix-trie" version = "0.8.4" @@ -3132,91 +3666,71 @@ dependencies = [ ] [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "presser" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", + "toml_edit", ] [[package]] -name = "proc-macro-crate" -version = "3.5.0" +name = "proc-macro2" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "unicode-ident", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "profiling" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "pxfm" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" [[package]] -name = "proc-macro2" -version = "1.0.107" +name = "quick-error" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.42.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.3", "rustls", "socket2", - "thiserror 2.0.20", + "thiserror 2.0.21", "tokio", "tracing", "web-time", @@ -3231,14 +3745,14 @@ dependencies = [ "bytes", "getrandom 0.4.3", "lru-slab", - "rand 0.10.2", + "rand 0.10.3", "rand_pcg", "ring", - "rustc-hash", + "rustc-hash 2.1.3", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.20", + "thiserror 2.0.21", "tinyvec", "tracing", "web-time", @@ -3246,9 +3760,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" dependencies = [ "cfg_aliases", "libc", @@ -3297,9 +3811,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", "getrandom 0.4.3", @@ -3340,6 +3854,12 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3347,93 +3867,96 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "raw-window-metal" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" dependencies = [ - "bitflags 2.13.1", + "objc2 0.6.5", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] -name = "redox_users" -version = "0.5.2" +name = "read-fonts" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.20", + "bytemuck", + "font-types", + "once_cell", ] [[package]] -name = "redrob-core" -version = "0.2.4" +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "base64 0.22.1", - "bigdecimal", - "chrono", - "fs2", - "futures-util", - "keyring", - "mongodb", - "redrob-engine-client", - "reqwest 0.12.28", - "secrecy", - "serde", - "serde_json", - "sqlx", - "tempfile", - "thiserror 2.0.20", - "tokio", - "url", - "uuid", + "bitflags 1.3.2", ] [[package]] -name = "redrob-engine-client" -version = "0.1.0" -source = "git+https://github.com/redrob-labs/redrob-code?tag=v0.6.3#ef3e511afb1107fd0066fa95cb29256b7d020d3c" +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "base64 0.22.1", - "dirs", - "log", - "serde", - "serde_json", - "ureq", + "bitflags 2.13.2", +] + +[[package]] +name = "redox_syscall" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +dependencies = [ + "bitflags 2.13.2", ] [[package]] name = "redrob-query" -version = "0.2.4" +version = "0.3.0" dependencies = [ - "redrob-core", + "arboard", + "eframe", + "egui", + "egui_extras", + "env_logger", + "log", + "redrob-query-core", + "rfd", "serde", "serde_json", - "tauri", - "tauri-build", "tokio", "uuid", + "webbrowser", + "winresource", ] [[package]] -name = "ref-cast" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +name = "redrob-query-core" +version = "0.3.0" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "base64 0.22.1", + "bigdecimal", + "chrono", + "fs2", + "futures-util", + "keyring", + "mongodb", + "reqwest", + "secrecy", + "serde", + "serde_json", + "sqlx", + "tempfile", + "thiserror 2.0.21", + "tokio", + "url", + "uuid", ] [[package]] @@ -3465,6 +3988,12 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] name = "reqwest" version = "0.12.28" @@ -3487,6 +4016,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -3501,51 +4031,44 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.4.2", + "wasm-streams", "web-sys", "webpki-roots", ] [[package]] -name = "reqwest" -version = "0.13.4" +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "rfd" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", + "block2 0.6.2", + "dispatch2", "js-sys", + "libc", "log", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", + "pollster 0.4.0", + "raw-window-handle", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", "web-sys", + "windows-sys 0.61.2", ] -[[package]] -name = "resolv-conf" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" - [[package]] name = "ring" version = "0.17.14" @@ -3560,6 +4083,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ron" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3" +dependencies = [ + "bitflags 2.13.2", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.3" @@ -3587,14 +4130,27 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -3613,6 +4169,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + [[package]] name = "rustls-pki-types" version = "1.15.1" @@ -3656,55 +4224,19 @@ dependencies = [ ] [[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.2" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", + "windows-sys 0.61.2", ] [[package]] -name = "schemars_derive" -version = "0.8.22" +name = "scoped-tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.119", -] +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -3728,7 +4260,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -3741,7 +4273,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3759,33 +4291,16 @@ dependencies = [ ] [[package]] -name = "selectors" -version = "0.36.1" +name = "self_cell" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" -dependencies = [ - "bitflags 2.13.1", - "cssparser", - "derive_more", - "log", - "new_debug_unreachable", - "phf", - "phf_codegen", - "precomputed-hash", - "rustc-hash", - "servo_arc", - "smallvec", -] +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "serde" @@ -3797,18 +4312,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - [[package]] name = "serde_bytes" version = "0.11.19" @@ -3836,27 +4339,17 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ - "indexmap 2.14.2", + "foldhash", + "indexmap", "itoa", "memchr", "serde", @@ -3872,23 +4365,14 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", + "syn 3.0.9", ] [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -3907,66 +4391,24 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.22.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" dependencies = [ - "base64 0.22.1", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.2", - "jiff", - "schemars 0.9.0", - "schemars 1.2.2", "serde_core", - "serde_json", "serde_with_macros", - "time", ] [[package]] name = "serde_with_macros" -version = "3.22.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "servo_arc" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" -dependencies = [ - "stable_deref_trait", + "syn 3.0.9", ] [[package]] @@ -3993,9 +4435,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" dependencies = [ "cfg-if", "cpufeatures 0.3.1", @@ -4042,9 +4484,19 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + +[[package]] +name = "skrifa" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts", +] [[package]] name = "slab" @@ -4052,71 +4504,104 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" -version = "1.16.0" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" dependencies = [ "serde", ] [[package]] -name = "socket2" -version = "0.6.5" +name = "smithay-client-toolkit" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ + "bitflags 2.13.2", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", "libc", - "windows-sys 0.61.2", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] -name = "softbuffer" -version = "0.4.8" +name = "smithay-client-toolkit" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", + "bitflags 2.13.2", + "calloop 0.14.5", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.5", + "thiserror 2.0.21", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", ] [[package]] -name = "soup3" -version = "0.5.0" +name = "smol_str" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", + "serde", ] [[package]] -name = "soup3-sys" -version = "0.5.0" +name = "socket2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", "libc", - "system-deps", + "windows-sys 0.61.2", ] [[package]] @@ -4128,6 +4613,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.13.2", +] + [[package]] name = "sqlx" version = "0.9.0" @@ -4162,7 +4656,7 @@ dependencies = [ "futures-util", "hashbrown 0.16.1", "hashlink", - "indexmap 2.14.2", + "indexmap", "log", "memchr", "percent-encoding", @@ -4171,7 +4665,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.20", + "thiserror 2.0.21", "tokio", "tokio-stream", "tracing", @@ -4202,7 +4696,7 @@ dependencies = [ "cfg-if", "dotenvy", "either", - "heck 0.5.0", + "heck", "hex", "proc-macro2", "quote", @@ -4225,7 +4719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ "bigdecimal", - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "bytes", "chrono", @@ -4240,9 +4734,9 @@ dependencies = [ "percent-encoding", "serde", "sha1", - "sha2 0.11.0", + "sha2 0.11.1", "sqlx-core", - "thiserror 2.0.20", + "thiserror 2.0.21", "tracing", "uuid", ] @@ -4256,7 +4750,7 @@ dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "chrono", "crc", @@ -4273,14 +4767,14 @@ dependencies = [ "md-5", "memchr", "num-bigint", - "rand 0.10.2", + "rand 0.10.3", "serde", "serde_json", - "sha2 0.11.0", + "sha2 0.11.1", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.20", + "thiserror 2.0.21", "tracing", "uuid", "whoami", @@ -4306,7 +4800,7 @@ dependencies = [ "percent-encoding", "serde", "sqlx-core", - "thiserror 2.0.20", + "thiserror 2.0.21", "tracing", "url", "uuid", @@ -4319,28 +4813,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "string_cache" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - -[[package]] -name = "string_cache_codegen" -version = "0.6.1" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", -] +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stringprep" @@ -4360,31 +4836,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "swift-rs" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45c444e496845d3f2a351146bff59aae4975b2280238df1dfaa0c7d1846f38e" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -4399,9 +4854,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.5" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -4419,13 +4874,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] @@ -4434,7 +4889,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4449,19 +4904,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - [[package]] name = "tagptr" version = "0.2.0" @@ -4474,282 +4916,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -[[package]] -name = "tao" -version = "0.35.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" -dependencies = [ - "bitflags 2.13.1", - "block2", - "core-foundation 0.10.1", - "core-graphics", - "crossbeam-channel", - "dbus", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni 0.21.1", - "libc", - "log", - "ndk", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "once_cell", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni 0.21.1", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest 0.13.4", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.20", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png 0.17.16", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2 0.10.9", - "syn 2.0.119", - "tauri-utils", - "thiserror 2.0.20", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-runtime" -version = "2.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni 0.21.1", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.20", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" -dependencies = [ - "gtk", - "http", - "jni 0.21.1", - "log", - "objc2", - "objc2-app-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dom_query", - "dunce", - "glob", - "http", - "infer", - "json-patch", - "log", - "memchr", - "phf", - "plist", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.20", - "toml 1.1.5+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" -dependencies = [ - "dunce", - "embed-resource", - "toml 1.1.5+spec-1.1.0", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -4757,19 +4929,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", - "rustix", + "rustix 1.1.5", "windows-sys 0.61.2", ] [[package]] -name = "tendril" -version = "0.5.1" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "new_debug_unreachable", + "winapi-util", ] [[package]] @@ -4783,11 +4955,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.21", ] [[package]] @@ -4803,13 +4975,27 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", ] [[package]] @@ -4863,24 +5049,15 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.53.1" +version = "1.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" dependencies = [ "bytes", "libc", @@ -4900,14 +5077,14 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] name = "tokio-rustls" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", @@ -4926,9 +5103,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ "bytes", "futures-core", @@ -4941,123 +5118,54 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.14.2", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml" -version = "1.1.5+spec-1.1.0" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ - "indexmap 2.14.2", + "indexmap", "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "serde_spanned", + "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.4", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.14.2", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.14.2", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.17+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" dependencies = [ - "indexmap 2.14.2", - "toml_datetime 1.1.1+spec-1.1.0", + "indexmap", + "toml_datetime", "toml_parser", - "winnow 1.0.4", + "winnow", ] [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ - "winnow 1.0.4", + "winnow", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tower" @@ -5080,7 +5188,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "futures-util", "http", @@ -5136,34 +5244,21 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tray-icon" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.20", - "windows-sys 0.61.2", -] - [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.3", +] + [[package]] name = "typed-builder" version = "0.22.0" @@ -5197,44 +5292,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "uds_windows" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ - "unic-common", + "memoffset", + "tempfile", + "windows-sys 0.61.2", ] [[package]] @@ -5243,11 +5308,17 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-normalization" @@ -5270,6 +5341,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -5282,31 +5359,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" -dependencies = [ - "base64 0.23.1", - "log", - "percent-encoding", - "ureq-proto", - "utf8-zero", -] - -[[package]] -name = "ureq-proto" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" -dependencies = [ - "base64 0.23.1", - "http", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.8" @@ -5320,35 +5372,23 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf8-zero" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" - [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" -version = "1.26.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5363,36 +5403,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "version_check" -version = "0.9.5" +name = "vello_common" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "b2e9aed918117e8152c9eddfd8362d73c465c23f26c44786aa331707b8a64fa2" +dependencies = [ + "bytemuck", + "fearless_simd", + "guillotiere", + "log", + "peniko", + "smallvec", + "thiserror 2.0.21", +] [[package]] -name = "vswhom" +name = "vello_cpu" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +checksum = "ac7349e1f55f6b801c7c277958df4ea53e7f20f21e8014910ad888b2ecda93ea" dependencies = [ - "libc", - "vswhom-sys", + "bytemuck", + "glifo", + "hashbrown 0.17.1", + "vello_common", ] [[package]] -name = "vswhom-sys" -version = "0.1.3" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -5406,9 +5447,9 @@ dependencies = [ [[package]] name = "want" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ "try-lock", ] @@ -5430,9 +5471,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -5443,19 +5484,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.78" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ "js-sys", + "tokio", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5463,22 +5505,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] @@ -5497,23 +5539,145 @@ dependencies = [ ] [[package]] -name = "wasm-streams" -version = "0.5.0" +name = "wayland-backend" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "cc", + "downcast-rs", + "rustix 1.1.5", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.2", + "rustix 1.1.5", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.13.2", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.5", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.105" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", @@ -5530,104 +5694,217 @@ dependencies = [ ] [[package]] -name = "web_atoms" -version = "0.2.6" +name = "webbrowser" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" +checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", + "jni", + "log", + "ndk-context", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "url", + "web-sys", ] [[package]] -name = "webkit2gtk" -version = "2.0.2" +name = "webpki-roots" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527ccdf43dd5b2e8676eed9984ce00e2bbb0a1b85b70c1969dcb6cd2eb55ab9e" +dependencies = [ + "arrayvec", + "bitflags 2.13.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.17.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c018fce9b6270aa203c2fdd56f3cce996713534bd757e4ea58c8560b121f14" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.2", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.17.1", + "indexmap", + "log", + "naga", + "naga-types", "once_cell", - "soup3", - "webkit2gtk-sys", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.21", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", ] [[package]] -name = "webkit2gtk-sys" -version = "2.0.2" +name = "wgpu-core-deps-apple" +version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +checksum = "061f3d319a40d39d00b1ecc2c33b89fe21d4e6fe01859df3500a3a8ecccd6b68" dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", + "wgpu-hal", ] [[package]] -name = "webpki-roots" -version = "1.0.9" +name = "wgpu-core-deps-emscripten" +version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "d98b86cf4abf524a902dd35f18ca6a3f08fc2ae9847c8f10b48e30491b1f0b86" dependencies = [ - "rustls-pki-types", + "wgpu-hal", ] [[package]] -name = "webview2-com" -version = "0.38.2" +name = "wgpu-core-deps-wasm" +version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +checksum = "d260ca2ca8adc654f513a869bf3cac0b47f5b8d3f87803d6942015f8de597aae" dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "wgpu-hal", ] [[package]] -name = "webview2-com-macros" -version = "0.8.1" +name = "wgpu-core-deps-windows-linux-android" +version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +checksum = "7586165fd5f6d881cb9ce4bb71f40d6caab2c0f1837e3fc1d9788a197fb6004f" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "wgpu-hal", ] [[package]] -name = "webview2-com-sys" -version = "0.38.2" +name = "wgpu-hal" +version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58" dependencies = [ - "thiserror 2.0.20", + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.2", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "hashbrown 0.17.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga", + "naga-types", + "ndk-sys", + "objc2 0.6.5", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "static_assertions", + "thiserror 2.0.21", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", "windows", - "windows-core 0.61.2", + "windows-core", + "windows-result", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f62e73117bb7a62bfd9c5a5841438a823f6566c6442a808ee269d2d055c081" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99dad6f1fbdbbdb4c278a6508b059d44688f5cebddf78d005a46a31340269286" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "js-sys", + "log", + "naga-types", + "raw-window-handle", + "static_assertions", + "web-sys", ] [[package]] @@ -5668,59 +5945,30 @@ dependencies = [ ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", - "windows-link 0.1.3", "windows-numerics", ] [[package]] name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -5731,19 +5979,19 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] name = "windows-future" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core", + "windows-link", "windows-threading", ] @@ -5769,12 +6017,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -5783,12 +6025,12 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core", + "windows-link", ] [[package]] @@ -5797,18 +6039,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -5817,16 +6050,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -5835,16 +6059,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-link", ] [[package]] @@ -5880,22 +6095,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-link", ] [[package]] @@ -5920,7 +6120,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -5933,28 +6133,13 @@ dependencies = [ [[package]] name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.7" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5967,12 +6152,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5985,12 +6164,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6015,12 +6188,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6033,12 +6200,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6051,12 +6212,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6069,12 +6224,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6088,20 +6237,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "winnow" -version = "0.5.40" +name = "winit" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ - "memchr", + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.13.2", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.3", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit 0.2.2", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", ] -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - [[package]] name = "winnow" version = "1.0.4" @@ -6112,13 +6297,13 @@ dependencies = [ ] [[package]] -name = "winreg" -version = "0.55.0" +name = "winresource" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b" dependencies = [ - "cfg-if", - "windows-sys 0.59.0", + "toml", + "version_check", ] [[package]] @@ -6133,50 +6318,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" -[[package]] -name = "wry" -version = "0.55.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dom_query", - "dpi", - "dunce", - "gdkx11", - "gtk", - "http", - "javascriptcore-rs", - "jni 0.21.1", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2 0.10.9", - "soup3", - "tao-macros", - "thiserror 2.0.20", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - [[package]] name = "wyz" version = "0.5.1" @@ -6187,26 +6328,68 @@ dependencies = [ ] [[package]] -name = "x11" +name = "x11-dl" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ "libc", + "once_cell", "pkg-config", ] [[package]] -name = "x11-dl" -version = "2.21.0" +name = "x11rb" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ + "as-raw-xcb-connection", + "gethostname", "libc", + "libloading", "once_cell", - "pkg-config", + "rustix 1.1.5", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.2", + "dlib", + "log", + "once_cell", + "xkeysym", ] +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + [[package]] name = "yoke" version = "0.8.3" @@ -6220,30 +6403,136 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "synstructure", ] +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.5", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.9", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" dependencies = [ "proc-macro2", "quote", @@ -6261,21 +6550,21 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "synstructure", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" [[package]] name = "zerotrie" @@ -6307,17 +6596,73 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] name = "zlib-rs" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" [[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.9", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.9", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 6c5faa8..11bbff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,46 @@ [workspace] -members = ["crates/redrob-core", "src-tauri"] -resolver = "2" +resolver = "3" +members = ["crates/*", "apps/*"] [workspace.package] -version = "0.2.4" +version = "0.3.0" edition = "2024" license = "GPL-3.0-or-later" -rust-version = "1.94" +rust-version = "1.95" authors = ["Janghoon Lee (Redrob) and contributors"] repository = "https://github.com/redrob-labs/redrob-query" homepage = "https://github.com/redrob-labs/redrob-query" +[workspace.dependencies] +arboard = { version = "3.6", default-features = false } +base64 = "0.22" +bigdecimal = "0.4" +chrono = { version = "0.4", features = ["serde"] } +eframe = { version = "0.36", default-features = false } +egui = { version = "0.36", default-features = false } +egui_extras = { version = "0.36", default-features = false } +env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] } +fs2 = "0.4" +futures-util = "0.3" +keyring = { version = "3", features = ["apple-native", "windows-native", "linux-native"] } +log = "0.4" +mongodb = "3" +# `rustls-tls` bundles the webpki roots; `rustls-tls-native-roots` adds the OS store, so a corporate +# TLS-inspecting proxy's root (installed on Windows, not in webpki) still verifies; `system-proxy` +# reads the OS proxy settings, which `default-features = false` had switched off. +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "rustls-tls-native-roots", "system-proxy", "stream"] } +rfd = "0.17" +secrecy = { version = "0.10", features = ["serde"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls-ring", "postgres", "mysql", "sqlite", "chrono", "bigdecimal", "uuid", "json"] } +tempfile = "3" +thiserror = "2" +tokio = { version = "1", features = ["sync", "time", "rt-multi-thread", "macros"] } +url = { version = "2", features = ["serde"] } +uuid = { version = "1", features = ["v4", "serde"] } +webbrowser = "1" + [workspace.lints.rust] unsafe_code = "forbid" @@ -20,3 +50,27 @@ pedantic = { level = "warn", priority = -1 } module_name_repetitions = "allow" missing_errors_doc = "allow" missing_panics_doc = "allow" +too_many_lines = "allow" + +[profile.dev.package."*"] +opt-level = 2 + +[profile.dev] +opt-level = 1 + +# CI gates compile with `--profile ci`: no optimisation anywhere (the dev profile optimises every +# dependency at level 2, which is what makes a cold `cargo test` take a quarter of an hour). +[profile.ci] +inherits = "dev" +opt-level = 0 +debug = "line-tables-only" +incremental = false + +[profile.ci.package."*"] +opt-level = 0 + +[profile.release] +lto = "thin" +codegen-units = 1 +panic = "abort" +strip = true diff --git a/DESIGN_SYSTEM_PIN.json b/DESIGN_SYSTEM_PIN.json deleted file mode 100644 index 5e160ba..0000000 --- a/DESIGN_SYSTEM_PIN.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "_why": [ - "@redrob-labs/ui deliberately ships no logo file: its Mark component takes src and darkSrc, so the", - "host supplies the artwork. There is no version to resolve, so these are pinned by sha256 and by", - "their path in the design system delivery.", - "", - "What the hash is FOR. 10-logo.md: the artwork is 'Never redrawn, restretched, recolored or", - "otherwise modified', and lists recoloring, substituting the gradient and adding an effect under", - "Incorrect usage. A brand file is the one asset here that a well-meaning edit ruins silently: nudge", - "the blue, flatten the gradient, re-export smaller, and every gate still passes because no build", - "reads a PNG's pixels. The hash is what fails.", - "", - "This repo shipped src/assets/redrob-mark-512.png, whose sha256 matched none of the five symbol", - "variants in the delivery and which carried no alpha channel -- a hand-made approximation. It is", - "deleted; both canonical grounds are vendored here instead." - ], - "package": "@redrob-labs/ui", - "version": "1.0.2", - "brand_assets": [ - { - "file": "src/assets/brand/redrob-symbol.png", - "delivery_path": "assets/Symbol/redrob-symbol.png", - "ground": "white and light", - "sha256": "6069834a96076b2dee6d26f189f0e8b09513f15012cfad49498ac4e0a049e09e" - }, - { - "file": "src/assets/brand/redrob-symbol-solid-white.png", - "delivery_path": "assets/Symbol/redrob-symbol-solid-white.png", - "ground": "redrob-black and dark", - "sha256": "78a8224f61f44a6bc91a2f3479b4082e63b30e1dc898d55a4c6118d15191daba" - } - ], - "product_band": { - "_why": [ - "20-color.md closes the product spectrum at seven and states there is no eighth product colour.", - "10-logo.md's way out is that a new product ships as a feature of the product whose band it fits.", - "Query is a developer tool over a database, which is Code's band." - ], - "band": "code", - "tile": "#02A252" - } -} diff --git a/README.ko.md b/README.ko.md index c71de79..d64285d 100644 --- a/README.ko.md +++ b/README.ko.md @@ -2,153 +2,164 @@ [English](./README.md) · **한국어** -Redrob Query는 Rust 1.94, Tauri 2, React 19, TypeScript로 만든 JVM 없는 AI 보조 데이터베이스 작업공간입니다. PostgreSQL, MySQL, SQLite, MongoDB용 데스크톱 작업공간으로, 기본은 읽기만 하고 허락한 연결에서만 고칩니다. 결정적인 인메모리 샘플 데이터만으로 동작하는 브라우저 데모도 제공합니다. +Redrob Query는 PostgreSQL, MySQL, SQLite, MongoDB를 위한 에이전트형 데이터베이스 클라이언트입니다. +네이티브 창 하나(Rust, wgpu 위의 egui)에서 기본은 읽기만 하고, 허락한 곳에서만 변경을 검토한 뒤 +고치며, 옆에 있는 에이전트가 쿼리 초안을 써 줍니다. JVM도, 브라우저 런타임도, 웹 데모도 없습니다. +로그인은 [Redrob Launcher](https://github.com/redrob-labs/redrob-launcher)에서 한 번만 하면 이 앱이 +그 계정을 읽습니다. -워크플로는 DBeaver Community를 참고했지만 Redrob Query는 처음부터 새로 구현한 것입니다. DBeaver 소스 코드나 브랜딩이 들어 있지 않고, Eclipse RCP·OSGi·JDBC·Java·JVM 런타임도 없습니다. +워크플로는 DBeaver Community를 참고했고, 엔진의 일부는 Beekeeper Studio Community Edition에서 +포팅했습니다([NOTICE](NOTICE), [UPSTREAM_NOTICES.md](UPSTREAM_NOTICES.md) 참고). Eclipse RCP·OSGi· +JDBC·Java·Electron은 들어 있지 않습니다. ## 출하 범위 | 영역 | 기능 | |---|---| -| 프로필 | 사용자 프로필 생성·테스트·편집·연결·연결 해제·삭제. 내장 프로필은 변경 불가 | -| PostgreSQL | 신원을 검증하는 TLS, 메타데이터, 한계가 정해진 SQL 읽기, 네이티브 스칼라 디코딩, 검토 후 편집 | -| MySQL | 신원을 검증하는 TLS, 메타데이터, 한계가 정해진 SQL 읽기, unsigned/decimal/시간/BIT 디코딩, 검토 후 편집 | -| SQLite | 편집을 허락하기 전에는 읽기 전용(`mode=ro`)으로 여는 파일 프로필, 메타데이터, 가드가 걸린 읽기, 검토 후 편집. 네이티브 데모는 `:memory:` 사용 | -| MongoDB | 표준/SRV 프로필, 별도 `authSource`, 컬렉션, 최대 25개 문서 필드 샘플링, 엄격한 `find`/`aggregate`/explain JSON | -| 결과 | 25/50/100/250행 페이징, 타입이 있는 가상 스크롤 행, 현재 페이지 정렬·필터, 열 표시 설정, 열·행 메뉴, 상세 패널, 실행 메시지, 눈에 보이는 CSV 내보내기 | -| 편집 | 연결마다 "Allow edits"를 켜기 전에는 꺼져 있습니다. 셀 고치기, 행 추가·삭제, 표 만들기·열 추가. 모든 변경은 쌓아 두고 검토한 뒤 한 트랜잭션으로 적용하며, 문장마다 정확히 한 행만 바꾸지 않으면 전부 되돌립니다. 고칠 수 없는 결과는 이유를 보여 줍니다 | -| 쿼리 작업공간 | 연결이 소유하는 SQL/MQL 탭, 엔진별 시작 쿼리, 저장된 쿼리(Save / Ctrl+S), 읽기 전용 문장 여러 개를 한 번에 실행하고 결과 고르기, 고정한 표, 로컬 데스크톱 복원, 한계가 정해진 실행 이력, 포매팅, 단축키 | -| Redrob AI | 모델 `auto`를 통한 엔진 인식 SQL/MQL 생성과 설명 | -| 브라우저 데모 | PostgreSQL/MySQL/SQLite/Mongo 샘플 워크플로, 로컬 AI 응답, 원자적 인메모리 관계형 편집 리뷰 | - -이 릴리스는 SQL Server를 지원하지 않습니다. 연결 UI에 없고, 프로필 검증과 AI 검증 양쪽에서 거부합니다. - -데스크톱 연결은 그 연결에서 "Allow edits"를 켜기 전까지 읽기 전용입니다. 켜도 쌓인 변경을 검토하고 Apply를 누르기 전에는 아무것도 쓰지 않습니다. 셀 수정은 값을 바인딩한 `UPDATE … WHERE <기본 키> = ?`가 되고, 행 추가·삭제도 같은 방식이며, 전체가 한 트랜잭션으로 실행되어 어느 한 문장이라도 정확히 한 행을 바꾸지 않으면 되돌립니다. 결과에 단일 열 기본 키가 들어 있는, 표 하나를 그대로 읽는 쿼리만 고칠 수 있습니다. 브라우저 데모의 편집은 프로필별 인메모리 픽스처를 바꾸며, 새로고침하면 사라집니다. +| 프로필 | 내장 **Demo SQLite**(`:memory:`, 오프라인에서 동작)와 사용자 프로필: 생성·테스트·편집·연결·연결 해제·삭제. 비밀번호는 파일이 아니라 OS 키링에 저장 | +| PostgreSQL / MySQL | 신원을 검증하는 TLS, 스키마별로 묶인 표와 뷰, 열, 기본 키·외래 키, 인덱스, 한계가 정해진 읽기 전용 SQL, 검토 후 편집 | +| SQLite | 파일 프로필(편집을 허락하기 전에는 `mode=ro`), 표, 열, 테이블 값 pragma로 읽는 키와 인덱스, 검토 후 편집 | +| MongoDB | 표준/SRV 프로필, `authSource`, 25개 문서 필드 샘플링이 붙은 컬렉션, 엄격한 `find` / `aggregate` / `explain` 요청 본문, 컬렉션 인덱스, JSON 문서 보기 | +| 편집기 | 연결별 탭, SQL과 Mongo JSON 색칠, Ctrl+Enter로 선택 영역 또는 커서가 있는 문장 실행(읽기 전용 배치는 문장 하나씩), Ctrl+Shift+F 포매팅, Ctrl+S로 텍스트를 파일에 저장, 엔진별 시작 쿼리, Mongo 요청 스트립 | +| 결과 | 가상 스크롤 행, 가져온 페이지 안에서 정렬·필터, 열 표시 설정, 셀 복사, TSV / JSON 복사·저장, 탭별 행 한도 | +| 편집 | 셀을 더블클릭해 새 값을 쌓고, 행 메뉴에서 삭제하고, Changes 패널에서 쌓인 변경을 검토한 뒤 Apply가 한 트랜잭션으로 실행합니다. 어느 문장이라도 정확히 한 행만 바꾸지 않으면 전부 되돌립니다. 단일 열 기본 키가 있는 표 하나의 결과와 읽기 전용이 아닌 프로필이 필요합니다 | +| 구조 | PK / FK / not-null 표시가 붙은 열, 외래 키, 인덱스. 고정된 타입 목록 위의 폼으로 표 만들기·열 추가 | +| Ask | 원하는 쿼리를 말로 설명하면 활성 탭의 텍스트만 맥락으로 함께 가고, 결과 행과 자격 증명은 절대 가지 않습니다. 답의 ` ```sql ` / ` ```mql ` 블록은 한 번 눌러 편집기에 넣습니다 | +| 작업공간 | 시작할 때 최대 30개 탭과 50개 이력을 복원(`workspace.json`), 창 위치는 `ui.ron`. `REDROB_QUERY_NO_PREFS=1`이면 아무것도 남기지 않음 | + +이 릴리스는 SQL Server를 지원하지 않습니다. 편집기에 입력한 쓰기 문장은 엔진의 읽기 전용 게이트가 +거부합니다. 쓰기 경로는 Changes 패널과 구조 폼 둘뿐입니다. ## 구조 ```text -React 19 + Monaco + TanStack Virtual - │ - Zustand 작업공간 상태 - │ DataBridge - ┌─────┴───────────┐ - DemoBridge Tauri IPC 허용목록 - (브라우저 메모리) │ - redrob-core (Rust) - ┌─────────┼──────────┐ - SQLx 구체 구현 MongoDB Redrob AI - PG/MySQL/SQLite 드라이버 HTTPS 클라이언트 +apps/redrob-query (wgpu 위의 eframe / egui 0.36) + profiles · schema · editor · grid · changes · structure · Ask 도크 + │ Workspace (옛 명령 하나당 메서드 하나, Pending 핸들로 답) +crates/redrob-query-core + DataService ── SQLx (PostgreSQL / MySQL / SQLite) · mongodb · reqwest (콘솔) + query.rs 읽기 전용 분류 · cell_edits.rs · ddl.rs · keys.rs · profile_store.rs · secret.rs + account.rs: Redrob Launcher의 공용 account.json ``` -[아키텍처](docs/ARCHITECTURE.md), [보안](docs/SECURITY.md), [데모 안내](docs/DEMO.md), [DBeaver 워크플로 대응표](docs/DBEAVER-PORTING-MAP.md)를 함께 보세요. - -## 빠른 시작: 브라우저 데모 - -브라우저 데모는 데이터베이스나 Redrob 서비스에 접속하지 않고, 작업공간을 저장하지도 않습니다. - -```bash -npm ci -npm run dev -``` +[아키텍처](docs/ARCHITECTURE.md), [보안](docs/SECURITY.md), [DBeaver 워크플로 맵](docs/DBEAVER-PORTING-MAP.md)을 +참고하세요. -`http://127.0.0.1:1420`을 엽니다. 데모는 연결된 **Acme Warehouse** 샘플 프로필로 시작합니다. 결정적인 메타데이터와 쿼리, 기본 50행 크기에서 두 페이지 분량의 결과, 로컬 AI 응답, CSV 내보내기, 프로필 수명주기 시뮬레이션, 리뷰를 거치는 인메모리 관계형 편집을 지원합니다. +## 설치 -요구 사항: Node.js 22.12+, npm 11+. +모든 릴리스는 Redrob Launcher가 읽는 이름으로 다음을 싣습니다. -## 데스크톱 개발 +| 플랫폼 | 자산 | +|---|---| +| Linux x86_64 | `redrob-query--linux-x86_64.tar.gz`, `redrob-query--linux-x86_64.deb` | +| macOS arm64 | `redrob-query--macos-arm64.dmg`, `redrob-query--macos-arm64.zip` (서명·공증·스테이플) | +| Windows x64 | `redrob-query--windows-x64-setup.exe` (영어 + 한국어 설치 프로그램), `redrob-query--windows-x64-portable.zip` (서명) | -1. Node.js 22.12+, npm 11+, 그리고 [Tauri v2 사전 요구 사항](https://v2.tauri.app/start/prerequisites/)의 플랫폼 패키지를 설치합니다. -2. rustup으로 Rust를 설치합니다. `rust-toolchain.toml`이 Rust 1.94.0과 rustfmt, Clippy를 고정합니다. -3. 의존성을 설치하고 Tauri를 실행합니다. +여기에 `SHA256SUMS.txt`와 `redrob-launcher.json`이 더해집니다. Windows 설치 프로그램은 사용자별로 +`%LOCALAPPDATA%\Programs\Redrob Query`에 설치하며, 설치된 제품은 **ProductName `Redrob Query`**, +실행 파일은 **`bin\redrob-query.exe`**, 설정 > 앱 항목은 `RedrobQuery`(게시자 `Redrob`)입니다. Redrob +Launcher가 이 이름으로 설치 여부를 알아봅니다. 자세한 내용: [installer/windows/README.md](installer/windows/README.md). -```bash -npm ci -npm run tauri dev -``` +## 소스에서 빌드 -현재 호스트 플랫폼용 패키지 빌드: +Rust는 rustup으로 설치합니다. `rust-toolchain.toml`이 1.95.0과 rustfmt, Clippy를 고정합니다. Linux에서는 +셸이 X11 / Wayland / GL 개발 헤더와 GTK 3(파일 대화상자)에 링크합니다. ```bash -npm run tauri build +sudo apt-get install -y libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev \ + libgl1-mesa-dev libgtk-3-dev libdbus-1-dev +cargo run --release -p redrob-query ``` -Linux에서는 Tauri가 요구하는 WebKitGTK 4.1, JavaScriptCoreGTK 4.1, libsoup 3, librsvg 개발 패키지가 필요합니다. 프런트엔드 프로덕션 빌드와 모든 `redrob-core` 검사는 그 패키지 없이도 돌아가고, `npm run tauri build`만 필요합니다. - -`src-tauri/icons/`에 표준 Tauri PNG·ICNS·ICO·iOS·Android 아이콘이 들어 있습니다. 모두 RGBA여야 합니다 — `tauri::generate_context!`가 RGBA가 아닌 아이콘에서 컴파일 타임에 패닉하므로, `npm run release:check`가 이를 단정합니다. - -서명된 인스톨러는 **Signed desktop release** 워크플로만 만듭니다. `vMAJOR.MINOR.PATCH` 태그를 푸시하면(태그는 `package.json`의 버전과 같아야 합니다) Linux x64·macOS Intel·macOS Apple Silicon·Windows x64를 빌드하고, 업데이터 번들에 서명하고, macOS 빌드를 공증하고, Windows 인스톨러는 조직의 Azure Artifact Signing 계정으로 Authenticode 서명합니다 — 번들 직전에 잡이 OpenID Connect로 로그인하고 tauri가 `redrob-labs/.github`의 공용 서명 스크립트를 실행하므로 인증서 파일이나 클라이언트 시크릿은 어디에도 없습니다. 각 레그는 산출물을 [Redrob Launcher](https://github.com/redrob-labs/redrob-launcher)가 설치하는 이름으로 `release-*` 아티팩트에 올리고, 그 파일들로 `latest.json`을 쓰면서 모든 업데이터 서명을 실제 바이트와 대조한 뒤, 공용 `release-publish` 잡이 `SHA256SUMS.txt`·`redrob-launcher.json`과 함께 GitHub Release를 latest로 **발행**합니다(`-` 접미사가 있는 버전은 pre-release로 발행되어 `releases/latest`에 잡히지 않습니다). GitHub Releases가 업데이트 채널이라 `releases/latest/download/latest.json`은 가장 최근에 발행된 버전을 서비스합니다. 워크플로를 `version` 입력으로 수동 실행하면 드라이런입니다: 모든 레그를 빌드·서명·검증하고 아무것도 발행하지 않습니다. 자격 증명이 없으면 서명 없는 산출물을 발행하는 대신 릴리스를 중단합니다. - -## 데스크톱 로컬 작업공간 데이터 +`redrob-query --version`은 버전을 출력하고 끝나며 디스플레이가 필요 없습니다. -데스크톱 모드는 열린 쿼리 탭 최대 30개와 첫 페이지가 성공한 실행 이력 50개를 버전이 붙은 렌더러 로컬 스토리지에 저장합니다. 저장되는 것은 쿼리 텍스트와 탭·이력 메타데이터뿐이고, 결과 행·데이터베이스 자격 증명·AI 프롬프트는 절대 저장하지 않습니다. 빈 탭 초안도 유효하며 복원됩니다. 실행 이력은 첫 페이지에서 성공적으로 끝난, 비어 있지 않은 쿼리만 남깁니다. 쿼리 텍스트는 평문이고 그 안에 민감한 리터럴이 들어 있을 수 있으므로, 이력 메뉴에 **저장 중단 및 로컬 데이터 삭제**가 있습니다. 이 opt-out은 별도로 저장되어 재시작 후에도 사용자가 명시적으로 다시 켤 때까지 꺼진 상태로 유지됩니다. 잘못된·중복·엔진 불일치·과대·손상된 항목은 거부하며, 저장이 실패하면 성공했다고 말하지 않고 실패를 표시합니다. - -브라우저 데모의 탭과 이력은 현재 페이지 세션 동안만 메모리에 남습니다. 데스크톱 상태를 선택·복원·로드하는 것이 자동 연결이나 자동 쿼리 실행을 일으키는 일은 없습니다. - -## 검증 +### 검증 ```bash -npm run release:check -npm run typecheck -npm test -npm run test:coverage -npm run build -npm audit --audit-level=low cargo fmt --all -- --check -cargo clippy --locked -p redrob-core --all-targets --all-features -- -D warnings -cargo test --locked -p redrob-core --all-features -cargo audit --file Cargo.lock +cargo clippy -p redrob-query -p redrob-query-core --all-targets --locked -- -D warnings +cargo test -p redrob-query-core --locked +python3 scripts/check-brand.py ``` -`npm run release:check`는 npm·Cargo·Tauri·락파일·Rust 툴체인 버전, 제품명, 번들 식별자, 창 라벨, 개발 URL 메타데이터, 그리고 아이콘이 모두 RGBA인지 검증합니다. 커버리지 하한은 statements/lines 85%, branches 75%, functions 65%입니다. - -기본 Rust 코어 실행은 격리된 테스트만 돌리고 외부 커넥터 테스트 3개는 ignored로 보고합니다. 이건 실제 서버에 대한 증거가 아닙니다. 그 게이트를 돌리려면 버려도 되는 서비스 URL을 넣고 명시적으로 opt-in 하세요. +기본 테스트는 격리되어 있고 라이브 서버가 필요한 커넥터 테스트 세 개를 ignored로 보고합니다. 돌리려면 +일회용 서비스 URL을 내보내고 명시적으로 켭니다. ```bash export REDROB_TEST_POSTGRES_URL='postgresql://…' export REDROB_TEST_MYSQL_URL='mysql://…' export REDROB_TEST_MONGO_URL='mongodb://…' export REDROB_TEST_MONGO_DATABASE='redrob_test' # 선택 -cargo test --locked -p redrob-core --all-features -- --ignored +cargo test -p redrob-query-core --locked -- --ignored ``` -명시적으로 실행한 라이브 테스트는 필요한 URL이 없으면 실패합니다. 프로덕션 자격 증명을 쓰지 마세요. Mongo 게이트는 UUID 이름의 컬렉션만 만들고 지웁니다. Rust는 `.env` 파일을 자동으로 읽지 않습니다. +### 브랜드 자산 -`npm run check`는 워크스페이스 전체 Rust 검사까지 포함하므로 Linux에서는 네이티브 Tauri 시스템 패키지가 필요합니다. +`resources/icons`, `resources/brand`, 설치 프로그램의 `res/` 아래 모든 파일은 +`scripts/build-brand-assets.py --design-system <경로>`가 디자인 시스템 배포물에서 만들고 +`brand/SOURCES.json`에 sha256으로 고정합니다. `scripts/check-brand.py`(CI와 릴리스)가 손으로 고친 파일이나 +고정되지 않은 파일을 거부합니다. -## 보안 요약 +### 릴리스 -- 비밀이 아닌 프로필 메타데이터는 평문 `connections.json`에 저장하고, 비밀번호와 Redrob 키는 OS 키링을 씁니다. -- 비밀을 담지 않는 fsync 저널, 준비 단계 키링 항목, 배타적 프로필 락이 갱신을 복구 가능하게 만들고 안전하지 않은 복구나 동시 소유 상황에서 닫힌 방향으로 실패하게 합니다. -- 직접 쓰는 쿼리는 읽기만 합니다. 한 번 실행에 읽기 전용 문장만 받으며(여러 개는 전부 읽기 전용일 때만, 하나씩 따로 실행), PostgreSQL/MySQL은 읽기 전용 트랜잭션을, SQLite는 닫힌 방향으로 실패하는 PRAGMA 허용목록을 씁니다. 쓰기는 편집을 허락한 연결에서 검토한 편집으로만 일어납니다. -- Mongo 요청은 엄격하고 한계가 있습니다. aggregate의 `$out`과 `$merge`는 재귀적으로 거부합니다. 메타데이터는 최대 25개 문서에서 최상위 타입·존재·널 허용 힌트를 병합한 것이며 완전한 스키마가 아닙니다. -- PostgreSQL/MySQL TLS를 켜면 공개 신뢰 루트로 인증서 신원을 검증합니다. 사설·자체 서명 CA 설정은 제공하지 않습니다. -- 데스크톱 AI는 프롬프트와 선택적으로 현재 쿼리를 Redrob에 보냅니다. 결과 행과 자격 증명은 첨부하지 않지만, 사용자가 그 텍스트에 직접 넣은 민감한 리터럴은 전송됩니다. +**Release** 워크플로(`gh workflow run release.yml --ref main -f tag=vX.Y.Z`)가 cargo 바이너리 하나로 세 +타깃을 빌드하고, Windows는 작업의 OpenID Connect 로그인으로 Azure Artifact Signing에서 서명하고, macOS는 +조직의 Developer ID로 서명·공증하며, 공용 `redrob-labs/.github` 게시 작업이 `SHA256SUMS.txt`와 +`redrob-launcher.json`을 쓰고 GitHub Release를 latest로 게시합니다. 태그는 `main` 위에 있어야 하고 +`Cargo.toml`의 워크스페이스 버전과 같아야 하며 [CHANGELOG.md](CHANGELOG.md)에 `## ` 절이 있어야 +합니다. `-f dry_run=true`는 브랜치를 빌드·서명만 하고 아무것도 게시하지 않습니다. -전체 [보안 모델과 한계](docs/SECURITY.md)를 읽어 보세요. +## 로컬 데이터 -## 현재 경계 +| 파일 | 내용 | +|---|---| +| `connections.json` | 비밀이 없는 프로필 메타데이터(엔진의 파일, 원자적, 저널링) | +| `account.json` | 런처의 Connect나 사용자가 써 둔 이 앱 자체의 Redrob 로그인. 공용 파일보다 우선 | +| `workspace.json` | 열린 탭(쿼리 텍스트만)과 실행 이력. 결과 행·비밀번호·프롬프트는 저장하지 않음 | +| `ui.ron` | eframe의 창 위치와 패널 크기 | -- 자유 입력 쓰기 SQL은 없습니다. 변경은 검토하는 편집과 표 만들기 폼으로만 합니다. 표에 단일 열 기본 키가 있어야 고칠 수 있고, MongoDB 결과는 고칠 수 없습니다. -- 페이징은 서버·브리지 단위로 한계가 있고, 정렬과 필터는 현재 로드된 페이지에만 적용됩니다. -- 사설 CA·클라이언트 인증서 UI, SSH 터널, 클라우드 인증 플러그인, 스크립트 실행기, ER 다이어그램, 관리 도구 모음, 데이터 전송 파이프라인, 비교·마이그레이션 도구, 드라이버 마켓플레이스, SQL Server 커넥터는 없습니다. -- Mongo 샘플 메타데이터는 한계가 있는 힌트이며, 컬렉션 스키마를 확정적으로 추론한 것이 아닙니다. -- 외부 PostgreSQL/MySQL/Mongo와 Redrob API 연동은 사용자가 제공하는 서비스·자격 증명이 필요하고, 브라우저 데모는 그것을 검증하지 않습니다. +디렉터리는 Windows `%APPDATA%\Redrob Query`, macOS `~/Library/Application Support/Redrob Query`, 그 외 +`$XDG_CONFIG_HOME/redrob-query`(`~/.config/redrob-query`)이며 `REDROB_QUERY_CONFIG_DIR`로 바꿀 수 있습니다. +쿼리 텍스트는 평문이라 민감한 리터럴이 들어 있을 수 있습니다. 작업공간과 창 상태를 남기지 않으려면 +`REDROB_QUERY_NO_PREFS=1`을 설정하세요. -## Redrob AI 설정 +## 보안 요약 -**Redrob Code가 필요합니다.** 데스크톱 AI는 이 컴퓨터에 하나 있는 공유 Redrob Code 데몬(`~/.redrob/run/server.json`; 없으면 `redrob service start`로 띄움)의 다음 경로에 말합니다. +- 비밀이 없는 프로필 메타데이터는 평문 `connections.json`에, 비밀번호는 OS 키링에 저장합니다. +- 비밀이 없는 fsync 저널, 단계적 키링 항목, 배타적 프로필 잠금으로 갱신을 복구 가능하게 하고, 안전하지 + 않은 복구나 동시 소유 상황에서는 닫힌 쪽으로 실패합니다. +- 입력한 쿼리는 읽기만 합니다. 매 실행은 읽기 전용 문장만 받고(여러 개는 전부 읽기 전용일 때만, 각각 따로 + 실행), PostgreSQL / MySQL은 읽기 전용 트랜잭션, SQLite는 닫힌 쪽으로 실패하는 PRAGMA 허용목록을 + 씁니다. 쓰기는 읽기 전용이 아닌 프로필에서, 모든 값을 바인딩해, 검토한 편집과 구조 폼으로만 일어납니다. +- Mongo 요청은 엄격하고 한계가 있습니다. aggregate의 `$out`과 `$merge`는 재귀적으로 거부합니다. +- PostgreSQL / MySQL TLS를 켜면 공개 신뢰 루트로 인증서 신원을 검증합니다. +- Ask는 프롬프트와 활성 탭의 텍스트를 계정의 bearer 키로 Redrob 콘솔에 보냅니다. 결과 행과 자격 증명은 + 붙지 않지만, 두 텍스트에 직접 입력한 민감한 리터럴은 전송됩니다. -```text -/v1/chat/completions -``` +전체 [보안 모델과 한계](docs/SECURITY.md)를 읽어 보세요. + +## Redrob 계정 -모든 요청에 데몬 자체의 인증, `x-redrob-app: query`, 실행마다 하나인 `x-redrob-session`이 실리고, 모델은 `auto`입니다. Redrob 콘솔 키는 데몬이 보관하고 데몬이 그 키로 콘솔을 부릅니다. 이 앱은 자체 키를 갖지 않고 콘솔로 직접 가는 경로도 없습니다 -- 마스킹이 적용되는 곳이 한 곳이 되도록. 로그인은 한 번만: Redrob Code에서, Redrob Launcher에서, 또는 여기서. **Redrob 설정**은 붙여 넣은 키를 엔진에 넘기고(`PUT /auth/redrob`), 엔진이 로그인돼 있는지 `GET /provider`에서 읽어 엔진이 쓰는 키가 런처의 것이면 **Redrob Launcher로 로그인됨**이라고 말합니다. Redrob Code가 없으면 설정 대화상자와 모든 AI 요청이 "Redrob Code is not installed. Install it with Redrob Launcher."와 [런처 최신 릴리스](https://github.com/redrob-labs/redrob-launcher/releases/latest) 링크를 보입니다. 이전 버전의 이 앱이 OS 키링에 저장했던 키는 한 번 엔진에 넘기고 지웁니다. `REDROB_API_KEY`와 런처 공유 계정 파일 읽기는 없어졌습니다. 데몬 레코드와 바이너리는 `redrob-engine-client`(redrob-code `docs/LOCAL-ENGINE-API.md` 3절)가 찾고, `REDROB_RUN_DIR`와 `REDROB_CODE_BIN`은 테스트를 비공개 데몬으로 향하게 합니다. 브라우저 데모의 AI는 로컬이고 결정적이며, 데모에 입력한 키는 보관하거나 전송하지 않습니다. +Ask는 Redrob Launcher가 이 컴퓨터를 로그인시킨 계정을 씁니다. `$XDG_CONFIG_HOME/redrob-shared`, +`~/Library/Application Support/ai.redrob.shared`, `%APPDATA%\Redrob\Shared`(`REDROB_SHARED_DIR`로 변경)의 +`account.json`을 런처의 `docs/shared-account.md` 계약대로 요청마다 읽고, 앱이 열려 있는 동안 지켜봅니다. +환경 변수 `REDROB_API_KEY`, 그다음 이 앱 자체의 `account.json`이 그보다 우선합니다. 키는 +`{apiBaseUrl}/chat/completions`에 모델 `auto`로 bearer 토큰으로만 보내며, 콘솔이 거부하면 콘솔의 메시지를 +그대로 보여 줍니다. 계정이 없으면 Ask 도크가 그렇다고 말하고 런처를 열어 줍니다. 이 앱에는 자체 로그인이 +없습니다. -## 기여 +## 현재 경계 -[CONTRIBUTING.md](CONTRIBUTING.md)에 브랜치 모델, 필수 검사, 그리고 실수로 깨기 쉬운 두 가지 규칙이 있습니다. +- 자유 형식 쓰기 SQL은 없습니다. 변경은 검토한 편집 집합과 표 폼으로만 갑니다. 고치려면 단일 열 기본 키가 + 필요하고, MongoDB 결과와 복합 키는 아직 고칠 수 없습니다. +- 정렬과 필터는 데이터베이스가 아니라 가져온 페이지(탭의 한도까지)에만 적용됩니다. +- 사용자 CA·클라이언트 인증서 UI, SSH 터널, ER 다이어그램, 데이터 전송 파이프라인, 비교·마이그레이션 도구, + 드라이버 마켓플레이스, SQL Server 커넥터는 없습니다. +- Mongo 샘플 메타데이터는 한계가 있는 힌트이지 스키마가 아닙니다. -## 라이선스와 귀속 +## 라이선스와 저작자 표시 -Redrob Query는 [GNU General Public License 버전 3 이상](LICENSE)으로 배포됩니다. Beekeeper Studio Community Edition 소스를 이식하기 시작하면서 Apache-2.0에서 옮겼습니다 — 그쪽이 GPL-3.0-or-later이므로 더 강한 카피레프트가 코드와 함께 따라옵니다. 귀속과 상표 유보는 [NOTICE](NOTICE)를 보세요. 서드파티 구성요소는 각자의 라이선스를 따릅니다. +Redrob Query는 [GNU General Public License, version 3 or later](LICENSE)로 배포합니다. GPL-3.0-or-later인 +Beekeeper Studio Community Edition 소스를 들여오기 전까지는 Apache-2.0이었고, 더 강한 카피레프트가 코드와 +함께 옵니다. 저작자 표시와 상표 유보는 [NOTICE](NOTICE), 상류 코드 고지는 +[UPSTREAM_NOTICES.md](UPSTREAM_NOTICES.md)를 보세요. 서드파티 구성 요소는 각자의 라이선스를 따릅니다. diff --git a/README.md b/README.md index 879da28..bc2cdc1 100644 --- a/README.md +++ b/README.md @@ -2,149 +2,170 @@ **English** · [한국어](./README.ko.md) -Redrob Query is a JVM-free, AI-assisted database workspace built with Rust 1.94, Tauri 2, React 19, and TypeScript. It is a desktop workspace for PostgreSQL, MySQL, SQLite, and MongoDB that reads by default and edits only where you allow it, plus an interactive browser demo backed only by deterministic in-memory sample data. +Redrob Query is an agentic database client for PostgreSQL, MySQL, SQLite and MongoDB: one native +window (Rust, egui on wgpu) that reads by default, edits only where you allow it after you have +reviewed the change, and drafts queries with an agent beside you. There is no JVM, no browser +runtime and no web demo. Signing in happens once, in [Redrob Launcher](https://github.com/redrob-labs/redrob-launcher); +this app reads that account. -The workflow is informed by DBeaver Community, but Redrob Query is a clean implementation. It includes no DBeaver source code or branding and no Eclipse RCP, OSGi, JDBC, Java, or JVM runtime. +The workflow is informed by DBeaver Community and parts of the engine were ported from Beekeeper +Studio Community Edition (see [NOTICE](NOTICE) and [UPSTREAM_NOTICES.md](UPSTREAM_NOTICES.md)). It +includes no Eclipse RCP, OSGi, JDBC, Java or Electron. -## Shipped scope +## What ships | Area | Capability | |---|---| -| Profiles | Create, test, edit, connect, disconnect, and remove user profiles; built-in profiles are immutable | -| PostgreSQL | Identity-verifying TLS, metadata, bounded SQL reads, native scalar decoding, reviewed edits | -| MySQL | Identity-verifying TLS, metadata, bounded SQL reads, unsigned/decimal/temporal/BIT decoding, reviewed edits | -| SQLite | File profiles that open read-only (`mode=ro`) unless edits are allowed, metadata, guarded reads, reviewed edits; native demo uses `:memory:` | -| MongoDB | Standard/SRV profiles, separate `authSource`, collections, bounded 25-document field sampling, strict `find`/`aggregate`/explain JSON | -| Results | 25/50/100/250-row paging, typed virtualized rows, current-page sort/filter, column visibility, column and row menus, a details panel, execution messages, and visible CSV export | -| Editing | Off per connection until "Allow edits" is ticked. Edit cells, add and delete rows, create a table or add a column; every change is staged, reviewed and applied in one transaction that rolls back unless each statement changes exactly one row. A result that cannot be edited says why | -| Query workspace | Connection-owned SQL/MQL tabs, engine starters, saved queries (Save / Ctrl+S), several read-only statements in one run with a result picker, pinned tables, local desktop restoration, bounded execution history, formatting, and shortcuts | -| Redrob AI | Engine-aware SQL/MQL generation and explanation through model `auto` | -| Browser demo | Sample PostgreSQL/MySQL/SQLite/Mongo workflows, local AI responses, and atomic in-memory relational edit review | - -SQL Server is not supported in this release: it is absent from the connection UI and rejected by both profile and AI validation paths. - -Desktop connections are read-only until you tick "Allow edits" on the connection. Even then nothing is written until you review the staged changes and press Apply: cell edits become `UPDATE … WHERE = ?` with bound values, rows are inserted and deleted the same way, and the whole set runs in one transaction that is undone if any statement does not change exactly one row. Only a plain read of one table with a single-column primary key in the result can be edited. Browser-demo edits change a per-profile in-memory fixture; reload discards them. +| Profiles | Built-in **Demo SQLite** (`:memory:`, works offline) plus your own profiles: create, test, edit, connect, disconnect, remove. Passwords live in the OS keyring, never in a file | +| PostgreSQL / MySQL | Identity-verifying TLS, schema-grouped tables and views, columns, primary and foreign keys, indexes, bounded read-only SQL, reviewed edits | +| SQLite | File profiles (read-only `mode=ro` until edits are allowed), tables, columns, keys and indexes through the table-valued pragmas, reviewed edits | +| MongoDB | Standard and SRV profiles, `authSource`, collections with 25-document field sampling, strict `find` / `aggregate` / `explain` request bodies, collection indexes, a JSON document view | +| Editor | Tabs per connection, SQL and Mongo JSON colouring, Ctrl+Enter runs the selection or the statement under the cursor (a read-only batch runs statement by statement), Ctrl+Shift+F formats, Ctrl+S saves the text to a file, per-engine starter queries, a Mongo request strip | +| Results | Virtualised rows, sort and filter on the fetched page, column visibility, cell copy, TSV / JSON copy and save, a row limit per tab | +| Editing | Double-click a cell to stage a new value, delete rows from the row menu, review the staged set in the Changes panel, Apply runs it in one transaction that rolls back unless every statement changes exactly one row. Needs a single-table result with a one-column primary key on a profile that is not read-only | +| Structure | Columns with PK / FK / not-null flags, foreign keys, indexes; create a table or add a column from a form over a fixed type list | +| Ask | Describe the query you want; the active tab's text goes along as context, result rows and credentials never do. Fenced `sql` / `mql` blocks in the answer insert into the editor with one click | +| Workspace | Up to 30 tabs and 50 history entries restored on start (`workspace.json`), window geometry in `ui.ron`; `REDROB_QUERY_NO_PREFS=1` keeps nothing | + +SQL Server is not supported in this release. Writes typed into the editor are refused by the engine's +read-only gate; the only write paths are the Changes panel and the structure forms. ## Architecture ```text -React 19 + Monaco + TanStack Virtual - │ - Zustand workspace state - │ DataBridge - ┌─────┴───────────┐ - DemoBridge Tauri IPC allowlist - (browser memory) │ - redrob-core (Rust) - ┌─────────┼──────────┐ - SQLx concrete MongoDB Redrob AI - PG/MySQL/SQLite driver HTTPS client +apps/redrob-query (eframe / egui 0.36 on wgpu) + profiles · schema · editor · grid · changes · structure · Ask dock + │ Workspace (one method per former command, answered by Pending handles) +crates/redrob-query-core + DataService ── SQLx (PostgreSQL / MySQL / SQLite) · mongodb · reqwest (console) + query.rs read-only classification · cell_edits.rs · ddl.rs · keys.rs · profile_store.rs · secret.rs + account.rs: Redrob Launcher's shared account.json ``` -See [Architecture](docs/ARCHITECTURE.md), [Security](docs/SECURITY.md), [Demo guide](docs/DEMO.md), and the [DBeaver workflow map](docs/DBEAVER-PORTING-MAP.md). - -## Quick start: browser demo - -The browser demo does not connect to a database or Redrob service and does not persist its workspace. - -```bash -npm ci -npm run dev -``` +See [Architecture](docs/ARCHITECTURE.md), [Security](docs/SECURITY.md) and the +[DBeaver workflow map](docs/DBEAVER-PORTING-MAP.md). -Open `http://127.0.0.1:1420`. The demo starts with the connected **Acme Warehouse** sample profile. It supports deterministic metadata and queries, two result pages at the default 50-row size, local AI responses, CSV export, profile lifecycle simulation, and reviewed in-memory relational edits. +## Install -Requirements: Node.js 22.12+ and npm 11+. +Every release carries, under the names Redrob Launcher reads: -## Desktop development +| Platform | Assets | +|---|---| +| Linux x86_64 | `redrob-query--linux-x86_64.tar.gz`, `redrob-query--linux-x86_64.deb` | +| macOS arm64 | `redrob-query--macos-arm64.dmg`, `redrob-query--macos-arm64.zip` (signed, notarized, stapled) | +| Windows x64 | `redrob-query--windows-x64-setup.exe` (English + Korean setup), `redrob-query--windows-x64-portable.zip` (signed) | -1. Install Node.js 22.12+, npm 11+, and the platform packages from the [Tauri v2 prerequisites guide](https://v2.tauri.app/start/prerequisites/). -2. Install Rust through rustup; `rust-toolchain.toml` pins Rust 1.94.0, rustfmt, and Clippy. -3. Install dependencies and launch Tauri: +plus `SHA256SUMS.txt` and `redrob-launcher.json`. The Windows setup installs per user into +`%LOCALAPPDATA%\Programs\Redrob Query`; the installed product is **ProductName `Redrob Query`** with +the executable **`bin\redrob-query.exe`** and the Settings > Apps entry `RedrobQuery` +(publisher `Redrob`) -- the names Redrob Launcher detects it by. Details: +[installer/windows/README.md](installer/windows/README.md). -```bash -npm ci -npm run tauri dev -``` +## Build from source -Build a package for the current host platform with: +Rust comes from rustup; `rust-toolchain.toml` pins 1.95.0 with rustfmt and Clippy. On Linux the +shell links the X11 / Wayland / GL development headers and GTK 3 (for the file dialogs): ```bash -npm run tauri build +sudo apt-get install -y libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev \ + libgl1-mesa-dev libgtk-3-dev libdbus-1-dev +cargo run --release -p redrob-query ``` -Linux requires the WebKitGTK 4.1, JavaScriptCoreGTK 4.1, libsoup 3, and librsvg development metadata expected by Tauri. Frontend production builds and all `redrob-core` checks run without those packages; only `npm run tauri build` needs them. - -`src-tauri/icons/` holds standard Tauri PNG, ICNS, ICO, iOS, and Android icons. Every PNG must be RGBA — `tauri::generate_context!` panics at compile time on anything else — and `npm run release:check` asserts it. - -Signed installers are produced only by the **Signed desktop release** workflow, which a pushed `vMAJOR.MINOR.PATCH` tag starts (the tag must equal the version in `package.json`). It builds Linux x64, macOS Intel, macOS Apple Silicon, and Windows x64, signs the updater bundles, notarizes the macOS builds, and Authenticode-signs the Windows installers through the organisation's Azure Artifact Signing account -- the job logs in with OpenID Connect right before bundling and tauri runs the shared `redrob-labs/.github` signing script; no certificate file or client secret exists anywhere. Each leg uploads its files as a `release-*` artifact under the names [Redrob Launcher](https://github.com/redrob-labs/redrob-launcher) installs, `latest.json` is written from them and every updater signature is verified against its bytes, and the shared `release-publish` job then **publishes** the GitHub Release as latest with `SHA256SUMS.txt` and `redrob-launcher.json` beside the installers (a version with a `-` suffix is published as a pre-release, which `releases/latest` does not serve). GitHub Releases is the update channel, so `releases/latest/download/latest.json` serves the newest published version. A manual run of the workflow with a `version` input is a dry run: it builds, signs and verifies every leg and publishes nothing. Missing credentials stop the release rather than publishing unsigned artifacts. - -## Desktop local workspace data - -Desktop mode stores up to 30 open query tabs and 50 successful first-page history entries in versioned renderer local storage. Only query text and tab/history metadata are stored, never result rows, database credentials, or AI prompts. Empty tab drafts are valid and restored; execution history retains only nonempty queries that completed successfully on the first page. Query text is plaintext and may itself contain sensitive literals, so the history menu provides **Stop saving & clear local data**. That opt-out is stored separately and remains disabled across restarts until the user explicitly re-enables saving. Invalid, duplicate, engine-incompatible, oversized, or corrupt entries are rejected; storage failure is shown instead of claiming a successful save. +`redrob-query --version` prints the version and exits; it needs no display. -Browser-demo tabs and history remain in memory for the current page session only. Selecting, restoring, or loading desktop state never auto-connects or auto-runs a query. - -## Validation +### Validation ```bash -npm run release:check -npm run typecheck -npm test -npm run test:coverage -npm run build -npm audit --audit-level=low cargo fmt --all -- --check -cargo clippy --locked -p redrob-core --all-targets --all-features -- -D warnings -cargo test --locked -p redrob-core --all-features -cargo audit --file Cargo.lock +cargo clippy -p redrob-query -p redrob-query-core --all-targets --locked -- -D warnings +cargo test -p redrob-query-core --locked +python3 scripts/check-brand.py ``` -`npm run release:check` verifies npm, Cargo, Tauri, lockfile, Rust toolchain, product name, bundle identifier, window label, and development URL metadata. Coverage has global regression floors of 85% statements/lines, 75% branches, and 65% functions. - -The default Rust core run executes hermetic tests and reports three external connector tests as ignored. It is not live-server evidence. To run the ignored gates, export disposable service URLs and explicitly opt in: +The default test run is hermetic and reports three connector tests as ignored; they need live +servers. To run them, export disposable service URLs and opt in: ```bash export REDROB_TEST_POSTGRES_URL='postgresql://…' export REDROB_TEST_MYSQL_URL='mysql://…' export REDROB_TEST_MONGO_URL='mongodb://…' export REDROB_TEST_MONGO_DATABASE='redrob_test' # optional -cargo test --locked -p redrob-core --all-features -- --ignored +cargo test -p redrob-query-core --locked -- --ignored ``` -An explicitly run live test fails when its required URL is absent. Use non-production credentials; the Mongo gate creates and drops only a UUID-named collection. Rust does not automatically load `.env` files. +### Brand assets -`npm run check` also includes workspace-wide Rust checks and therefore requires native Tauri system packages on Linux. +Every file under `resources/icons`, `resources/brand` and the installer `res/` folders is built from +the design system's published files by `scripts/build-brand-assets.py --design-system ` and +pinned by sha256 in `brand/SOURCES.json`; `scripts/check-brand.py` (CI and the release) refuses a +hand-edited or unpinned file. -## Security summary +### Release -- Non-secret profile metadata is stored in plaintext `connections.json`; passwords and the Redrob key use the OS keyring. -- A secret-free fsynced journal, staged keyring entry, and exclusive profile lock make updates recoverable and fail closed under unsafe recovery or concurrent ownership. -- Typed queries are reads only: each run accepts read-only statements (several only when every one is read-only, each executed on its own), with PostgreSQL/MySQL read-only transactions and a fail-closed SQLite PRAGMA allowlist. Writes happen only through reviewed edits on a connection that allows them. -- Mongo requests are strict and bounded; aggregate `$out` and `$merge` are rejected recursively. Metadata merges top-level type/presence/nullability hints from at most 25 documents and is not a complete schema. -- Enabled PostgreSQL/MySQL TLS verifies certificate identity with public trust roots. Private/self-signed CA configuration is unavailable. -- Desktop AI sends the prompt and optional active query to Redrob. Result rows and credentials are not attached, but sensitive literals manually included in either text are transmitted. +The **Release** workflow (`gh workflow run release.yml --ref main -f tag=vX.Y.Z`) builds the three +targets from the one cargo binary, signs Windows through Azure Artifact Signing with the job's OpenID +Connect login, signs and notarizes macOS with the organisation's Developer ID, and the shared +`redrob-labs/.github` publish job writes `SHA256SUMS.txt` and `redrob-launcher.json` and publishes the +GitHub Release as latest. The tag must be on `main`, equal the workspace version in `Cargo.toml`, and +have a `## ` section in [CHANGELOG.md](CHANGELOG.md). `-f dry_run=true` builds and signs a +branch and publishes nothing. -Read the complete [security model and limitations](docs/SECURITY.md). +## Local data -## Current boundaries +| File | What | +|---|---| +| `connections.json` | Non-secret profile metadata (the engine's own file, atomic, journaled) | +| `account.json` | This app's own Redrob sign-in, if the launcher's Connect or you wrote one; wins over the shared file | +| `workspace.json` | Open tabs (query text only) and the run history; never result rows, passwords or prompts | +| `ui.ron` | eframe's window geometry and panel sizes | + +Directory: `%APPDATA%\Redrob Query` on Windows, `~/Library/Application Support/Redrob Query` on macOS, +`$XDG_CONFIG_HOME/redrob-query` (`~/.config/redrob-query`) elsewhere; `REDROB_QUERY_CONFIG_DIR` +overrides it. Query text is plain text and may contain sensitive literals; set +`REDROB_QUERY_NO_PREFS=1` to keep no workspace or window state. -- No free-form write SQL: changes go through the reviewed edit and table forms only. Tables need a single-column primary key to be edited; MongoDB results are not editable. -- Paging is server/bridge bounded, while sorting and filtering apply only to the currently loaded page. -- No custom CA/client-certificate UI, SSH tunnel, cloud-auth plugin, script runner, ER diagram, administration suite, data-transfer pipeline, compare/migration tooling, driver marketplace, or SQL Server connector. -- Mongo sampled metadata is a bounded hint, not authoritative collection schema inference. -- External PostgreSQL/MySQL/Mongo and Redrob API integration require user-supplied services/credentials and are not exercised by the browser demo. +## Security summary -## Redrob AI configuration +- Non-secret profile metadata is stored in plaintext `connections.json`; passwords use the OS keyring. +- A secret-free fsynced journal, staged keyring entry and exclusive profile lock make updates + recoverable and fail closed under unsafe recovery or concurrent ownership. +- Typed queries are reads only: each run accepts read-only statements (several only when every one + is read-only, each executed on its own), with PostgreSQL / MySQL read-only transactions and a + fail-closed SQLite PRAGMA allowlist. Writes happen only through reviewed edits and the structure + forms, on a profile that is not read-only, with every value bound. +- Mongo requests are strict and bounded; aggregate `$out` and `$merge` are rejected recursively. +- Enabled PostgreSQL / MySQL TLS verifies certificate identity with public trust roots. +- Ask sends the prompt and the active tab's text to the Redrob console with the account's bearer + key. Result rows and credentials are not attached, but sensitive literals you typed into either + text are transmitted. -**Requires Redrob Code.** Desktop AI talks to the one shared Redrob Code daemon on this machine (`~/.redrob/run/server.json`; started with `redrob service start` when none is running) at: +Read the complete [security model and limitations](docs/SECURITY.md). -```text -/v1/chat/completions -``` +## The Redrob account + +Ask uses the account Redrob Launcher signed this computer in with: `account.json` in +`$XDG_CONFIG_HOME/redrob-shared`, `~/Library/Application Support/ai.redrob.shared` or +`%APPDATA%\Redrob\Shared` (`REDROB_SHARED_DIR` overrides), read on every request and watched while +the app is open, as the launcher's `docs/shared-account.md` contract asks. `REDROB_API_KEY` in the +environment, then this app's own `account.json`, win over it. The key is sent only as a bearer token to +`{apiBaseUrl}/chat/completions` with model `auto`; the console's own error message is shown when it +refuses. Without an account the Ask dock says so and offers to open the launcher; this app has no +sign-in of its own. + +## Current boundaries -Every request carries the daemon's own authorization, `x-redrob-app: query` and a per-run `x-redrob-session`; the request uses model `auto`. The daemon holds the Redrob console key and calls the console with it; this app never has a key of its own and has no direct path to the console, so there is one place masking applies. Sign in once -- in Redrob Code, in Redrob Launcher, or here: **Redrob settings** hands a pasted key to the engine (`PUT /auth/redrob`) and reads whether the engine is signed in from `GET /provider`, saying **Signed in with Redrob Launcher** when that is whose key the engine uses. Without Redrob Code the dialog and every AI request say "Redrob Code is not installed. Install it with Redrob Launcher." with a link to [the launcher's latest release](https://github.com/redrob-labs/redrob-launcher/releases/latest). A key an earlier version of this app saved in the OS keyring is handed to the engine once and deleted. `REDROB_API_KEY` and the launcher shared-account reader are gone. The daemon record and binary come from `redrob-engine-client` (redrob-code `docs/LOCAL-ENGINE-API.md`, section 3); `REDROB_RUN_DIR` and `REDROB_CODE_BIN` point tests at a private daemon. Browser-demo AI is local and deterministic; an entered demo key is not retained or sent. +- No free-form write SQL: changes go through the reviewed edit set and the table forms. Tables need a + one-column primary key to be edited; MongoDB results and composite keys are not editable yet. +- Sorting and filtering apply to the fetched page (up to the tab's limit), not to the database. +- No custom CA or client-certificate UI, SSH tunnel, ER diagram, data-transfer pipeline, compare or + migration tooling, driver marketplace, or SQL Server connector. +- Mongo sampled metadata is a bounded hint, not a schema. ## License and attribution -Redrob Query is licensed under the [GNU General Public License, version 3 or later](LICENSE). It was Apache-2.0 until it began incorporating Beekeeper Studio Community Edition source, which is GPL-3.0-or-later; the stronger copyleft travels with the code. See [NOTICE](NOTICE) for attribution and for the trademark reservation. Third-party components remain subject to their own licenses. +Redrob Query is licensed under the [GNU General Public License, version 3 or later](LICENSE). It was +Apache-2.0 until it began incorporating Beekeeper Studio Community Edition source, which is +GPL-3.0-or-later; the stronger copyleft travels with the code. See [NOTICE](NOTICE) for attribution +and the trademark reservation, and [UPSTREAM_NOTICES.md](UPSTREAM_NOTICES.md) for the upstream code +notices. Third-party components remain subject to their own licenses. diff --git a/apps/redrob-query/Cargo.toml b/apps/redrob-query/Cargo.toml new file mode 100644 index 0000000..16f2f9d --- /dev/null +++ b/apps/redrob-query/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "redrob-query" +description = "Redrob Query: an agentic database client for PostgreSQL, MySQL, SQLite and MongoDB" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +publish = false + +[[bin]] +name = "redrob-query" +path = "src/main.rs" + +[dependencies] +redrob-query-core = { path = "../../crates/redrob-query-core" } +eframe = { workspace = true, features = ["wgpu", "accesskit", "wayland", "x11", "persistence", "default_fonts"] } +egui = { workspace = true } +egui_extras = { workspace = true } +env_logger = { workspace = true } +log = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true } +uuid = { workspace = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +arboard = { workspace = true } +rfd = { workspace = true } +webbrowser = { workspace = true } + +[build-dependencies] +winresource = "0.1" + +[lints] +workspace = true diff --git a/apps/redrob-query/build.rs b/apps/redrob-query/build.rs new file mode 100644 index 0000000..a760ef1 --- /dev/null +++ b/apps/redrob-query/build.rs @@ -0,0 +1,33 @@ +//! Windows only: embed the app icon and version info (VERSIONINFO) into `redrob-query.exe`, so the +//! Start menu, Settings > Apps and Redrob Launcher show the Redrob mark and the product name. +//! +//! On every other target this does nothing. A missing resource compiler is a warning, so a +//! cross-compile from macOS or Linux still links, unless `REDROB_QUERY_REQUIRE_WINRES=1` (set by the +//! release workflow) turns it into an error. + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=../../resources/icons/redrob-query.ico"); + println!("cargo:rerun-if-env-changed=REDROB_QUERY_REQUIRE_WINRES"); + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { + return; + } + let mut res = winresource::WindowsResource::new(); + res.set_icon("../../resources/icons/redrob-query.ico") + .set("ProductName", "Redrob Query") + .set("FileDescription", "Redrob Query") + .set("CompanyName", "Redrob") + .set( + "LegalCopyright", + "Copyright (c) 2026 Janghoon Lee (Redrob) and contributors. GPL-3.0-or-later.", + ) + .set("OriginalFilename", "redrob-query.exe") + .set("InternalName", "redrob-query"); + if let Err(e) = res.compile() { + if std::env::var_os("REDROB_QUERY_REQUIRE_WINRES").is_some() { + println!("cargo:warning=embedding Windows resources failed: {e}"); + std::process::exit(1); + } + println!("cargo:warning=redrob-query.exe built without icon/version resources: {e}"); + } +} diff --git a/apps/redrob-query/src/account.rs b/apps/redrob-query/src/account.rs new file mode 100644 index 0000000..bffc491 --- /dev/null +++ b/apps/redrob-query/src/account.rs @@ -0,0 +1,64 @@ +//! The shared Redrob account in the shell: who is signed in (never the key), and a watcher that +//! notices the launcher writing or deleting `account.json` while the app is open. +//! +//! The contract (redrob-launcher `docs/shared-account.md`) asks apps to read the file when they +//! need it rather than once at start. The engine does exactly that on every AI request; the shell +//! additionally polls the files' modification stamps every couple of seconds so the status line +//! and the AI dock follow a sign-in or sign-out without a restart. There is no sign-in UI of this +//! app's own beyond opening Redrob Launcher. + +use std::path::PathBuf; +use std::time::{Duration, Instant, SystemTime}; + +use redrob_query_core::{Workspace, ai::AiKeyStatus}; + +const POLL_EVERY: Duration = Duration::from_secs(2); + +pub struct AccountWatch { + pub status: AiKeyStatus, + stamp: Vec<(PathBuf, Option<(SystemTime, u64)>)>, + last_poll: Instant, +} + +impl AccountWatch { + pub fn new(workspace: &Workspace) -> Self { + Self { + status: workspace.ai_key_status(), + stamp: workspace.account_stamp(), + last_poll: Instant::now(), + } + } + + /// Re-reads the account when a file changed. Returns `true` when the status moved, so the + /// caller can refresh what shows it. Cheap: metadata only, every [`POLL_EVERY`]. + pub fn poll(&mut self, workspace: &Workspace) -> bool { + if self.last_poll.elapsed() < POLL_EVERY { + return false; + } + self.last_poll = Instant::now(); + let stamp = workspace.account_stamp(); + if stamp == self.stamp { + return false; + } + self.stamp = stamp; + let status = workspace.ai_key_status(); + let changed = status != self.status; + self.status = status; + changed + } + + /// Where the shared file is read from, for the status tooltip. + pub fn describe_paths(&self) -> String { + self.stamp + .iter() + .map(|(path, stamp)| { + format!( + "{} ({})", + path.display(), + if stamp.is_some() { "present" } else { "absent" } + ) + }) + .collect::>() + .join("\n") + } +} diff --git a/apps/redrob-query/src/ai_dock.rs b/apps/redrob-query/src/ai_dock.rs new file mode 100644 index 0000000..ffea040 --- /dev/null +++ b/apps/redrob-query/src/ai_dock.rs @@ -0,0 +1,259 @@ +//! The AI dock: ask for a query in plain words, get a draft back, put it in the editor. +//! +//! Each turn sends only the prompt and the active tab's text (never result rows or credentials) +//! to the console with the shared account. Fenced `sql` / `mql` / `json` blocks in the answer each +//! get an Insert button. Without a signed-in account the dock says so and offers the launcher. + +use egui::{RichText, Ui}; +use redrob_query_core::{ + AiAssistantRequest, AiChatResponse, DatabaseKind, Pending, QueryLanguage, Workspace, + ai::{AiKeySource, AiKeyStatus, NOT_SIGNED_IN_MESSAGE}, +}; + +use crate::theme; + +#[derive(Clone, Debug)] +pub struct Turn { + pub user: bool, + pub text: String, +} + +#[derive(Default)] +pub struct AiDock { + pub prompt: String, + pub turns: Vec, + pending: Option>, + error: Option, + pub status: AiKeyStatus, +} + +pub enum AiEvent { + /// Put this draft in the active editor tab. + Insert(String), + Notice(String, bool), +} + +impl AiDock { + pub fn new(workspace: &Workspace) -> Self { + Self { + status: workspace.ai_key_status(), + ..Self::default() + } + } + + pub fn refresh_status(&mut self, workspace: &Workspace) { + self.status = workspace.ai_key_status(); + } + + pub fn poll(&mut self, events: &mut Vec) { + if let Some(pending) = &mut self.pending + && let Some(result) = pending.poll() + { + self.pending = None; + match result { + Ok(response) => self.turns.push(Turn { + user: false, + text: response.message.content, + }), + Err(error) => { + let text = error.to_string(); + self.error = Some(text.clone()); + events.push(AiEvent::Notice(text, true)); + } + } + } + } + + pub fn ui( + &mut self, + ui: &mut Ui, + workspace: &Workspace, + active: Option<(DatabaseKind, QueryLanguage, String)>, + events: &mut Vec, + ) { + ui.horizontal(|ui| { + ui.heading("Ask"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.small_button("Clear").clicked() { + self.turns.clear(); + self.error = None; + } + }); + }); + match self.status.source { + AiKeySource::None => { + ui.label(RichText::new(NOT_SIGNED_IN_MESSAGE).color(theme::CAUTION)); + if ui.button("Open Redrob Launcher").clicked() { + let _ = webbrowser::open(Workspace::launcher_url()); + } + } + AiKeySource::Launcher => { + ui.label( + RichText::new(format!("{} · via Redrob Launcher", self.status.label)) + .small() + .weak(), + ); + } + AiKeySource::Own => { + ui.label( + RichText::new(format!("{} · this app's own key", self.status.label)) + .small() + .weak(), + ); + } + } + ui.separator(); + + let signed_in = self.status.signed_in(); + let scroll_height = (ui.available_height() - 120.0).max(80.0); + egui::ScrollArea::vertical() + .id_salt("ai-turns") + .auto_shrink([false, false]) + .max_height(scroll_height) + .stick_to_bottom(true) + .show(ui, |ui| { + if self.turns.is_empty() { + ui.label(RichText::new( + "Describe the query you want. The active tab's text is sent along as context; result rows never are.", + ).weak()); + } + for (index, turn) in self.turns.iter().enumerate() { + let who = if turn.user { "You" } else { "Redrob" }; + ui.label(RichText::new(who).small().strong().color(if turn.user { ui.visuals().text_color() } else { theme::REDROB_BLUE })); + if turn.user { + ui.label(&turn.text); + } else { + render_answer(ui, index, &turn.text, events); + } + ui.add_space(6.0); + } + if self.pending.is_some() { + ui.horizontal(|ui| { + ui.spinner(); + ui.label(RichText::new("Thinking…").weak()); + }); + } + if let Some(error) = &self.error { + ui.label(RichText::new(error).color(theme::DANGER)); + } + }); + + ui.separator(); + let can_ask = signed_in && self.pending.is_none() && active.is_some(); + let response = ui.add_enabled( + signed_in, + egui::TextEdit::multiline(&mut self.prompt) + .desired_rows(3) + .desired_width(f32::INFINITY) + .hint_text(if active.is_some() { + "e.g. customers who ordered twice this month, newest first" + } else { + "Open a query tab first" + }), + ); + let submit = response.has_focus() + && ui.input(|i| { + i.key_pressed(egui::Key::Enter) && (i.modifiers.command || i.modifiers.ctrl) + }); + ui.horizontal(|ui| { + if (ui + .add_enabled( + can_ask && !self.prompt.trim().is_empty(), + theme::primary_button("Ask (Ctrl+Enter)"), + ) + .clicked() + || (submit && can_ask)) + && let Some((kind, language, text)) = active + { + self.ask(workspace, kind, language, text); + } + }); + } + + fn ask( + &mut self, + workspace: &Workspace, + kind: DatabaseKind, + language: QueryLanguage, + active: String, + ) { + let prompt = self.prompt.trim().to_owned(); + if prompt.is_empty() { + return; + } + self.error = None; + self.turns.push(Turn { + user: true, + text: prompt.clone(), + }); + self.prompt.clear(); + let language = match language { + QueryLanguage::Auto => { + if kind == DatabaseKind::MongoDb { + QueryLanguage::MongoJson + } else { + QueryLanguage::Sql + } + } + other => other, + }; + self.pending = Some(workspace.ai_chat(AiAssistantRequest { + prompt, + active_query: Some(active).filter(|t| !t.trim().is_empty()), + database_kind: kind, + query_language: language, + })); + } +} + +/// Renders an answer: prose as labels, fenced code blocks as code with an Insert button. +fn render_answer(ui: &mut Ui, index: usize, text: &str, events: &mut Vec) { + for (block, piece) in split_fences(text) { + if block { + egui::Frame::group(ui.style()).show(ui, |ui| { + ui.horizontal(|ui| { + ui.add( + egui::Label::new(RichText::new(piece.trim()).monospace().small()).wrap(), + ); + }); + ui.horizontal(|ui| { + if ui + .add(theme::primary_button("Insert into editor")) + .clicked() + { + events.push(AiEvent::Insert(piece.trim().to_owned())); + } + if ui.small_button("Copy").clicked() { + ui.ctx().copy_text(piece.trim().to_owned()); + } + }); + }); + } else if !piece.trim().is_empty() { + ui.push_id(("ai-prose", index, piece.len()), |ui| { + ui.add(egui::Label::new(piece.trim()).wrap()); + }); + } + } +} + +/// Splits markdown into `(is_code_block, text)` pieces by triple-backtick fences; the language tag is dropped. +pub fn split_fences(text: &str) -> Vec<(bool, String)> { + let mut pieces = Vec::new(); + let mut in_block = false; + let mut current = String::new(); + for line in text.lines() { + if line.trim_start().starts_with("```") { + if !current.is_empty() || in_block { + pieces.push((in_block, std::mem::take(&mut current))); + } + in_block = !in_block; + continue; + } + current.push_str(line); + current.push('\n'); + } + if !current.is_empty() { + pieces.push((in_block, current)); + } + pieces +} diff --git a/apps/redrob-query/src/app.rs b/apps/redrob-query/src/app.rs new file mode 100644 index 0000000..af88226 --- /dev/null +++ b/apps/redrob-query/src/app.rs @@ -0,0 +1,663 @@ +//! The shell: one window with the profiles and schema on the left, query tabs in the middle (editor +//! above, result below, structure beside), the Ask dock on the right and a status line at the +//! bottom. Every panel talks to the engine through the `Workspace` and reports back as events the +//! app routes here, so no panel reaches into another. + +use egui::{RichText, Ui}; +use redrob_query_core::{ConnectionProfile, DatabaseKind, MetadataNode, QueryLanguage, Workspace}; +use uuid::Uuid; + +use crate::account::AccountWatch; +use crate::ai_dock::{AiDock, AiEvent}; +use crate::changes::{ChangesEvent, ChangesPanel}; +use crate::editor::QueryTab; +use crate::highlight::Dialect; +use crate::profiles::{ProfilesEvent, ProfilesPanel}; +use crate::schema::{SchemaBrowser, SchemaEvent, preview_query}; +use crate::services; +use crate::structure::{StructureEvent, StructurePanel}; +use crate::theme; +use crate::workspace::{HistoryEntry, SavedTab, WorkspaceFile, now_unix}; + +pub struct QueryApp { + workspace: Workspace, + profiles: ProfilesPanel, + schema: SchemaBrowser, + structure: StructurePanel, + changes: ChangesPanel, + ai: AiDock, + account: AccountWatch, + tabs: Vec, + active: usize, + history: Vec, + show_structure: bool, + show_ai: bool, + show_history: bool, + notice: Option<(String, bool)>, + tab_counter: usize, + dirty_since_save: bool, +} + +impl QueryApp { + pub fn new(ctx: &egui::Context) -> Self { + theme::apply(ctx); + let workspace = match services::config_dir() { + Some(dir) => Workspace::persistent(&dir).or_else(|error| { + log::warn!("profile storage unavailable ({error}); running without it"); + Workspace::ephemeral() + }), + None => Workspace::ephemeral(), + } + .unwrap_or_else(|error| { + // Without a tokio runtime nothing can run; say so and exit rather than show a dead window. + eprintln!("redrob-query: could not start the engine: {error}"); + std::process::exit(1); + }); + let repaint = ctx.clone(); + workspace.set_wake(move || repaint.request_repaint()); + + let saved = WorkspaceFile::load().unwrap_or_default(); + let mut tabs: Vec = saved + .tabs + .iter() + .map(|tab| QueryTab::new(tab.title.clone(), tab.text.clone(), None)) + .collect(); + for (tab, saved) in tabs.iter_mut().zip(&saved.tabs) { + tab.connection_id = saved.connection_id; + } + let profiles = ProfilesPanel::new(&workspace); + let ai = AiDock::new(&workspace); + let account = AccountWatch::new(&workspace); + let tab_counter = tabs.len(); + Self { + profiles, + schema: SchemaBrowser::default(), + structure: StructurePanel::default(), + changes: ChangesPanel::default(), + ai, + account, + tabs, + active: saved.active_tab.min(tab_counter.saturating_sub(1)), + history: saved.history, + show_structure: saved.structure_open, + show_ai: saved.ai_dock_open, + show_history: false, + notice: None, + tab_counter, + dirty_since_save: false, + workspace, + } + } + + fn notify(&mut self, text: impl Into, is_error: bool) { + let text = text.into(); + if is_error { + log::warn!("{text}"); + } + self.notice = Some((text, is_error)); + } + + fn profile(&self, id: Option) -> Option<&ConnectionProfile> { + id.and_then(|id| self.profiles.profile(id)) + } + + fn save_workspace(&mut self) { + let file = WorkspaceFile { + schema: 1, + tabs: self + .tabs + .iter() + .map(|tab| SavedTab { + title: tab.title.clone(), + text: tab.text.clone(), + connection_id: tab.connection_id, + }) + .collect(), + active_tab: self.active, + history: self.history.clone(), + ai_dock_open: self.show_ai, + structure_open: self.show_structure, + }; + file.save(); + self.dirty_since_save = false; + } + + fn open_tab_for(&mut self, profile: &ConnectionProfile, text: Option) { + // Re-use an open tab on this connection that is still its untouched starter. + if text.is_none() + && let Some(index) = self.tabs.iter().position(|t| { + t.connection_id == Some(profile.id) + && t.text.trim() == crate::highlight::starter_query(profile.kind).trim() + && t.result.is_none() + }) + { + self.active = index; + return; + } + self.tab_counter += 1; + let mut tab = match text { + Some(text) => QueryTab::new( + format!("{} {}", profile.name, self.tab_counter), + text, + Some(profile), + ), + None => QueryTab::for_connection(profile, self.tab_counter), + }; + tab.focus_editor = true; + if self.tabs.len() >= crate::workspace::MAX_TABS { + self.notify( + format!( + "At most {} tabs are kept; close one first.", + crate::workspace::MAX_TABS + ), + true, + ); + return; + } + self.tabs.push(tab); + self.active = self.tabs.len() - 1; + self.dirty_since_save = true; + } + + /// Tabs that were restored with a connection id get the live profile's dialect once the + /// profile list arrives. + fn bind_restored_tabs(&mut self) { + for tab in &mut self.tabs { + if let Some(profile) = tab.connection_id.and_then(|id| self.profiles.profile(id)) { + let dialect = Dialect::for_kind(profile.kind); + if tab.dialect != dialect || tab.read_only_profile != profile.read_only { + tab.set_connection(profile); + } + } + } + } + + fn poll(&mut self) { + let mut profile_events = Vec::new(); + self.profiles.poll(&self.workspace, &mut profile_events); + let mut connected_now = Vec::new(); + for event in profile_events { + match event { + ProfilesEvent::OpenQuery(profile) => self.open_tab_for(&profile, None), + ProfilesEvent::Connected(id) => connected_now.push(id), + ProfilesEvent::Disconnected(id) => { + self.schema.forget(id); + self.structure.forget(id); + } + ProfilesEvent::Notice(text, is_error) => self.notify(text, is_error), + } + } + for id in connected_now { + self.schema.load_root(&self.workspace, id); + } + self.bind_restored_tabs(); + + let mut schema_events = Vec::new(); + self.schema.poll(&mut schema_events); + self.handle_schema_events(schema_events); + + let mut structure_events = Vec::new(); + self.structure.poll(&mut structure_events); + for event in structure_events { + match event { + StructureEvent::Changed(id) => { + self.schema.load_root(&self.workspace, id); + self.notify("Table change applied", false); + } + StructureEvent::Notice(text, is_error) => self.notify(text, is_error), + } + } + + let mut changes_events = Vec::new(); + self.changes.poll(&mut changes_events); + for event in changes_events { + match event { + ChangesEvent::Applied(result) => { + self.notify( + format!( + "Applied {} row change(s) in {} ms", + result.rows_affected, result.elapsed_ms + ), + false, + ); + // Re-run the tab so the grid shows the committed state. + if let Some(tab) = self.tabs.get_mut(self.active) { + if let Some(grid) = &mut tab.result { + grid.clear_changes(); + } + tab.run_now(&self.workspace, None); + } + } + ChangesEvent::Notice(text, is_error) => self.notify(text, is_error), + } + } + + let mut ai_events = Vec::new(); + self.ai.poll(&mut ai_events); + for event in ai_events { + match event { + AiEvent::Insert(draft) => { + if let Some(tab) = self.tabs.get_mut(self.active) { + tab.insert(draft); + tab.focus_editor = true; + } else { + self.notify("Open a query tab to insert the draft into", true); + } + } + AiEvent::Notice(text, is_error) => self.notify(text, is_error), + } + } + + if self.account.poll(&self.workspace) { + self.ai.refresh_status(&self.workspace); + let text = if self.account.status.signed_in() { + format!("Signed in: {}", self.account.status.label) + } else { + "Signed out of Redrob".to_owned() + }; + self.notify(text, false); + } + + for index in 0..self.tabs.len() { + let profile = self.tabs[index] + .connection_id + .and_then(|id| self.profiles.profile(id)) + .cloned(); + self.tabs[index].poll(&self.workspace, profile.as_ref()); + for finished in self.tabs[index].take_finished() { + self.history.push(HistoryEntry { + connection_id: finished.connection_id, + connection_name: finished.connection_name, + query: finished.query, + ran_at_unix: now_unix(), + rows: finished.rows, + elapsed_ms: finished.elapsed_ms, + ok: finished.ok, + }); + if self.history.len() > crate::workspace::MAX_HISTORY { + let drop = self.history.len() - crate::workspace::MAX_HISTORY; + self.history.drain(..drop); + } + self.dirty_since_save = true; + } + } + } + + fn handle_schema_events(&mut self, events: Vec) { + for event in events { + match event { + SchemaEvent::Select { + connection_id, + node, + } => { + if let Some(kind) = self.profiles.profile(connection_id).map(|p| p.kind) { + self.structure + .show_table(&self.workspace, connection_id, kind, node); + self.show_structure = true; + } + } + SchemaEvent::Preview { + connection_id, + node, + } => { + if let Some(profile) = self.profiles.profile(connection_id).cloned() { + let query = preview_query(profile.kind, &node); + self.open_tab_for(&profile, Some(query)); + if let Some(tab) = self.tabs.get_mut(self.active) { + tab.title.clone_from(&node.name); + tab.run_now(&self.workspace, None); + } + } + } + SchemaEvent::Notice(text, is_error) => self.notify(text, is_error), + } + } + } + + fn top_bar(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + ui.label(RichText::new(services::APP_NAME).strong().size(16.0)); + ui.label(RichText::new(env!("CARGO_PKG_VERSION")).small().weak()); + ui.separator(); + ui.toggle_value(&mut self.show_structure, "Structure"); + ui.toggle_value(&mut self.show_history, "History"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.toggle_value(&mut self.show_ai, "Ask").changed() { + self.dirty_since_save = true; + } + ui.separator(); + let status = &self.account.status; + if status.signed_in() { + theme::status_dot(ui, theme::SUCCESS); + ui.label(RichText::new(&status.label).small()) + .on_hover_text(self.account.describe_paths()); + } else { + let weak = ui.visuals().weak_text_color(); + theme::status_dot(ui, weak); + if ui + .small_button("Sign in with Redrob Launcher") + .on_hover_text(self.account.describe_paths()) + .clicked() + { + let _ = webbrowser::open(Workspace::launcher_url()); + } + } + }); + }); + } + + fn status_bar(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + match &self.notice { + Some((text, is_error)) => { + let colour = theme::tone_for_error(ui, *is_error); + ui.label(RichText::new(text).small().color(colour)); + } + None => { + ui.label(RichText::new("Ready").small().weak()); + } + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let connected = self + .profiles + .profiles + .iter() + .filter(|p| self.profiles.is_connected(p.id)) + .count(); + ui.label( + RichText::new(format!("{connected} connected · {} tabs", self.tabs.len())) + .small() + .weak(), + ); + }); + }); + } + + fn tab_bar(&mut self, ui: &mut Ui) { + let mut close: Option = None; + let mut new_tab = false; + egui::ScrollArea::horizontal() + .id_salt("tabs") + .auto_shrink([false, true]) + .show(ui, |ui| { + ui.horizontal(|ui| { + for (index, tab) in self.tabs.iter().enumerate() { + let mut title = tab.title.clone(); + if tab.is_running() { + title.push_str(" ⟳"); + } else if tab + .result + .as_ref() + .is_some_and(crate::grid::ResultGrid::has_changes) + { + title.push_str(" •"); + } + let selected = index == self.active; + let text = if selected { + RichText::new(title).color(theme::REDROB_BLUE).strong() + } else { + RichText::new(title) + }; + let response = ui.selectable_label(selected, text); + if response.clicked() { + self.active = index; + } + if response.middle_clicked() { + close = Some(index); + } + response.context_menu(|ui| { + if ui.button("Close").clicked() { + close = Some(index); + ui.close(); + } + }); + } + if ui + .button("+") + .on_hover_text("New tab on the selected profile") + .clicked() + { + new_tab = true; + } + }); + }); + if let Some(index) = close { + self.tabs.remove(index); + if self.active >= self.tabs.len() { + self.active = self.tabs.len().saturating_sub(1); + } + self.dirty_since_save = true; + } + if new_tab { + let selected = self + .profiles + .selected + .and_then(|id| self.profiles.profile(id)) + .cloned() + .or_else(|| { + self.profiles + .profiles + .iter() + .find(|p| self.profiles.is_connected(p.id)) + .cloned() + }); + match selected { + Some(profile) if self.profiles.is_connected(profile.id) => { + self.tab_counter += 1; + let mut tab = QueryTab::for_connection(&profile, self.tab_counter); + tab.focus_editor = true; + self.tabs.push(tab); + self.active = self.tabs.len() - 1; + self.dirty_since_save = true; + } + Some(profile) => self.profiles.connect(&self.workspace, profile.id), + None => self.notify("Select or connect a profile first", true), + } + } + } + + fn history_ui(&mut self, ui: &mut Ui) { + ui.heading("History"); + let mut rerun: Option = None; + egui::ScrollArea::vertical() + .id_salt("history") + .auto_shrink([false, false]) + .show(ui, |ui| { + if self.history.is_empty() { + ui.label(RichText::new("Nothing has run yet.").weak()); + } + for entry in self.history.iter().rev() { + let one_line: String = + entry.query.split_whitespace().collect::>().join(" "); + let shown: String = one_line.chars().take(90).collect(); + let colour = if entry.ok { + ui.visuals().text_color() + } else { + theme::DANGER + }; + let response = ui.add( + egui::Label::new(RichText::new(shown).monospace().small().color(colour)) + .sense(egui::Sense::click()) + .truncate(), + ); + let meta = format!( + "{} · {} rows · {} ms", + entry.connection_name, entry.rows, entry.elapsed_ms + ); + ui.label(RichText::new(meta).small().weak()); + if response.double_clicked() { + rerun = Some(entry.clone()); + } + response + .on_hover_text(format!("{}\n\nDouble-click to open in a tab", entry.query)); + ui.separator(); + } + }); + if let Some(entry) = rerun { + if let Some(profile) = self.profile(entry.connection_id).cloned() { + self.open_tab_for(&profile, Some(entry.query)); + } else { + self.notify("That connection is not available any more", true); + } + } + } + + fn central(&mut self, ui: &mut Ui) { + self.tab_bar(ui); + ui.separator(); + if self.tabs.is_empty() { + ui.vertical_centered(|ui| { + ui.add_space(ui.available_height() * 0.3); + ui.label( + RichText::new("Connect a profile on the left, then open a query tab.").weak(), + ); + ui.label( + RichText::new("The built-in Demo SQLite database works offline.") + .small() + .weak(), + ); + }); + return; + } + let active = self.active.min(self.tabs.len() - 1); + self.active = active; + let profile = self.tabs[active] + .connection_id + .and_then(|id| self.profiles.profile(id)) + .cloned(); + let read_only = profile.as_ref().is_none_or(|p| p.read_only); + let collections: Vec = profile + .as_ref() + .filter(|p| p.kind == DatabaseKind::MongoDb) + .map(|p| self.schema.roots(p.id).to_vec()) + .unwrap_or_default(); + + let mut notice = None; + egui::Panel::top("editor-pane") + .resizable(true) + .default_size(260.0) + .size_range(120.0..=f32::INFINITY) + .show(ui, |ui| { + notice = self.tabs[active].editor_ui( + ui, + &self.workspace, + profile.as_ref(), + &collections, + ); + }); + if let Some(text) = notice { + self.notify(text, false); + } + + if self.show_structure { + egui::Panel::right("structure-pane") + .resizable(true) + .default_size(300.0) + .show(ui, |ui| { + self.structure.ui(ui, &self.workspace, read_only); + }); + } + let tab = &mut self.tabs[active]; + if let Some(grid) = &mut tab.result + && grid.has_changes() + { + egui::Panel::bottom("changes-pane") + .resizable(true) + .default_size(160.0) + .show(ui, |ui| { + self.changes + .ui(ui, &self.workspace, grid, tab.source.as_ref(), read_only); + }); + } + egui::CentralPanel::default().show(ui, |ui| match &mut tab.result { + Some(grid) => grid.ui(ui), + None => { + ui.label(RichText::new("Run a query to see its rows here (Ctrl+Enter).").weak()); + } + }); + } +} + +impl eframe::App for QueryApp { + fn ui(&mut self, ui: &mut Ui, _frame: &mut eframe::Frame) { + self.poll(); + + egui::Panel::top("top-bar").show(ui, |ui| self.top_bar(ui)); + egui::Panel::bottom("status-bar").show(ui, |ui| self.status_bar(ui)); + egui::Panel::left("side") + .resizable(true) + .default_size(280.0) + .size_range(200.0..=600.0) + .show(ui, |ui| { + let mut events = Vec::new(); + self.profiles.ui(ui, &self.workspace, &mut events); + for event in events { + match event { + ProfilesEvent::OpenQuery(profile) => self.open_tab_for(&profile, None), + ProfilesEvent::Connected(id) => self.schema.load_root(&self.workspace, id), + ProfilesEvent::Disconnected(id) => { + self.schema.forget(id); + self.structure.forget(id); + } + ProfilesEvent::Notice(text, is_error) => self.notify(text, is_error), + } + } + ui.separator(); + let connected: Vec = self + .profiles + .profiles + .iter() + .filter(|p| self.profiles.is_connected(p.id)) + .cloned() + .collect(); + let refs: Vec<&ConnectionProfile> = connected.iter().collect(); + let mut events = Vec::new(); + self.schema.ui(ui, &self.workspace, &refs, &mut events); + self.handle_schema_events(events); + }); + if self.show_ai { + egui::Panel::right("ai-dock") + .resizable(true) + .default_size(340.0) + .size_range(260.0..=700.0) + .show(ui, |ui| { + let active = self.tabs.get(self.active).and_then(|tab| { + let profile = tab.connection_id.and_then(|id| self.profiles.profile(id))?; + let language = match tab.dialect { + Dialect::Sql => QueryLanguage::Sql, + Dialect::MongoJson => QueryLanguage::MongoJson, + }; + Some((profile.kind, language, tab.text.clone())) + }); + let mut events = Vec::new(); + self.ai.ui(ui, &self.workspace, active, &mut events); + for event in events { + match event { + AiEvent::Insert(draft) => { + if let Some(tab) = self.tabs.get_mut(self.active) { + tab.insert(draft); + tab.focus_editor = true; + } + } + AiEvent::Notice(text, is_error) => self.notify(text, is_error), + } + } + }); + } + if self.show_history { + egui::Panel::right("history-pane") + .resizable(true) + .default_size(300.0) + .show(ui, |ui| self.history_ui(ui)); + } + egui::CentralPanel::default().show(ui, |ui| self.central(ui)); + + if self.dirty_since_save { + self.save_workspace(); + } + } + + fn on_exit(&mut self) { + self.save_workspace(); + } +} diff --git a/apps/redrob-query/src/changes.rs b/apps/redrob-query/src/changes.rs new file mode 100644 index 0000000..1eb550c --- /dev/null +++ b/apps/redrob-query/src/changes.rs @@ -0,0 +1,225 @@ +//! The Changes panel: the staged cell edits and row deletions of a result, reviewed as a list, +//! then applied through `apply_cell_edits` in one transaction (each row update must hit exactly +//! one row or everything rolls back). Nothing is written until Apply is pressed. + +use egui::{RichText, Ui}; +use redrob_query_core::{ + DataValue, MutationResult, Pending, Workspace, + cell_edits::{CellChange, CellEditSet, RowEdit}, +}; +use uuid::Uuid; + +use crate::grid::{ResultGrid, format_value}; +use crate::theme; + +/// Which table a result came from, when the editor could tell: a single-table SELECT, or a +/// preview opened from the schema tree. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResultSource { + pub connection_id: Uuid, + pub schema: Option, + pub table: String, +} + +/// The one-column primary key the result can be edited by, or why it cannot. +pub fn editable_key(grid: &ResultGrid) -> Result<(String, usize), &'static str> { + match grid.key_columns.as_slice() { + [] => Err("the table has no primary key"), + [key] => grid + .page + .columns + .iter() + .position(|c| &c.name == key) + .map(|index| (key.clone(), index)) + .ok_or("select the primary key column to edit"), + _ => Err("composite primary keys cannot be edited here yet"), + } +} + +/// The staged state as the engine's change set. Rows whose key is NULL are refused. +pub fn build_edit_set(grid: &ResultGrid, source: &ResultSource) -> Result { + let (key, key_index) = editable_key(grid).map_err(str::to_owned)?; + let key_of = |row: usize| -> Result { + match grid.page.rows.get(row).and_then(|r| r.get(key_index)) { + Some(DataValue::Null) | None => { + Err(format!("row {} has no primary key value", row + 1)) + } + Some(value) => Ok(value.clone()), + } + }; + let mut rows: Vec = Vec::new(); + for (&(row, column), value) in &grid.staged { + if grid.deleted.contains(&row) { + continue; + } + if column == key_index { + return Err("the primary key column itself cannot be edited".to_owned()); + } + let change = CellChange { + column: grid.page.columns[column].name.clone(), + value: value.clone(), + }; + let key_value = key_of(row)?; + match rows.iter_mut().find(|r| r.key == key_value) { + Some(existing) => existing.changes.push(change), + None => rows.push(RowEdit { + key: key_value, + changes: vec![change], + }), + } + } + let deletes = grid + .deleted + .iter() + .map(|&row| key_of(row)) + .collect::, _>>()?; + Ok(CellEditSet { + schema: source.schema.clone(), + table: source.table.clone(), + primary_key: key, + rows, + inserts: Vec::new(), + deletes, + }) +} + +#[derive(Default)] +pub struct ChangesPanel { + apply: Option>, + pub last: Option<(String, bool)>, +} + +/// What the panel asks the app to do after an apply. +pub enum ChangesEvent { + Applied(MutationResult), + Notice(String, bool), +} + +impl ChangesPanel { + pub fn busy(&self) -> bool { + self.apply.is_some() + } + + pub fn poll(&mut self, events: &mut Vec) { + if let Some(apply) = &mut self.apply + && let Some(result) = apply.poll() + { + self.apply = None; + match result { + Ok(outcome) => { + self.last = Some(( + format!( + "Applied: {} rows changed in {} ms", + outcome.rows_affected, outcome.elapsed_ms + ), + true, + )); + events.push(ChangesEvent::Applied(outcome)); + } + Err(error) => { + self.last = Some((error.to_string(), false)); + events.push(ChangesEvent::Notice(error.to_string(), true)); + } + } + } + } + + pub fn ui( + &mut self, + ui: &mut Ui, + workspace: &Workspace, + grid: &mut ResultGrid, + source: Option<&ResultSource>, + read_only_profile: bool, + ) { + ui.horizontal(|ui| { + ui.heading("Changes"); + let count = grid.staged.len() + grid.deleted.len(); + ui.label(RichText::new(format!("{count} staged")).weak()); + }); + if let Some((text, ok)) = &self.last { + ui.label(RichText::new(text).color(if *ok { theme::SUCCESS } else { theme::DANGER })); + } + let Some(source) = source else { + ui.label(RichText::new("Edits need a result from one table.").weak()); + return; + }; + if read_only_profile { + ui.label(RichText::new("This profile is read-only. Untick \"Read-only\" in its connection form to allow reviewed edits.").color(theme::CAUTION)); + } + let set = build_edit_set(grid, source); + egui::ScrollArea::vertical() + .id_salt("changes") + .auto_shrink([false, true]) + .max_height(220.0) + .show(ui, |ui| { + for (&(row, column), value) in &grid.staged { + let before = grid + .page + .rows + .get(row) + .and_then(|r| r.get(column)) + .map(format_value) + .unwrap_or_default(); + ui.horizontal(|ui| { + ui.label(RichText::new(format!("row {}", row + 1)).small().weak()); + ui.label(RichText::new(&grid.page.columns[column].name).monospace()); + ui.label(RichText::new(before).weak().strikethrough()); + ui.label("→"); + ui.label(RichText::new(format_value(value)).color(theme::CAUTION)); + }); + } + for &row in &grid.deleted { + ui.horizontal(|ui| { + ui.label(RichText::new(format!("row {}", row + 1)).small().weak()); + ui.label(RichText::new("delete").color(theme::DANGER)); + }); + } + }); + match &set { + Ok(set) => { + ui.label( + RichText::new(format!( + "{} row update(s), {} delete(s) on {}{} keyed by {}", + set.rows.len(), + set.deletes.len(), + set.schema + .as_deref() + .map(|s| format!("{s}.")) + .unwrap_or_default(), + set.table, + set.primary_key + )) + .small(), + ); + } + Err(reason) => { + ui.label(RichText::new(reason).small().color(theme::CAUTION)); + } + } + ui.horizontal(|ui| { + let can_apply = set.is_ok() && grid.has_changes() && !self.busy() && !read_only_profile; + if ui + .add_enabled(can_apply, theme::primary_button("Apply")) + .clicked() + && let Ok(set) = set + { + self.last = None; + self.apply = Some(workspace.apply_cell_edits(source.connection_id, set)); + } + if ui + .add_enabled( + grid.has_changes() && !self.busy(), + egui::Button::new("Discard"), + ) + .clicked() + { + grid.clear_changes(); + self.last = None; + } + if self.busy() { + ui.spinner(); + } + }); + } +} diff --git a/apps/redrob-query/src/editor.rs b/apps/redrob-query/src/editor.rs new file mode 100644 index 0000000..ed25ae1 --- /dev/null +++ b/apps/redrob-query/src/editor.rs @@ -0,0 +1,502 @@ +//! Query tabs: the editor with syntax colouring, the run / format / save shortcuts, and each tab's +//! result grid with its source table (for the changes and structure panels). +//! +//! Ctrl+Enter runs the selection, else the statement under the cursor (SQL) or the whole text +//! (Mongo JSON); a batch of several read-only statements runs them one by one and shows the last +//! result. Ctrl+Shift+F formats. Ctrl+S saves the tab's text to a file (asking for one the first +//! time). The workspace itself (tabs, history) is saved by the app. + +use std::path::PathBuf; + +use egui::{RichText, Ui}; +use redrob_query_core::{ + ConnectionProfile, DatabaseKind, Pending, QueryClassification, QueryRequest, QueryResultPage, + Workspace, query::classify_sql, +}; +use uuid::Uuid; + +use crate::changes::ResultSource; +use crate::grid::ResultGrid; +use crate::highlight::{self, Dialect}; +use crate::mongo::MongoStrip; +use crate::theme; + +const DEFAULT_LIMIT: usize = 500; + +/// A run that finished, for the history list. +pub struct Finished { + pub connection_id: Option, + pub connection_name: String, + pub query: String, + pub rows: usize, + pub elapsed_ms: u64, + pub ok: bool, +} + +pub struct QueryTab { + pub title: String, + pub text: String, + pub connection_id: Option, + pub dialect: Dialect, + pub result: Option, + pub source: Option, + pub error: Option, + pub mongo: MongoStrip, + pub limit: usize, + pub file: Option, + run: Option>, + keys: Option>>, + /// Statements still to run in a batch, and how many ran. + queue: Vec, + ran_in_batch: usize, + running_text: String, + cursor_char: usize, + finished: Vec, + pub read_only_profile: bool, + pending_insert: Option, + pub focus_editor: bool, +} + +impl QueryTab { + pub fn new(title: String, text: String, connection: Option<&ConnectionProfile>) -> Self { + let dialect = connection.map_or(Dialect::Sql, |p| Dialect::for_kind(p.kind)); + let mut mongo = MongoStrip::default(); + if dialect == Dialect::MongoJson { + mongo.sync_from(&text); + } + Self { + title, + text, + connection_id: connection.map(|p| p.id), + dialect, + result: None, + source: None, + error: None, + mongo, + limit: DEFAULT_LIMIT, + file: None, + run: None, + keys: None, + queue: Vec::new(), + ran_in_batch: 0, + running_text: String::new(), + cursor_char: 0, + finished: Vec::new(), + read_only_profile: connection.is_none_or(|p| p.read_only), + pending_insert: None, + focus_editor: false, + } + } + + pub fn for_connection(profile: &ConnectionProfile, index: usize) -> Self { + let text = highlight::starter_query(profile.kind).to_owned(); + Self::new(format!("{} {}", profile.name, index), text, Some(profile)) + } + + pub fn is_running(&self) -> bool { + self.run.is_some() + } + + /// Runs finished since the last call. + pub fn take_finished(&mut self) -> Vec { + std::mem::take(&mut self.finished) + } + + /// Appends (or replaces an empty tab with) a draft from the AI dock. + pub fn insert(&mut self, draft: String) { + self.pending_insert = Some(draft); + } + + pub fn set_connection(&mut self, profile: &ConnectionProfile) { + self.connection_id = Some(profile.id); + self.dialect = Dialect::for_kind(profile.kind); + self.read_only_profile = profile.read_only; + } + + pub fn poll(&mut self, workspace: &Workspace, profile: Option<&ConnectionProfile>) { + if let Some(run) = &mut self.run + && let Some(result) = run.poll() + { + self.run = None; + let (rows, elapsed, ok) = match &result { + Ok(page) => (page.stats.rows_returned, page.stats.elapsed_ms, true), + Err(_) => (0, 0, false), + }; + self.finished.push(Finished { + connection_id: self.connection_id, + connection_name: profile.map(|p| p.name.clone()).unwrap_or_default(), + query: self.running_text.clone(), + rows, + elapsed_ms: elapsed, + ok, + }); + match result { + Ok(page) => { + self.error = None; + let mut grid = ResultGrid::new(page); + grid.json_view = self.dialect == Dialect::MongoJson; + self.result = Some(grid); + self.ran_in_batch += 1; + if self.queue.is_empty() { + self.resolve_source(workspace, profile); + } else if let Some(connection_id) = self.connection_id { + let next = self.queue.remove(0); + self.start(workspace, connection_id, next); + } + } + Err(error) => { + self.queue.clear(); + self.error = Some(error.to_string()); + } + } + } + if let Some(keys) = &mut self.keys + && let Some(result) = keys.poll() + { + self.keys = None; + if let (Ok(columns), Some(grid)) = (result, &mut self.result) { + grid.key_columns = columns; + grid.editable = grid.key_columns.len() == 1 && !self.read_only_profile; + } + } + } + + /// Works out which table the result came from (a single-table SELECT), then asks for its key. + fn resolve_source(&mut self, workspace: &Workspace, profile: Option<&ConnectionProfile>) { + self.source = None; + let Some(profile) = profile else { return }; + if profile.kind == DatabaseKind::MongoDb { + return; + } + if let Some((schema, table)) = single_table_of(&self.running_text, profile.kind) { + self.source = Some(ResultSource { + connection_id: profile.id, + schema: schema.clone(), + table: table.clone(), + }); + self.keys = Some(workspace.primary_key_columns(profile.id, schema, table)); + } + } + + fn start(&mut self, workspace: &Workspace, connection_id: Uuid, query: String) { + self.running_text.clone_from(&query); + self.run = Some(workspace.execute_query(QueryRequest { + connection_id, + query, + language: self.dialect.language(), + parameters: Vec::new(), + limit: self.limit, + offset: 0, + timeout_ms: 30_000, + })); + } + + /// Runs `selection` when given, else the statement under the cursor; a read-only batch runs + /// statement by statement. + pub fn run_now(&mut self, workspace: &Workspace, selection: Option) { + let Some(connection_id) = self.connection_id else { + self.error = Some("choose a connection for this tab first".to_owned()); + return; + }; + if self.run.is_some() { + return; + } + self.error = None; + self.ran_in_batch = 0; + let text = match selection { + Some(s) if !s.trim().is_empty() => s, + _ => highlight::statement_at(&self.text, self.cursor_char, self.dialect), + }; + if text.trim().is_empty() { + self.error = Some("nothing to run".to_owned()); + return; + } + if self.dialect == Dialect::Sql + && let Some(mut statements) = Workspace::split_read_only_batch(&text) + && statements.len() > 1 + { + let first = statements.remove(0); + self.queue = statements; + self.start(workspace, connection_id, first); + return; + } + self.queue.clear(); + self.start(workspace, connection_id, text); + } + + pub fn format_text(&mut self) { + self.text = highlight::format(&self.text, self.dialect); + } + + /// Ctrl+S: write the tab's text to its file, asking for one the first time. + pub fn save_to_file(&mut self, save_as: bool) -> Option { + if save_as || self.file.is_none() { + let extension = if self.dialect == Dialect::MongoJson { + "json" + } else { + "sql" + }; + let picked = rfd::FileDialog::new() + .set_file_name(format!( + "{}.{extension}", + self.title.replace(' ', "-").to_lowercase() + )) + .save_file()?; + self.file = Some(picked); + } + let path = self.file.clone()?; + match std::fs::write(&path, &self.text) { + Ok(()) => { + if let Some(stem) = path.file_stem() { + self.title = stem.to_string_lossy().into_owned(); + } + Some(format!("Saved {}", path.display())) + } + Err(error) => { + self.error = Some(format!("could not save: {error}")); + None + } + } + } + + /// Draws the editor half of the tab. Returns a status-line message when there is one. + pub fn editor_ui( + &mut self, + ui: &mut Ui, + workspace: &Workspace, + profile: Option<&ConnectionProfile>, + collections: &[redrob_query_core::MetadataNode], + ) -> Option { + let mut notice = None; + if let Some(draft) = self.pending_insert.take() { + if self.text.trim().is_empty() { + self.text = draft; + } else { + if !self.text.ends_with('\n') { + self.text.push('\n'); + } + self.text.push('\n'); + self.text.push_str(&draft); + } + } + // Toolbar. + ui.horizontal(|ui| { + let running = self.run.is_some(); + let can_run = self.connection_id.is_some() && !running; + if ui + .add_enabled(can_run, theme::primary_button("▶ Run")) + .on_hover_text("Ctrl+Enter: the selection or the statement under the cursor") + .clicked() + { + self.run_now(workspace, None); + } + if running { + ui.spinner(); + if !self.queue.is_empty() || self.ran_in_batch > 0 { + ui.label( + RichText::new(format!( + "statement {} of {}", + self.ran_in_batch + 1, + self.ran_in_batch + 1 + self.queue.len() + )) + .small(), + ); + } + } + if ui.button("Format").on_hover_text("Ctrl+Shift+F").clicked() { + self.format_text(); + } + if ui + .button("Save") + .on_hover_text("Ctrl+S: save the text to a file") + .clicked() + { + notice = self.save_to_file(false); + } + ui.label(RichText::new("Limit").small()); + ui.add( + egui::DragValue::new(&mut self.limit) + .range(1..=10_000) + .speed(10), + ); + if let Some(profile) = profile { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let classification = if self.dialect == Dialect::Sql { + Some(classify_sql(&highlight::statement_at( + &self.text, + self.cursor_char, + self.dialect, + ))) + } else { + None + }; + if let Some(c) = classification { + let (text, colour) = match c { + QueryClassification::ReadOnly => ("read-only", theme::SUCCESS), + QueryClassification::Mutation => { + ("mutation (use the grid to edit)", theme::CAUTION) + } + QueryClassification::Destructive => { + ("destructive (refused)", theme::DANGER) + } + QueryClassification::TransactionControl => { + ("transaction control (refused)", theme::CAUTION) + } + QueryClassification::Unknown => ("unknown (refused)", theme::CAUTION), + }; + ui.label(RichText::new(text).small().color(colour)); + } + ui.label( + RichText::new(format!( + "{} · {}", + profile.name, + crate::profiles::kind_name(profile.kind) + )) + .small() + .weak(), + ); + }); + } else { + ui.label( + RichText::new("no connection: connect a profile, then open a tab from it") + .small() + .color(theme::CAUTION), + ); + } + }); + if self.dialect == Dialect::MongoJson + && let Some(template) = self.mongo.ui(ui, collections) + { + self.text = template; + } + + // The editor itself. + let dialect = self.dialect; + let mut layouter = move |ui: &Ui, text: &dyn egui::TextBuffer, wrap: f32| { + let job = highlight::layout(ui, text.as_str(), dialect, wrap); + ui.fonts_mut(|f| f.layout_job(job)) + }; + let output = egui::TextEdit::multiline(&mut self.text) + .code_editor() + .id_salt("query-editor") + .desired_width(f32::INFINITY) + .desired_rows(8) + .lock_focus(true) + .layouter(&mut layouter) + .show(ui); + if self.focus_editor { + output.response.request_focus(); + self.focus_editor = false; + } + if let Some(range) = output.cursor_range { + self.cursor_char = range.primary.index.0; + } + let selection = output + .cursor_range + .filter(|r| !r.is_empty()) + .map(|r| r.slice_str(&self.text).to_owned()); + let focused = output.response.has_focus(); + let (run, format, save, save_as) = ui.input_mut(|i| { + let cmd = egui::Modifiers::COMMAND; + ( + focused && i.consume_key(cmd, egui::Key::Enter), + focused && i.consume_key(cmd | egui::Modifiers::SHIFT, egui::Key::F), + i.consume_key(cmd, egui::Key::S), + i.consume_key(cmd | egui::Modifiers::SHIFT, egui::Key::S), + ) + }); + if run { + self.run_now(workspace, selection); + } + if format { + self.format_text(); + } + if save_as { + notice = self.save_to_file(true); + } else if save { + notice = self.save_to_file(false); + } + if self.dialect == Dialect::MongoJson && output.response.changed() { + self.mongo.sync_from(&self.text); + } + if let Some(error) = &self.error { + ui.label(RichText::new(error).color(theme::DANGER)); + } + notice + } +} + +/// `(schema, table)` of a SELECT that reads from exactly one table with no join, so the result can +/// be edited by its primary key. Quoted identifiers are unquoted; anything else is `None`. +pub fn single_table_of(sql: &str, kind: DatabaseKind) -> Option<(Option, String)> { + let upper = sql.to_ascii_uppercase(); + let words: Vec<&str> = upper.split_whitespace().collect(); + if words.first().copied() != Some("SELECT") { + return None; + } + if words.iter().any(|w| { + matches!( + *w, + "JOIN" | "UNION" | "GROUP" | "DISTINCT" | "INTERSECT" | "EXCEPT" + ) + }) || upper.matches("SELECT").count() != 1 + || upper.contains(" FROM (") + { + return None; + } + // The token after FROM, in the original text's case. + let from_at = upper.find(" FROM ")?; + let after = sql[from_at + 6..].trim_start(); + let end = after + .find(|c: char| c.is_whitespace() || c == ';' || c == ',') + .unwrap_or(after.len()); + let target = &after[..end]; + if target.is_empty() || target.contains('(') { + return None; + } + // An alias or a second table after a comma means more than one relation. + let rest = after[end..].trim_start(); + if rest.starts_with(',') { + return None; + } + let unquote = |s: &str| -> String { + let s = s.trim(); + let quoted = (s.starts_with('"') && s.ends_with('"')) + || (s.starts_with('`') && s.ends_with('`')) + || (s.starts_with('[') && s.ends_with(']')); + if quoted && s.len() >= 2 { + s[1..s.len() - 1].replace("\"\"", "\"").replace("``", "`") + } else { + s.to_owned() + } + }; + let parts: Vec<&str> = split_qualified(target); + match (parts.as_slice(), kind) { + ([table], _) => Some((None, unquote(table))), + ([schema, table], DatabaseKind::PostgreSql | DatabaseKind::MySql) => { + Some((Some(unquote(schema)), unquote(table))) + } + _ => None, + } +} + +/// Splits `a.b` on dots outside quotes. +fn split_qualified(target: &str) -> Vec<&str> { + let mut parts = Vec::new(); + let mut start = 0; + let mut quote: Option = None; + for (i, c) in target.char_indices() { + match (quote, c) { + (Some(q), c) if c == q => quote = None, + (None, '"' | '`') => quote = Some(c), + (None, '[') => quote = Some(']'), + (None, '.') => { + parts.push(&target[start..i]); + start = i + 1; + } + _ => {} + } + } + parts.push(&target[start..]); + parts +} diff --git a/apps/redrob-query/src/grid.rs b/apps/redrob-query/src/grid.rs new file mode 100644 index 0000000..8cd7cb9 --- /dev/null +++ b/apps/redrob-query/src/grid.rs @@ -0,0 +1,630 @@ +//! The result grid: one `QueryResultPage` shown as a virtualised table. +//! +//! Sorting and filtering act on the page the engine returned (at most the request's limit), never +//! on the database, so they cost nothing and promise nothing more than what was fetched. Cells are +//! formatted from `DataValue` for display and parsed back on edit; an edit is only staged -- the +//! changes panel turns the staged cells into a `CellEditSet` the engine applies in one transaction. + +use std::collections::{BTreeMap, BTreeSet}; + +use egui::{RichText, Ui}; +use egui_extras::{Column, TableBuilder}; +use redrob_query_core::{DataValue, QueryResultPage}; + +use crate::theme; + +const ROW_HEIGHT: f32 = 22.0; +const MAX_CELL_CHARS: usize = 200; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SortDirection { + Ascending, + Descending, +} + +enum EditAction { + Start(usize, usize), + Draft(String), + Commit, + Cancel, + SetNull(usize, usize), + Revert(usize, usize), +} + +pub struct ResultGrid { + pub page: QueryResultPage, + sort: Option<(usize, SortDirection)>, + pub filter: String, + hidden: BTreeSet, + /// Row indices of the page in display order after filter and sort. + order: Vec, + order_dirty: bool, + /// (row, column) -> new value, staged by inline editing. + pub staged: BTreeMap<(usize, usize), DataValue>, + pub deleted: BTreeSet, + editing: Option<(usize, usize, String)>, + /// Columns of the primary key, when the result is one editable table; empty is read-only. + pub key_columns: Vec, + pub editable: bool, + pub json_view: bool, + pub selected_row: Option, +} + +impl ResultGrid { + pub fn new(page: QueryResultPage) -> Self { + let rows = page.rows.len(); + Self { + page, + sort: None, + filter: String::new(), + hidden: BTreeSet::new(), + order: (0..rows).collect(), + order_dirty: false, + staged: BTreeMap::new(), + deleted: BTreeSet::new(), + editing: None, + key_columns: Vec::new(), + editable: false, + json_view: false, + selected_row: None, + } + } + + pub fn has_changes(&self) -> bool { + !self.staged.is_empty() || !self.deleted.is_empty() + } + + pub fn clear_changes(&mut self) { + self.staged.clear(); + self.deleted.clear(); + self.editing = None; + } + + fn rebuild_order(&mut self) { + let filter = self.filter.trim().to_lowercase(); + let mut order: Vec = (0..self.page.rows.len()) + .filter(|&row| { + filter.is_empty() + || self.page.rows[row] + .iter() + .any(|value| format_value(value).to_lowercase().contains(&filter)) + }) + .collect(); + if let Some((column, direction)) = self.sort { + order.sort_by(|&a, &b| { + let left = self.page.rows[a].get(column); + let right = self.page.rows[b].get(column); + let ordering = compare(left, right); + match direction { + SortDirection::Ascending => ordering, + SortDirection::Descending => ordering.reverse(), + } + }); + } + self.order = order; + self.order_dirty = false; + } + + /// The value shown in a cell: a staged edit wins over the fetched one. + pub fn cell(&self, row: usize, column: usize) -> Option<&DataValue> { + self.staged + .get(&(row, column)) + .or_else(|| self.page.rows.get(row).and_then(|r| r.get(column))) + } + + /// The whole page as tab-separated text (header first), hidden columns left out. + pub fn to_tsv(&self) -> String { + let columns: Vec = (0..self.page.columns.len()) + .filter(|c| !self.hidden.contains(c)) + .collect(); + let mut out = String::new(); + out.push_str( + &columns + .iter() + .map(|&c| self.page.columns[c].name.clone()) + .collect::>() + .join("\t"), + ); + out.push('\n'); + for &row in &self.order { + let line = columns + .iter() + .map(|&c| { + self.cell(row, c) + .map(format_value) + .unwrap_or_default() + .replace(['\t', '\n'], " ") + }) + .collect::>() + .join("\t"); + out.push_str(&line); + out.push('\n'); + } + out + } + + /// The page as a JSON array of objects, in display order. + pub fn to_json(&self) -> String { + let documents: Vec = + self.order.iter().map(|&row| self.row_json(row)).collect(); + serde_json::to_string_pretty(&documents).unwrap_or_default() + } + + pub fn row_json(&self, row: usize) -> serde_json::Value { + let mut object = serde_json::Map::new(); + for (c, column) in self.page.columns.iter().enumerate() { + if self.hidden.contains(&c) { + continue; + } + if let Some(value) = self.cell(row, c) { + object.insert(column.name.clone(), value_json(value)); + } + } + serde_json::Value::Object(object) + } + + pub fn ui(&mut self, ui: &mut Ui) { + if self.order_dirty || self.order.len() > self.page.rows.len() { + self.rebuild_order(); + } + self.toolbar(ui); + if self.json_view { + self.json_ui(ui); + } else { + self.table_ui(ui); + } + } + + fn toolbar(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + let stats = &self.page.stats; + let mut summary = format!("{} rows · {} ms", stats.rows_returned, stats.elapsed_ms); + if stats.rows_affected > 0 { + summary.push_str(&format!(" · {} affected", stats.rows_affected)); + } + ui.label(RichText::new(summary).small()); + if stats.truncated { + ui.label( + RichText::new("truncated at the limit") + .small() + .color(theme::CAUTION), + ); + } + if self.editable { + ui.label(RichText::new("editable").small().color(theme::SUCCESS)) + .on_hover_text(format!("Primary key: {}", self.key_columns.join(", "))); + } else { + ui.label(RichText::new("read-only").small().weak()) + .on_hover_text( + "Edits need one table with a primary key and a writable profile", + ); + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.menu_button("Export", |ui| { + if ui.button("Copy as TSV").clicked() { + ui.ctx().copy_text(self.to_tsv()); + ui.close(); + } + if ui.button("Copy as JSON").clicked() { + ui.ctx().copy_text(self.to_json()); + ui.close(); + } + if ui.button("Save TSV…").clicked() { + if let Some(path) = rfd::FileDialog::new() + .set_file_name("result.tsv") + .save_file() + { + let _ = std::fs::write(path, self.to_tsv()); + } + ui.close(); + } + if ui.button("Save JSON…").clicked() { + if let Some(path) = rfd::FileDialog::new() + .set_file_name("result.json") + .save_file() + { + let _ = std::fs::write(path, self.to_json()); + } + ui.close(); + } + }); + ui.menu_button("Columns", |ui| { + for (index, column) in self.page.columns.iter().enumerate() { + let mut shown = !self.hidden.contains(&index); + if ui.checkbox(&mut shown, &column.name).changed() { + if shown { + self.hidden.remove(&index); + } else { + self.hidden.insert(index); + } + } + } + if !self.hidden.is_empty() { + ui.separator(); + if ui.button("Show all").clicked() { + self.hidden.clear(); + } + } + }); + ui.selectable_value(&mut self.json_view, true, "JSON"); + ui.selectable_value(&mut self.json_view, false, "Grid"); + let response = ui.add( + egui::TextEdit::singleline(&mut self.filter) + .hint_text("Filter rows") + .desired_width(160.0), + ); + if response.changed() { + self.order_dirty = true; + } + }); + }); + } + + fn json_ui(&mut self, ui: &mut Ui) { + egui::ScrollArea::both() + .id_salt("json-view") + .auto_shrink([false, false]) + .show(ui, |ui| { + let text = self.to_json(); + let mut layouter = |ui: &Ui, text: &dyn egui::TextBuffer, wrap: f32| { + let job = crate::highlight::layout( + ui, + text.as_str(), + crate::highlight::Dialect::MongoJson, + wrap, + ); + ui.fonts_mut(|f| f.layout_job(job)) + }; + let mut shown = text; + ui.add( + egui::TextEdit::multiline(&mut shown) + .code_editor() + .interactive(false) + .desired_width(f32::INFINITY) + .layouter(&mut layouter), + ); + }); + } + + fn table_ui(&mut self, ui: &mut Ui) { + let columns: Vec = (0..self.page.columns.len()) + .filter(|c| !self.hidden.contains(c)) + .collect(); + if columns.is_empty() { + ui.label(RichText::new("No columns to show.").weak()); + return; + } + let order = self.order.clone(); + let mut sort_click: Option = None; + let mut edit: Option = None; + let mut toggle_delete: Option = None; + let mut select_row: Option = None; + let editing = self.editing.clone(); + let editable = self.editable; + let text_height = ROW_HEIGHT; + + let mut builder = TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::exact(44.0)) + .min_scrolled_height(0.0); + for _ in &columns { + builder = builder.column(Column::initial(140.0).at_least(48.0).clip(true)); + } + builder + .header(ROW_HEIGHT + 2.0, |mut header| { + header.col(|ui| { + ui.label(RichText::new("#").small().weak()); + }); + for &c in &columns { + header.col(|ui| { + let column = &self.page.columns[c]; + let arrow = match self.sort { + Some((s, SortDirection::Ascending)) if s == c => " ▲", + Some((s, SortDirection::Descending)) if s == c => " ▼", + _ => "", + }; + let label = RichText::new(format!("{}{arrow}", column.name)).strong(); + let response = ui + .add( + egui::Label::new(label) + .sense(egui::Sense::click()) + .truncate(), + ) + .on_hover_text(format!("{} · click to sort", column.data_type)); + if response.clicked() { + sort_click = Some(c); + } + }); + } + }) + .body(|body| { + body.rows(text_height, order.len(), |mut row| { + let index = row.index(); + let page_row = order[index]; + let deleted = self.deleted.contains(&page_row); + row.set_selected(self.selected_row == Some(page_row)); + row.col(|ui| { + let text = RichText::new((page_row + 1).to_string()).small().weak(); + let text = if deleted { + text.strikethrough().color(theme::DANGER) + } else { + text + }; + let response = ui.add(egui::Label::new(text).sense(egui::Sense::click())); + if response.clicked() { + select_row = Some(page_row); + } + response.context_menu(|ui| { + if ui.button("Copy row as JSON").clicked() { + ui.ctx().copy_text( + serde_json::to_string_pretty(&self.row_json(page_row)) + .unwrap_or_default(), + ); + ui.close(); + } + if editable { + let label = if deleted { "Undo delete" } else { "Delete row" }; + if ui.button(label).clicked() { + toggle_delete = Some(page_row); + ui.close(); + } + } + }); + }); + for &c in &columns { + row.col(|ui| { + if let Some((er, ec, text)) = &editing + && *er == page_row + && *ec == c + { + let mut draft = text.clone(); + let response = ui.add( + egui::TextEdit::singleline(&mut draft) + .desired_width(f32::INFINITY) + .font(egui::TextStyle::Monospace), + ); + if !response.has_focus() && !response.lost_focus() { + response.request_focus(); + } + if draft != *text { + edit = Some(EditAction::Draft(draft)); + } + let enter = ui.input(|i| i.key_pressed(egui::Key::Enter)); + let escape = ui.input(|i| i.key_pressed(egui::Key::Escape)); + if escape { + edit = Some(EditAction::Cancel); + } else if enter || response.lost_focus() { + edit = Some(EditAction::Commit); + } + return; + } + let value = self.cell(page_row, c); + let staged = self.staged.contains_key(&(page_row, c)); + let shown = value.map_or_else(String::new, format_cell); + let mut text = RichText::new(shown).monospace(); + if deleted { + text = text.strikethrough().color(theme::DANGER); + } else if staged { + text = text.color(theme::CAUTION); + } else if matches!(value, Some(DataValue::Null) | None) { + text = text.weak().italics(); + } + let response = ui.add( + egui::Label::new(text) + .sense(egui::Sense::click()) + .truncate(), + ); + if response.clicked() { + select_row = Some(page_row); + } + if response.double_clicked() && editable && !deleted { + edit = Some(EditAction::Start(page_row, c)); + } + if let Some(value) = value { + let full = format_value(value); + if full.chars().count() > MAX_CELL_CHARS || full.contains('\n') { + response + .clone() + .on_hover_text(full.chars().take(2000).collect::()); + } + } + response.context_menu(|ui| { + if ui.button("Copy value").clicked() { + ui.ctx() + .copy_text(value.map(format_value).unwrap_or_default()); + ui.close(); + } + if editable && !deleted && ui.button("Edit cell").clicked() { + edit = Some(EditAction::Start(page_row, c)); + ui.close(); + } + if editable && !deleted && ui.button("Set NULL").clicked() { + edit = Some(EditAction::SetNull(page_row, c)); + ui.close(); + } + if staged && ui.button("Revert cell").clicked() { + edit = Some(EditAction::Revert(page_row, c)); + ui.close(); + } + }); + }); + } + }); + }); + + if let Some(row) = select_row { + self.selected_row = Some(row); + } + if let Some(c) = sort_click { + self.sort = match self.sort { + Some((s, SortDirection::Ascending)) if s == c => { + Some((c, SortDirection::Descending)) + } + Some((s, SortDirection::Descending)) if s == c => None, + _ => Some((c, SortDirection::Ascending)), + }; + self.order_dirty = true; + } + match edit { + Some(EditAction::Start(row, column)) => { + let current = self.cell(row, column).map(format_value).unwrap_or_default(); + self.editing = Some((row, column, current)); + } + Some(EditAction::Draft(text)) => { + if let Some(state) = &mut self.editing { + state.2 = text; + } + } + Some(EditAction::Commit) => { + if let Some((row, column, text)) = self.editing.take() { + self.stage(row, column, &text); + } + } + Some(EditAction::Cancel) => self.editing = None, + Some(EditAction::SetNull(row, column)) => self.stage(row, column, "NULL"), + Some(EditAction::Revert(row, column)) => { + self.staged.remove(&(row, column)); + } + None => {} + } + if let Some(row) = toggle_delete + && !self.deleted.remove(&row) + { + self.deleted.insert(row); + } + } + + /// Stages `text` as the new value of a cell, parsed against the fetched value's type. The text + /// `NULL` (any case) stages a NULL; a text equal to the fetched value un-stages the cell. + pub fn stage(&mut self, row: usize, column: usize, text: &str) { + let Some(original) = self.page.rows.get(row).and_then(|r| r.get(column)) else { + return; + }; + let parsed = parse_value(text, original); + if parsed == *original { + self.staged.remove(&(row, column)); + } else { + self.staged.insert((row, column), parsed); + } + } +} + +/// A value as text, in full. +pub fn format_value(value: &DataValue) -> String { + match value { + DataValue::Null => "NULL".to_owned(), + DataValue::Boolean(b) => b.to_string(), + DataValue::Integer(s) | DataValue::Decimal(s) | DataValue::Text(s) => s.clone(), + DataValue::Float(f) => { + if f.fract() == 0.0 && f.abs() < 1e15 { + format!("{f:.1}") + } else { + f.to_string() + } + } + DataValue::Binary(b) => format!("base64:{b}"), + DataValue::Date(s) | DataValue::Time(s) | DataValue::DateTime(s) => s.clone(), + DataValue::Uuid(u) => u.to_string(), + DataValue::Json(v) | DataValue::Bson(v) => match v { + serde_json::Value::String(s) => s.clone(), + other => serde_json::to_string(other).unwrap_or_default(), + }, + } +} + +/// A value as it fits a grid cell: one line, bounded. +fn format_cell(value: &DataValue) -> String { + let text = format_value(value); + let one_line = text.replace(['\n', '\r'], "⏎"); + if one_line.chars().count() > MAX_CELL_CHARS { + let mut cut: String = one_line.chars().take(MAX_CELL_CHARS).collect(); + cut.push('…'); + cut + } else { + one_line + } +} + +/// `text` parsed into the variant of `original`, so an edited integer stays an integer and an +/// edited JSON column stays JSON. Anything that does not parse is kept as text, which the database +/// then accepts or refuses with its own error. +pub fn parse_value(text: &str, original: &DataValue) -> DataValue { + if text.eq_ignore_ascii_case("null") { + return DataValue::Null; + } + let trimmed = text.trim(); + match original { + DataValue::Boolean(_) => match trimmed.to_ascii_lowercase().as_str() { + "true" | "t" | "1" | "yes" => DataValue::Boolean(true), + "false" | "f" | "0" | "no" => DataValue::Boolean(false), + _ => DataValue::Text(text.to_owned()), + }, + DataValue::Integer(_) if trimmed.parse::().is_ok() => { + DataValue::Integer(trimmed.to_owned()) + } + DataValue::Float(_) => trimmed + .parse::() + .map_or_else(|_| DataValue::Text(text.to_owned()), DataValue::Float), + DataValue::Decimal(_) if trimmed.parse::().is_ok() => { + DataValue::Decimal(trimmed.to_owned()) + } + DataValue::Uuid(_) => trimmed + .parse() + .map_or_else(|_| DataValue::Text(text.to_owned()), DataValue::Uuid), + DataValue::Date(_) => DataValue::Date(trimmed.to_owned()), + DataValue::Time(_) => DataValue::Time(trimmed.to_owned()), + DataValue::DateTime(_) => DataValue::DateTime(trimmed.to_owned()), + DataValue::Binary(_) => DataValue::Binary(trimmed.trim_start_matches("base64:").to_owned()), + DataValue::Json(_) => serde_json::from_str(trimmed).map_or_else( + |_| DataValue::Json(serde_json::Value::String(text.to_owned())), + DataValue::Json, + ), + DataValue::Bson(_) => serde_json::from_str(trimmed).map_or_else( + |_| DataValue::Bson(serde_json::Value::String(text.to_owned())), + DataValue::Bson, + ), + // A NULL cell has no type to parse into: numbers become numbers, the rest text. + DataValue::Null if trimmed.parse::().is_ok() => DataValue::Integer(trimmed.to_owned()), + _ => DataValue::Text(text.to_owned()), + } +} + +/// A value as JSON, for the JSON view and exports. +pub fn value_json(value: &DataValue) -> serde_json::Value { + match value { + DataValue::Null => serde_json::Value::Null, + DataValue::Boolean(b) => serde_json::Value::Bool(*b), + DataValue::Integer(s) => s.parse::().map_or_else( + |_| serde_json::Value::String(s.clone()), + serde_json::Value::from, + ), + DataValue::Float(f) => serde_json::Number::from_f64(*f) + .map_or(serde_json::Value::Null, serde_json::Value::Number), + DataValue::Json(v) | DataValue::Bson(v) => v.clone(), + other => serde_json::Value::String(format_value(other)), + } +} + +fn compare(left: Option<&DataValue>, right: Option<&DataValue>) -> std::cmp::Ordering { + use std::cmp::Ordering; + match (left, right) { + (None | Some(DataValue::Null), None | Some(DataValue::Null)) => Ordering::Equal, + (None | Some(DataValue::Null), _) => Ordering::Greater, + (_, None | Some(DataValue::Null)) => Ordering::Less, + (Some(a), Some(b)) => match (numeric(a), numeric(b)) { + (Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(Ordering::Equal), + _ => format_value(a).cmp(&format_value(b)), + }, + } +} + +fn numeric(value: &DataValue) -> Option { + match value { + DataValue::Integer(s) | DataValue::Decimal(s) => s.parse().ok(), + DataValue::Float(f) => Some(*f), + DataValue::Boolean(b) => Some(f64::from(u8::from(*b))), + _ => None, + } +} diff --git a/apps/redrob-query/src/highlight.rs b/apps/redrob-query/src/highlight.rs new file mode 100644 index 0000000..a44ac02 --- /dev/null +++ b/apps/redrob-query/src/highlight.rs @@ -0,0 +1,498 @@ +//! Syntax colouring and a small formatter for SQL and Mongo JSON (MQL). +//! +//! A hand-rolled lexer rather than syntect: the editor needs keywords, strings, numbers, comments +//! and identifiers told apart in a few hundred lines, cached per frame by egui's layouter. The +//! formatter is deliberately simple -- it puts the major clauses on their own lines and indents +//! what follows -- so it never changes the meaning of a statement it does not understand. + +use egui::text::{LayoutJob, TextFormat}; +use egui::{Color32, FontId, Ui}; +use redrob_query_core::{DatabaseKind, QueryLanguage}; + +/// The query language the editor colours with. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Dialect { + Sql, + MongoJson, +} + +impl Dialect { + pub fn for_kind(kind: DatabaseKind) -> Self { + match kind { + DatabaseKind::MongoDb => Self::MongoJson, + _ => Self::Sql, + } + } + + pub fn language(self) -> QueryLanguage { + match self { + Self::Sql => QueryLanguage::Sql, + Self::MongoJson => QueryLanguage::MongoJson, + } + } +} + +const SQL_KEYWORDS: &[&str] = &[ + "SELECT", + "FROM", + "WHERE", + "AND", + "OR", + "NOT", + "IN", + "IS", + "NULL", + "AS", + "ON", + "JOIN", + "LEFT", + "RIGHT", + "INNER", + "OUTER", + "FULL", + "CROSS", + "GROUP", + "BY", + "ORDER", + "HAVING", + "LIMIT", + "OFFSET", + "ASC", + "DESC", + "DISTINCT", + "UNION", + "ALL", + "INSERT", + "INTO", + "VALUES", + "UPDATE", + "SET", + "DELETE", + "CREATE", + "TABLE", + "INDEX", + "VIEW", + "DROP", + "ALTER", + "ADD", + "COLUMN", + "PRIMARY", + "KEY", + "FOREIGN", + "REFERENCES", + "UNIQUE", + "DEFAULT", + "CHECK", + "CONSTRAINT", + "BEGIN", + "COMMIT", + "ROLLBACK", + "TRANSACTION", + "WITH", + "RECURSIVE", + "CASE", + "WHEN", + "THEN", + "ELSE", + "END", + "EXISTS", + "BETWEEN", + "LIKE", + "ILIKE", + "CAST", + "COUNT", + "SUM", + "AVG", + "MIN", + "MAX", + "COALESCE", + "NULLIF", + "TRUE", + "FALSE", + "EXPLAIN", + "ANALYZE", + "SHOW", + "DESCRIBE", + "PRAGMA", + "RETURNING", + "USING", + "NATURAL", + "OVER", + "PARTITION", + "WINDOW", + "ROWS", + "RANGE", + "FETCH", + "FIRST", + "NEXT", + "ONLY", + "TOP", + "INTERSECT", + "EXCEPT", + "TRUNCATE", + "GRANT", + "REVOKE", + "IF", + "REPLACE", + "TEMPORARY", + "TEMP", +]; + +/// Clause-leading keywords the formatter starts a new line at (GROUP/ORDER carry their BY, +/// INSERT its INTO, UNION its ALL, and a LEFT/RIGHT/INNER/FULL/CROSS its JOIN). +const SQL_CLAUSES: &[&str] = &[ + "SELECT", + "FROM", + "WHERE", + "GROUP", + "ORDER", + "HAVING", + "LIMIT", + "OFFSET", + "UNION", + "VALUES", + "SET", + "RETURNING", + "INSERT", + "UPDATE", + "DELETE", + "ON", + "WITH", + "INTERSECT", + "EXCEPT", +]; + +const MQL_KEYWORDS: &[&str] = &[ + "collection", + "operation", + "filter", + "projection", + "sort", + "limit", + "explain", + "pipeline", + "find", + "aggregate", + "true", + "false", + "null", +]; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Token { + Keyword, + Identifier, + Number, + Str, + Comment, + Operator, + Punct, + Mongo, + Whitespace, +} + +/// The colours of each token class, read from the theme so both themes stay legible. +struct Palette { + keyword: Color32, + identifier: Color32, + number: Color32, + string: Color32, + comment: Color32, + operator: Color32, + mongo: Color32, +} + +impl Palette { + fn from_ui(ui: &Ui) -> Self { + let dark = ui.visuals().dark_mode; + let text = ui.visuals().text_color(); + if dark { + Self { + keyword: Color32::from_rgb(0x7F, 0xA6, 0xFF), + identifier: text, + number: Color32::from_rgb(0xD6, 0xB5, 0x6E), + string: Color32::from_rgb(0x8F, 0xD1, 0x9A), + comment: Color32::from_rgb(0x80, 0x86, 0x90), + operator: Color32::from_rgb(0xC9, 0xCE, 0xD6), + mongo: Color32::from_rgb(0xC9, 0x9A, 0xF0), + } + } else { + Self { + keyword: crate::theme::REDROB_BLUE, + identifier: text, + number: Color32::from_rgb(0x9A, 0x5B, 0x00), + string: Color32::from_rgb(0x1E, 0x7A, 0x3E), + comment: Color32::from_rgb(0x74, 0x7A, 0x84), + operator: Color32::from_rgb(0x40, 0x46, 0x50), + mongo: Color32::from_rgb(0x7A, 0x3E, 0xB8), + } + } + } + + fn colour(&self, token: Token) -> Color32 { + match token { + Token::Keyword => self.keyword, + Token::Identifier | Token::Whitespace | Token::Punct => self.identifier, + Token::Number => self.number, + Token::Str => self.string, + Token::Comment => self.comment, + Token::Operator => self.operator, + Token::Mongo => self.mongo, + } + } +} + +/// Splits `text` into `(token, byte range)` pieces. Every byte of the input is covered. +fn lex(text: &str, dialect: Dialect) -> Vec<(Token, std::ops::Range)> { + let bytes = text.as_bytes(); + let mut out = Vec::new(); + let mut i = 0; + while i < bytes.len() { + let start = i; + let c = bytes[i]; + let token = if c.is_ascii_whitespace() { + while i < bytes.len() && bytes[i].is_ascii_whitespace() { + i += 1; + } + Token::Whitespace + } else if dialect == Dialect::Sql && c == b'-' && bytes.get(i + 1) == Some(&b'-') { + while i < bytes.len() && bytes[i] != b'\n' { + i += 1; + } + Token::Comment + } else if c == b'/' && bytes.get(i + 1) == Some(&b'/') { + while i < bytes.len() && bytes[i] != b'\n' { + i += 1; + } + Token::Comment + } else if c == b'/' && bytes.get(i + 1) == Some(&b'*') { + i += 2; + while i < bytes.len() && !(bytes[i] == b'*' && bytes.get(i + 1) == Some(&b'/')) { + i += 1; + } + i = (i + 2).min(bytes.len()); + Token::Comment + } else if c == b'\'' || c == b'"' || c == b'`' { + i += 1; + while i < bytes.len() { + if bytes[i] == b'\\' && dialect == Dialect::MongoJson { + i += 2; + continue; + } + if bytes[i] == c { + // SQL doubles a quote to escape it. + if dialect == Dialect::Sql && bytes.get(i + 1) == Some(&c) { + i += 2; + continue; + } + i += 1; + break; + } + i += 1; + } + i = i.min(bytes.len()); + if c == b'\'' || dialect == Dialect::MongoJson { + Token::Str + } else { + Token::Identifier + } + } else if c.is_ascii_digit() + || (c == b'.' && bytes.get(i + 1).is_some_and(u8::is_ascii_digit)) + { + while i < bytes.len() + && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'.' || bytes[i] == b'_') + { + i += 1; + } + Token::Number + } else if c.is_ascii_alphabetic() || c == b'_' || c == b'$' || c >= 0x80 { + while i < bytes.len() + && (bytes[i].is_ascii_alphanumeric() + || bytes[i] == b'_' + || bytes[i] == b'$' + || bytes[i] >= 0x80) + { + i += 1; + } + let word = &text[start..i]; + match dialect { + Dialect::Sql if SQL_KEYWORDS.iter().any(|k| k.eq_ignore_ascii_case(word)) => { + Token::Keyword + } + Dialect::MongoJson if word.starts_with('$') => Token::Mongo, + Dialect::MongoJson if MQL_KEYWORDS.contains(&word) => Token::Keyword, + _ => Token::Identifier, + } + } else if b"=<>!+-*/%|&^~".contains(&c) { + i += 1; + Token::Operator + } else { + i += 1; + Token::Punct + }; + out.push((token, start..i)); + } + out +} + +/// A coloured layout of `text` for the editor's layouter; `wrap_width` is the editor's width. +pub fn layout(ui: &Ui, text: &str, dialect: Dialect, wrap_width: f32) -> LayoutJob { + let palette = Palette::from_ui(ui); + let font = FontId::monospace(13.0); + let mut job = LayoutJob::default(); + job.wrap.max_width = wrap_width; + for (token, range) in lex(text, dialect) { + let mut format = TextFormat::simple(font.clone(), palette.colour(token)); + if token == Token::Comment { + format.italics = true; + } + job.append(&text[range], 0.0, format); + } + job +} + +/// Reformats `text`: SQL gets its major clauses on their own lines, keywords upper-cased outside +/// strings and comments; Mongo JSON is pretty-printed when it parses, left alone otherwise. +pub fn format(text: &str, dialect: Dialect) -> String { + match dialect { + Dialect::MongoJson => serde_json::from_str::(text) + .ok() + .and_then(|value| serde_json::to_string_pretty(&value).ok()) + .unwrap_or_else(|| text.to_owned()), + Dialect::Sql => format_sql(text), + } +} + +fn format_sql(text: &str) -> String { + // Keywords upper-cased; whitespace outside strings and comments collapsed; each major clause + // at paren depth 0 starts a new line. Strings, identifiers and comments are left untouched. + let mut out = String::new(); + let mut depth: i32 = 0; + let mut pending_space = false; + let tokens: Vec<(Token, &str)> = lex(text, Dialect::Sql) + .into_iter() + .filter(|(token, _)| *token != Token::Whitespace) + .map(|(token, range)| (token, &text[range])) + .collect(); + for (index, (token, piece)) in tokens.iter().enumerate() { + let upper = piece.to_ascii_uppercase(); + let word = if *token == Token::Keyword { + upper.as_str() + } else { + piece + }; + let previous = index + .checked_sub(1) + .map(|i| tokens[i].1.to_ascii_uppercase()); + let starts_clause = *token == Token::Keyword + && depth == 0 + && index > 0 + && (SQL_CLAUSES.contains(&word) + || (word == "JOIN" + && !previous.as_deref().is_some_and(|p| { + matches!( + p, + "LEFT" | "RIGHT" | "INNER" | "FULL" | "CROSS" | "OUTER" | "NATURAL" + ) + })) + || (matches!(word, "LEFT" | "RIGHT" | "INNER" | "FULL" | "CROSS") + && tokens.get(index + 1).is_some_and(|(_, n)| { + let n = n.to_ascii_uppercase(); + n == "JOIN" || n == "OUTER" + }))); + let is_second_word = matches!(word, "BY" | "INTO" | "ALL" | "OUTER" | "JOIN") + && previous.as_deref().is_some_and(|p| { + matches!( + p, + "GROUP" + | "ORDER" + | "INSERT" + | "UNION" + | "LEFT" + | "RIGHT" + | "INNER" + | "FULL" + | "CROSS" + | "OUTER" + ) + }); + if starts_clause && !is_second_word && !out.is_empty() { + out.push('\n'); + pending_space = false; + } else if *token == Token::Comment { + if !out.is_empty() && !out.ends_with('\n') { + out.push('\n'); + } + pending_space = false; + } + let tight_before = matches!(*piece, "," | ")" | ";" | ".") + || (*piece == "(" + && previous.as_deref().is_some_and(|p| { + // A call: `count(` not `IN (`. + p.chars().all(|c| c.is_alphanumeric() || c == '_') && !SQL_KEYWORDS.contains(&p) + })); + let tight_after = out.ends_with('(') || out.ends_with('.'); + if pending_space && !tight_before && !tight_after { + out.push(' '); + } + out.push_str(word); + if *token == Token::Comment { + out.push('\n'); + pending_space = false; + } else { + pending_space = true; + } + match *piece { + "(" => depth += 1, + ")" => depth = (depth - 1).max(0), + _ => {} + } + } + out.trim_end().to_owned() +} + +/// The query a new tab opens with for a connection kind, so the first run shows something real. +pub fn starter_query(kind: DatabaseKind) -> &'static str { + match kind { + DatabaseKind::PostgreSql => { + "SELECT table_schema, table_name\nFROM information_schema.tables\nWHERE table_schema NOT IN ('pg_catalog', 'information_schema')\nORDER BY table_schema, table_name\nLIMIT 100;" + } + DatabaseKind::MySql => { + "SELECT table_name, table_rows\nFROM information_schema.tables\nWHERE table_schema = DATABASE()\nORDER BY table_name\nLIMIT 100;" + } + DatabaseKind::SQLite => { + "SELECT name, type\nFROM sqlite_master\nWHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'\nORDER BY name;" + } + DatabaseKind::MongoDb => { + "{\n \"collection\": \"\",\n \"operation\": \"find\",\n \"filter\": {},\n \"limit\": 50\n}" + } + DatabaseKind::SqlServer => "-- SQL Server is not supported in this release.", + } +} + +/// The statement under the cursor: the text between the semicolons (or blank lines) around +/// `cursor_char`, or the whole text when there is no separator. Mongo JSON is always the whole text. +pub fn statement_at(text: &str, cursor_char: usize, dialect: Dialect) -> String { + if dialect == Dialect::MongoJson { + return text.trim().to_owned(); + } + let chars: Vec = text.chars().collect(); + let cursor = cursor_char.min(chars.len()); + // Walk back to the previous ';' (outside strings would be nicer; a cursor-local heuristic). + let mut start = cursor; + while start > 0 && chars[start - 1] != ';' { + start -= 1; + } + let mut end = cursor; + while end < chars.len() && chars[end] != ';' { + end += 1; + } + let statement: String = chars[start..end].iter().collect(); + let statement = statement.trim(); + if statement.is_empty() { + // The cursor sits right after a ';': take the statement before it. + let before: String = chars[..start.saturating_sub(1)].iter().collect(); + return before.rsplit(';').next().unwrap_or("").trim().to_owned(); + } + statement.to_owned() +} diff --git a/apps/redrob-query/src/main.rs b/apps/redrob-query/src/main.rs new file mode 100644 index 0000000..4fa10d9 --- /dev/null +++ b/apps/redrob-query/src/main.rs @@ -0,0 +1,96 @@ +//! Redrob Query desktop app: an agentic database client in one egui window. +//! +//! Usage: `redrob-query [--version]` +#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] +#![deny(clippy::unwrap_used, clippy::expect_used)] +// Stylistic pedantic lints the shell does not follow: egui code reads better with a few bool flags +// per panel, `format!` pushed onto a status string, and `if`/`match` shapes mirroring the widgets. +#![allow( + clippy::format_push_string, + clippy::struct_excessive_bools, + clippy::match_same_arms, + clippy::single_match_else, + clippy::semicolon_if_nothing_returned, + clippy::if_same_then_else +)] + +mod account; +mod ai_dock; +mod app; +mod changes; +mod editor; +mod grid; +mod highlight; +mod mongo; +mod profiles; +mod schema; +mod services; +mod structure; +mod theme; +mod workspace; + +use app::QueryApp; + +/// The 256px app icon from the brand pipeline (resources/icons, pinned in brand/SOURCES.json). +const ICON_PNG: &[u8] = + include_bytes!("../../../resources/icons/hicolor/256x256/apps/redrob-query.png"); + +/// The main window: 1440 × 900 (eframe shrinks it to the monitor), at least 900 × 560. +fn native_options() -> eframe::NativeOptions { + let mut viewport = egui::ViewportBuilder::default() + .with_app_id(services::APP_ID) + .with_title(services::APP_NAME) + .with_inner_size([1440.0, 900.0]) + .with_min_inner_size([900.0, 560.0]); + if let Ok(icon) = eframe::icon_data::from_png_bytes(ICON_PNG) { + viewport = viewport.with_icon(icon); + } + eframe::NativeOptions { + viewport, + centered: true, + // eframe saves the window geometry and egui panel sizes beside the preferences. + persistence_path: services::persistence_path(), + ..Default::default() + } +} + +fn main() -> eframe::Result { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); + for arg in std::env::args().skip(1) { + match arg.as_str() { + "--version" => { + println!("redrob-query {}", env!("CARGO_PKG_VERSION")); + return Ok(()); + } + other => eprintln!("redrob-query: ignoring argument {other:?}"), + } + } + let mut options = native_options(); + // Nothing draws or dispatches indirectly, so wgpu's indirect-call validation only costs a + // compute shader at start-up that some drivers cannot compile. + if let eframe::egui_wgpu::WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup { + create.instance_descriptor.flags = create + .instance_descriptor + .flags + .difference(eframe::wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL) + .with_env(); + } + eframe::run_native( + services::APP_NAME, + options, + Box::new(|cc| { + if let Some(rs) = &cc.wgpu_render_state { + let info = rs.adapter.get_info(); + log::info!( + "graphics: {} on {:?} ({:?}; driver {} {})", + info.name, + info.backend, + info.device_type, + info.driver, + info.driver_info + ); + } + Ok(Box::new(QueryApp::new(&cc.egui_ctx))) + }), + ) +} diff --git a/apps/redrob-query/src/mongo.rs b/apps/redrob-query/src/mongo.rs new file mode 100644 index 0000000..5d399c2 --- /dev/null +++ b/apps/redrob-query/src/mongo.rs @@ -0,0 +1,97 @@ +//! Mongo tabs: a strip above the editor that writes a `find` or `aggregate` request body for a +//! collection, with `explain`, so the JSON the engine accepts does not have to be typed from +//! memory. The engine (`connection.rs`) accepts exactly these keys: `collection`, `operation` +//! (`find` | `aggregate`), `filter`, `projection`, `sort`, `limit`, `explain` (a verbosity string), +//! `pipeline`. + +use egui::{RichText, Ui}; +use redrob_query_core::MetadataNode; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MongoOperation { + Find, + Aggregate, +} + +#[derive(Clone, Debug)] +pub struct MongoStrip { + pub collection: String, + pub operation: MongoOperation, + pub explain: bool, +} + +impl Default for MongoStrip { + fn default() -> Self { + Self { + collection: String::new(), + operation: MongoOperation::Find, + explain: false, + } + } +} + +impl MongoStrip { + /// Draws the strip; returns a request body to put in the editor when a template was asked for. + pub fn ui(&mut self, ui: &mut Ui, collections: &[MetadataNode]) -> Option { + let mut template = None; + ui.horizontal(|ui| { + ui.label(RichText::new("Collection").small()); + egui::ComboBox::from_id_salt("mongo-collection") + .selected_text(if self.collection.is_empty() { + "choose…" + } else { + self.collection.as_str() + }) + .width(180.0) + .show_ui(ui, |ui| { + for node in collections { + ui.selectable_value(&mut self.collection, node.name.clone(), &node.name); + } + }); + ui.selectable_value(&mut self.operation, MongoOperation::Find, "find"); + ui.selectable_value(&mut self.operation, MongoOperation::Aggregate, "aggregate"); + ui.checkbox(&mut self.explain, "explain"); + if ui + .button("Insert template") + .on_hover_text("Replace the editor text with a request for this collection") + .clicked() + { + template = Some(self.template()); + } + }); + template + } + + pub fn template(&self) -> String { + let collection = serde_json::Value::String(self.collection.clone()); + // `explain` is a verbosity, as the engine reads it (query_planner | execution_stats | all_plans_execution). + let explain = if self.explain { + ",\n \"explain\": \"execution_stats\"" + } else { + "" + }; + match self.operation { + MongoOperation::Find => format!( + "{{\n \"collection\": {collection},\n \"operation\": \"find\",\n \"filter\": {{}},\n \"sort\": {{ \"_id\": 1 }},\n \"limit\": 50{explain}\n}}" + ), + MongoOperation::Aggregate => format!( + "{{\n \"collection\": {collection},\n \"operation\": \"aggregate\",\n \"pipeline\": [\n {{ \"$match\": {{}} }},\n {{ \"$limit\": 50 }}\n ]{explain}\n}}" + ), + } + } + + /// Picks up the collection named in the editor text, so the strip follows a typed query. + pub fn sync_from(&mut self, text: &str) { + if let Ok(serde_json::Value::Object(body)) = serde_json::from_str::(text) + { + if let Some(serde_json::Value::String(name)) = body.get("collection") { + self.collection.clone_from(name); + } + self.operation = match body.get("operation").and_then(|v| v.as_str()) { + Some("aggregate") => MongoOperation::Aggregate, + _ => MongoOperation::Find, + }; + self.explain = body.get("explain").is_some_and(|v| !v.is_null()); + } + } +} diff --git a/apps/redrob-query/src/profiles.rs b/apps/redrob-query/src/profiles.rs new file mode 100644 index 0000000..53de218 --- /dev/null +++ b/apps/redrob-query/src/profiles.rs @@ -0,0 +1,750 @@ +//! The Profiles tree and the connection form. +//! +//! Built-in profiles (the Demo `SQLite` database) are immutable; user profiles can be added, edited, +//! tested and removed. Passwords go to the OS keyring through the engine and are never kept here +//! beyond the form's own text field, which is cleared when the form closes. + +use std::collections::HashMap; + +use egui::{RichText, Ui}; +use redrob_query_core::{ + ConnectionConfig, ConnectionProfile, ConnectionState, ConnectionStatus, DatabaseKind, Pending, + RemoveProfileOutcome, SaveProfileOutcome, Workspace, +}; +use uuid::Uuid; + +use crate::theme; + +/// What the panel asks the app to do. +#[derive(Debug)] +pub enum ProfilesEvent { + /// A profile connected (or was already connected): open or focus a query tab on it. + OpenQuery(ConnectionProfile), + /// The schema tree for this profile should be (re)loaded. + Connected(Uuid), + Disconnected(Uuid), + /// Something worth the status line. + Notice(String, bool), +} + +pub struct ProfilesPanel { + pub profiles: Vec, + pub status: HashMap, + pub selected: Option, + list: Option>>, + connects: HashMap>, + disconnects: HashMap>, + removes: HashMap>, + pub form: Option, + confirm_remove: Option, + pub load_warnings: Vec, +} + +impl ProfilesPanel { + pub fn new(workspace: &Workspace) -> Self { + Self { + profiles: Vec::new(), + status: HashMap::new(), + selected: None, + list: Some(workspace.list_profiles()), + connects: HashMap::new(), + disconnects: HashMap::new(), + removes: HashMap::new(), + form: None, + confirm_remove: None, + load_warnings: workspace.profile_load_warnings(), + } + } + + pub fn refresh(&mut self, workspace: &Workspace) { + self.list = Some(workspace.list_profiles()); + } + + pub fn profile(&self, id: Uuid) -> Option<&ConnectionProfile> { + self.profiles.iter().find(|p| p.id == id) + } + + pub fn is_connected(&self, id: Uuid) -> bool { + self.status + .get(&id) + .is_some_and(|s| s.state == ConnectionState::Connected) + } + + pub fn connect(&mut self, workspace: &Workspace, id: Uuid) { + self.connects + .entry(id) + .or_insert_with(|| workspace.connect(id)); + } + + /// Drains finished operations; call once per frame before drawing. + pub fn poll(&mut self, workspace: &Workspace, events: &mut Vec) { + if let Some(list) = &mut self.list + && let Some(result) = list.poll() + { + self.list = None; + match result { + Ok(mut profiles) => { + profiles.sort_by(|a, b| { + b.built_in + .cmp(&a.built_in) + .then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase())) + }); + self.profiles = profiles; + } + Err(error) => events.push(ProfilesEvent::Notice(error.to_string(), true)), + } + } + let mut done = Vec::new(); + for (id, pending) in &mut self.connects { + if let Some(result) = pending.poll() { + done.push((*id, result)); + } + } + for (id, result) in done { + self.connects.remove(&id); + match result { + Ok(status) => { + let connected = status.state == ConnectionState::Connected; + let message = status.message.clone(); + self.status.insert(id, status); + if connected { + events.push(ProfilesEvent::Connected(id)); + if let Some(profile) = self.profile(id).cloned() { + events.push(ProfilesEvent::OpenQuery(profile)); + } + } else { + events.push(ProfilesEvent::Notice(message, true)); + } + } + Err(error) => events.push(ProfilesEvent::Notice(error.to_string(), true)), + } + } + let mut done = Vec::new(); + for (id, pending) in &mut self.disconnects { + if let Some(result) = pending.poll() { + done.push((*id, result)); + } + } + for (id, result) in done { + self.disconnects.remove(&id); + self.status.remove(&id); + events.push(ProfilesEvent::Disconnected(id)); + if let Err(error) = result { + events.push(ProfilesEvent::Notice(error.to_string(), true)); + } + } + let mut done = Vec::new(); + for (id, pending) in &mut self.removes { + if let Some(result) = pending.poll() { + done.push((*id, result)); + } + } + for (id, result) in done { + self.removes.remove(&id); + match result { + Ok(outcome) => { + self.status.remove(&id); + if self.selected == Some(id) { + self.selected = None; + } + events.push(ProfilesEvent::Disconnected(id)); + if let Some(warning) = outcome.warning { + events.push(ProfilesEvent::Notice(warning, false)); + } + self.refresh(workspace); + } + Err(error) => events.push(ProfilesEvent::Notice(error.to_string(), true)), + } + } + if let Some(form) = &mut self.form + && let Some(saved) = form.poll(events) + { + self.form = None; + self.selected = Some(saved.id); + self.refresh(workspace); + } + } + + pub fn ui(&mut self, ui: &mut Ui, workspace: &Workspace, events: &mut Vec) { + ui.horizontal(|ui| { + ui.heading("Profiles"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui + .button("+ New") + .on_hover_text("Add a connection profile") + .clicked() + { + self.form = Some(ProfileForm::new_profile()); + } + if ui + .small_button("⟳") + .on_hover_text("Reload profiles") + .clicked() + { + self.refresh(workspace); + } + }); + }); + for warning in &self.load_warnings { + ui.label(RichText::new(warning).small().color(theme::CAUTION)); + } + ui.separator(); + + let mut action: Option<(Uuid, RowAction)> = None; + egui::ScrollArea::vertical() + .id_salt("profiles") + .auto_shrink([false, true]) + .max_height(ui.available_height() * 0.45) + .show(ui, |ui| { + let (built_in, user): (Vec<_>, Vec<_>) = + self.profiles.iter().partition(|p| p.built_in); + if !built_in.is_empty() { + ui.label(RichText::new("Built-in").small().weak()); + for profile in built_in { + if let Some(a) = self.profile_row(ui, profile) { + action = Some(a); + } + } + } + ui.label(RichText::new("Your connections").small().weak()); + if user.is_empty() { + ui.label(RichText::new("None yet. Press New to add one.").weak()); + } + for profile in user { + if let Some(a) = self.profile_row(ui, profile) { + action = Some(a); + } + } + }); + + if let Some((id, action)) = action { + match action { + RowAction::Select => self.selected = Some(id), + RowAction::Connect => { + self.selected = Some(id); + if self.is_connected(id) { + if let Some(profile) = self.profile(id).cloned() { + events.push(ProfilesEvent::OpenQuery(profile)); + } + } else { + self.connect(workspace, id); + } + } + RowAction::Disconnect => { + self.disconnects + .entry(id) + .or_insert_with(|| workspace.disconnect(id)); + } + RowAction::Edit => { + if let Some(profile) = self.profile(id).cloned() { + self.form = Some(ProfileForm::edit(profile)); + } + } + RowAction::Remove => self.confirm_remove = Some(id), + } + } + + if let Some(id) = self.confirm_remove { + let name = self.profile(id).map(|p| p.name.clone()).unwrap_or_default(); + let mut open = true; + let mut decided = None; + egui::Window::new("Remove profile") + .collapsible(false) + .resizable(false) + .open(&mut open) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .show(ui.ctx(), |ui| { + ui.label(format!( + "Remove \"{name}\" and its saved password? The database itself is not touched." + )); + ui.horizontal(|ui| { + if ui + .add(egui::Button::new(RichText::new("Remove").color(egui::Color32::WHITE)).fill(theme::DANGER)) + .clicked() + { + decided = Some(true); + } + if ui.button("Cancel").clicked() { + decided = Some(false); + } + }); + }); + match decided { + Some(true) => { + self.confirm_remove = None; + self.removes.insert(id, workspace.remove_profile(id)); + } + Some(false) => self.confirm_remove = None, + None if !open => self.confirm_remove = None, + None => {} + } + } + + if let Some(form) = &mut self.form { + let mut keep = true; + form.ui(ui.ctx(), workspace, &mut keep); + if !keep { + self.form = None; + } + } + } + + fn profile_row(&self, ui: &mut Ui, profile: &ConnectionProfile) -> Option<(Uuid, RowAction)> { + let mut action = None; + let connected = self.is_connected(profile.id); + let connecting = self.connects.contains_key(&profile.id); + let selected = self.selected == Some(profile.id); + ui.horizontal(|ui| { + let dot = if connected { + theme::SUCCESS + } else if !profile.capabilities.connect { + theme::DANGER + } else { + ui.visuals().weak_text_color() + }; + theme::status_dot(ui, dot); + let label = format!("{} {}", kind_glyph(profile.kind), profile.name); + let response = ui.selectable_label(selected, label); + if response.clicked() { + action = Some((profile.id, RowAction::Select)); + } + if response.double_clicked() { + action = Some((profile.id, RowAction::Connect)); + } + response.context_menu(|ui| { + if connected { + if ui.button("New query tab").clicked() { + action = Some((profile.id, RowAction::Connect)); + ui.close(); + } + if ui.button("Disconnect").clicked() { + action = Some((profile.id, RowAction::Disconnect)); + ui.close(); + } + } else if ui.button("Connect").clicked() { + action = Some((profile.id, RowAction::Connect)); + ui.close(); + } + if !profile.built_in { + ui.separator(); + if ui.button("Edit…").clicked() { + action = Some((profile.id, RowAction::Edit)); + ui.close(); + } + if ui.button("Remove…").clicked() { + action = Some((profile.id, RowAction::Remove)); + ui.close(); + } + } + }); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if connecting { + ui.spinner(); + } else if connected { + if ui.small_button("Query").clicked() { + action = Some((profile.id, RowAction::Connect)); + } + } else if profile.capabilities.connect && ui.small_button("Connect").clicked() { + action = Some((profile.id, RowAction::Connect)); + } + }); + }); + if selected { + ui.indent("detail", |ui| { + let text = describe(profile); + ui.label(RichText::new(text).small().weak()); + if let Some(status) = self.status.get(&profile.id) { + let mut line = status.message.clone(); + if let Some(ms) = status.latency_ms { + line.push_str(&format!(" ({ms} ms)")); + } + ui.label(RichText::new(line).small()); + } + }); + } + action + } +} + +enum RowAction { + Select, + Connect, + Disconnect, + Edit, + Remove, +} + +pub fn kind_glyph(kind: DatabaseKind) -> &'static str { + match kind { + DatabaseKind::PostgreSql => "PG", + DatabaseKind::MySql => "My", + DatabaseKind::SQLite => "Lite", + DatabaseKind::MongoDb => "Mongo", + DatabaseKind::SqlServer => "MSSQL", + } +} + +pub fn kind_name(kind: DatabaseKind) -> &'static str { + match kind { + DatabaseKind::PostgreSql => "PostgreSQL", + DatabaseKind::MySql => "MySQL", + DatabaseKind::SQLite => "SQLite", + DatabaseKind::MongoDb => "MongoDB", + DatabaseKind::SqlServer => "SQL Server", + } +} + +fn describe(profile: &ConnectionProfile) -> String { + let c = &profile.config; + let mut text = kind_name(profile.kind).to_owned(); + match profile.kind { + DatabaseKind::SQLite => { + text.push_str(" · "); + text.push_str(c.file_path.as_deref().unwrap_or(":memory:")); + } + _ => { + if let Some(host) = &c.host { + text.push_str(" · "); + text.push_str(host); + if let Some(port) = c.port.or(profile.kind.default_port()) { + text.push_str(&format!(":{port}")); + } + } + if let Some(db) = c.database.as_deref().filter(|d| !d.is_empty()) { + text.push_str(&format!(" / {db}")); + } + } + } + if profile.read_only { + text.push_str(" · read-only"); + } + text +} + +/// The connection form: a window over the app with the fields of one profile. +pub struct ProfileForm { + pub profile: ConnectionProfile, + pub password: String, + pub editing: bool, + port_text: String, + auth_source: String, + test: Option>, + test_result: Option<(String, bool)>, + save: Option>, + error: Option, +} + +impl ProfileForm { + pub fn new_profile() -> Self { + let kind = DatabaseKind::PostgreSql; + let profile = ConnectionProfile { + id: Uuid::new_v4(), + name: String::new(), + kind, + config: ConnectionConfig { + host: Some("localhost".to_owned()), + port: kind.default_port(), + ..ConnectionConfig::default() + }, + read_only: true, + built_in: false, + capabilities: kind.capabilities(), + }; + Self::over(profile, false) + } + + pub fn edit(profile: ConnectionProfile) -> Self { + Self::over(profile, true) + } + + fn over(profile: ConnectionProfile, editing: bool) -> Self { + let port_text = profile + .config + .port + .map(|p| p.to_string()) + .unwrap_or_default(); + let auth_source = profile + .config + .options + .get("authSource") + .cloned() + .unwrap_or_default(); + Self { + profile, + password: String::new(), + editing, + port_text, + auth_source, + test: None, + test_result: None, + save: None, + error: None, + } + } + + fn poll(&mut self, events: &mut Vec) -> Option { + if let Some(test) = &mut self.test + && let Some(result) = test.poll() + { + self.test = None; + self.test_result = Some(match result { + Ok(status) => { + let ok = status.state == ConnectionState::Connected; + let mut text = status.message; + if let Some(ms) = status.latency_ms { + text.push_str(&format!(" ({ms} ms)")); + } + (text, ok) + } + Err(error) => (error.to_string(), false), + }); + } + if let Some(save) = &mut self.save + && let Some(result) = save.poll() + { + self.save = None; + match result { + Ok(outcome) => { + if let Some(warning) = outcome.warning { + events.push(ProfilesEvent::Notice(warning, false)); + } + events.push(ProfilesEvent::Notice( + format!("Saved profile \"{}\"", outcome.profile.name), + false, + )); + self.password.clear(); + return Some(outcome.profile); + } + Err(error) => self.error = Some(error.to_string()), + } + } + None + } + + /// Reads the form fields back into the profile; the text port becomes a number or nothing. + fn commit_fields(&mut self) { + let kind = self.profile.kind; + self.profile.capabilities = kind.capabilities(); + let config = &mut self.profile.config; + config.port = self.port_text.trim().parse::().ok(); + config.options.clear(); + if kind == DatabaseKind::MongoDb && !self.auth_source.trim().is_empty() { + config + .options + .insert("authSource".to_owned(), self.auth_source.trim().to_owned()); + } + if kind != DatabaseKind::MongoDb { + config.srv = false; + } + if kind == DatabaseKind::SQLite { + config.host = None; + config.port = None; + config.username = None; + config.database = None; + config.tls = false; + } else { + config.file_path = None; + for field in [&mut config.host, &mut config.username, &mut config.database] { + if field.as_deref().is_some_and(|s| s.trim().is_empty()) { + *field = None; + } + } + } + } + + fn secret(&self) -> Option { + (!self.password.is_empty() && self.profile.kind != DatabaseKind::SQLite) + .then(|| self.password.clone()) + } + + pub fn ui(&mut self, ctx: &egui::Context, workspace: &Workspace, keep: &mut bool) { + let title = if self.editing { + "Edit connection" + } else { + "New connection" + }; + let mut open = true; + egui::Window::new(title) + .collapsible(false) + .resizable(false) + .default_width(460.0) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .open(&mut open) + .show(ctx, |ui| self.fields(ui, workspace, keep)); + if !open { + *keep = false; + } + } + + fn fields(&mut self, ui: &mut Ui, workspace: &Workspace, keep: &mut bool) { + let busy = self.test.is_some() || self.save.is_some(); + egui::Grid::new("profile-form") + .num_columns(2) + .spacing([12.0, 8.0]) + .show(ui, |ui| { + ui.label("Name"); + ui.add(egui::TextEdit::singleline(&mut self.profile.name).desired_width(300.0)); + ui.end_row(); + + ui.label("Database"); + let before = self.profile.kind; + egui::ComboBox::from_id_salt("kind") + .selected_text(kind_name(self.profile.kind)) + .show_ui(ui, |ui| { + for kind in [ + DatabaseKind::PostgreSql, + DatabaseKind::MySql, + DatabaseKind::SQLite, + DatabaseKind::MongoDb, + ] { + ui.selectable_value(&mut self.profile.kind, kind, kind_name(kind)); + } + }); + if before != self.profile.kind { + self.port_text = self + .profile + .kind + .default_port() + .map(|p| p.to_string()) + .unwrap_or_default(); + self.test_result = None; + } + ui.end_row(); + + if self.profile.kind == DatabaseKind::SQLite { + ui.label("File"); + ui.horizontal(|ui| { + let path = self + .profile + .config + .file_path + .get_or_insert_with(String::new); + ui.add( + egui::TextEdit::singleline(path) + .desired_width(220.0) + .hint_text(":memory:"), + ); + if ui.button("Browse…").clicked() + && let Some(picked) = rfd::FileDialog::new() + .add_filter("SQLite", &["db", "sqlite", "sqlite3"]) + .pick_file() + { + *path = picked.display().to_string(); + } + }); + ui.end_row(); + } else { + ui.label("Host"); + ui.add( + egui::TextEdit::singleline( + self.profile.config.host.get_or_insert_with(String::new), + ) + .desired_width(300.0), + ); + ui.end_row(); + if !(self.profile.kind == DatabaseKind::MongoDb && self.profile.config.srv) { + ui.label("Port"); + ui.add( + egui::TextEdit::singleline(&mut self.port_text).desired_width(100.0), + ); + ui.end_row(); + } + ui.label("Database"); + ui.add( + egui::TextEdit::singleline( + self.profile.config.database.get_or_insert_with(String::new), + ) + .desired_width(300.0), + ); + ui.end_row(); + ui.label("User"); + ui.add( + egui::TextEdit::singleline( + self.profile.config.username.get_or_insert_with(String::new), + ) + .desired_width(300.0), + ); + ui.end_row(); + ui.label("Password"); + ui.add( + egui::TextEdit::singleline(&mut self.password) + .password(true) + .desired_width(300.0) + .hint_text(if self.editing { + "unchanged unless typed" + } else { + "" + }), + ); + ui.end_row(); + ui.label("Options"); + ui.horizontal(|ui| { + ui.checkbox(&mut self.profile.config.tls, "TLS"); + if self.profile.kind == DatabaseKind::MongoDb { + ui.checkbox(&mut self.profile.config.srv, "SRV (mongodb+srv)"); + } + }); + ui.end_row(); + if self.profile.kind == DatabaseKind::MongoDb { + ui.label("authSource"); + ui.add( + egui::TextEdit::singleline(&mut self.auth_source) + .desired_width(200.0) + .hint_text("admin"), + ); + ui.end_row(); + } + } + ui.label("Edits"); + ui.checkbox( + &mut self.profile.read_only, + "Read-only (uncheck to allow reviewed edits)", + ); + ui.end_row(); + }); + + if let Some((text, ok)) = &self.test_result { + let colour = if *ok { theme::SUCCESS } else { theme::DANGER }; + ui.label(RichText::new(text).color(colour)); + } + if let Some(error) = &self.error { + ui.label(RichText::new(error).color(theme::DANGER)); + } + ui.add_space(6.0); + ui.horizontal(|ui| { + if ui.add_enabled(!busy, egui::Button::new("Test")).clicked() { + self.commit_fields(); + self.error = None; + self.test_result = None; + match self.profile.validate() { + Ok(()) => { + self.test = + Some(workspace.test_connection(self.profile.clone(), self.secret())) + } + Err(error) => self.error = Some(error.to_string()), + } + } + if ui + .add_enabled(!busy, theme::primary_button("Save")) + .clicked() + { + self.commit_fields(); + self.error = None; + match self.profile.validate() { + Ok(()) => { + self.save = + Some(workspace.save_profile(self.profile.clone(), self.secret())) + } + Err(error) => self.error = Some(error.to_string()), + } + } + if ui.button("Cancel").clicked() { + self.password.clear(); + *keep = false; + } + if busy { + ui.spinner(); + } + }); + } +} diff --git a/apps/redrob-query/src/schema.rs b/apps/redrob-query/src/schema.rs new file mode 100644 index 0000000..b20f9ea --- /dev/null +++ b/apps/redrob-query/src/schema.rs @@ -0,0 +1,321 @@ +//! The Schema browser: tables/views/collections of each connected profile, with columns loaded on +//! expand. Nodes come from the engine's `load_metadata`, which returns the root (tables) for a +//! connection and the children (columns, or a Mongo collection's sampled fields) for a node id. + +use std::collections::HashMap; + +use egui::{RichText, Ui}; +use redrob_query_core::{ + ConnectionProfile, DatabaseKind, MetadataKind, MetadataNode, MetadataRequest, Pending, + Workspace, +}; +use uuid::Uuid; + +use crate::theme; + +/// What the browser asks the app to do. +#[derive(Debug)] +pub enum SchemaEvent { + /// Show this table in the structure panel and (double-click) run a starter SELECT on it. + Select { + connection_id: Uuid, + node: MetadataNode, + }, + Preview { + connection_id: Uuid, + node: MetadataNode, + }, + Notice(String, bool), +} + +#[derive(Default)] +struct Tree { + roots: Vec, + children: HashMap>, + loading: HashMap, Pending>>, + expanded: HashMap, + filter: String, +} + +#[derive(Default)] +pub struct SchemaBrowser { + trees: HashMap, + pub selected: Option<(Uuid, String)>, +} + +impl SchemaBrowser { + pub fn load_root(&mut self, workspace: &Workspace, connection_id: Uuid) { + let tree = self.trees.entry(connection_id).or_default(); + tree.roots.clear(); + tree.children.clear(); + tree.loading.insert( + None, + workspace.load_metadata(MetadataRequest { + connection_id, + parent_id: None, + }), + ); + } + + pub fn forget(&mut self, connection_id: Uuid) { + self.trees.remove(&connection_id); + if self + .selected + .as_ref() + .is_some_and(|(id, _)| *id == connection_id) + { + self.selected = None; + } + } + + /// The root nodes of a connection, for the editor's completion and the structure panel. + pub fn roots(&self, connection_id: Uuid) -> &[MetadataNode] { + self.trees + .get(&connection_id) + .map_or(&[], |tree| tree.roots.as_slice()) + } + + pub fn poll(&mut self, events: &mut Vec) { + for tree in self.trees.values_mut() { + let mut done = Vec::new(); + for (parent, pending) in &mut tree.loading { + if let Some(result) = pending.poll() { + done.push((parent.clone(), result)); + } + } + for (parent, result) in done { + tree.loading.remove(&parent); + match (parent, result) { + (None, Ok(nodes)) => tree.roots = nodes, + (Some(id), Ok(nodes)) => { + tree.children.insert(id, nodes); + } + (_, Err(error)) => events.push(SchemaEvent::Notice(error.to_string(), true)), + } + } + } + } + + pub fn ui( + &mut self, + ui: &mut Ui, + workspace: &Workspace, + connected: &[&ConnectionProfile], + events: &mut Vec, + ) { + ui.heading("Schema"); + if connected.is_empty() { + ui.label(RichText::new("Connect a profile to browse its tables.").weak()); + return; + } + egui::ScrollArea::vertical() + .id_salt("schema") + .auto_shrink([false, false]) + .show(ui, |ui| { + for profile in connected { + self.connection_ui(ui, workspace, profile, events); + } + }); + } + + fn connection_ui( + &mut self, + ui: &mut Ui, + workspace: &Workspace, + profile: &ConnectionProfile, + events: &mut Vec, + ) { + let connection_id = profile.id; + let tree = self.trees.entry(connection_id).or_default(); + let root_loading = tree.loading.contains_key(&None); + let header = egui::CollapsingHeader::new(RichText::new(&profile.name).strong()) + .id_salt(("schema-root", connection_id)) + .default_open(true); + let mut to_load: Vec = Vec::new(); + let mut reload = false; + let mut selected = self.selected.clone(); + header.show(ui, |ui| { + ui.horizontal(|ui| { + ui.add( + egui::TextEdit::singleline(&mut tree.filter) + .hint_text("Filter tables") + .desired_width(ui.available_width() - 40.0), + ); + if ui.small_button("⟳").on_hover_text("Reload").clicked() { + reload = true; + } + }); + if root_loading { + ui.horizontal(|ui| { + ui.spinner(); + ui.label(RichText::new("Loading…").weak()); + }); + } else if tree.roots.is_empty() { + ui.label(RichText::new("No tables or collections.").weak()); + } + let filter = tree.filter.to_lowercase(); + // Group by schema attribute when present (PostgreSQL, MySQL). + let mut groups: Vec<(String, Vec<&MetadataNode>)> = Vec::new(); + for node in &tree.roots { + if !filter.is_empty() && !node.name.to_lowercase().contains(&filter) { + continue; + } + let schema = node.attributes.get("schema").cloned().unwrap_or_default(); + match groups.iter_mut().find(|(s, _)| *s == schema) { + Some((_, nodes)) => nodes.push(node), + None => groups.push((schema, vec![node])), + } + } + let grouped = groups.len() > 1 || groups.first().is_some_and(|(s, _)| !s.is_empty()); + for (schema, nodes) in groups { + let body = |ui: &mut Ui| { + for node in nodes { + let expanded = tree.expanded.get(&node.id).copied().unwrap_or(false); + let is_selected = selected + .as_ref() + .is_some_and(|(c, id)| *c == connection_id && *id == node.id); + let glyph = match node.kind { + MetadataKind::View => "▤", + MetadataKind::Collection => "{}", + _ => "▦", + }; + ui.horizontal(|ui| { + let arrow = if expanded { "▾" } else { "▸" }; + if ui.small_button(arrow).clicked() { + let now = !expanded; + tree.expanded.insert(node.id.clone(), now); + if now + && !tree.children.contains_key(&node.id) + && !tree.loading.contains_key(&Some(node.id.clone())) + { + to_load.push(node.id.clone()); + } + } + let response = + ui.selectable_label(is_selected, format!("{glyph} {}", node.name)); + if response.clicked() { + selected = Some((connection_id, node.id.clone())); + events.push(SchemaEvent::Select { + connection_id, + node: node.clone(), + }); + } + if response.double_clicked() { + events.push(SchemaEvent::Preview { + connection_id, + node: node.clone(), + }); + } + response.context_menu(|ui| { + if ui.button("Preview rows").clicked() { + events.push(SchemaEvent::Preview { + connection_id, + node: node.clone(), + }); + ui.close(); + } + if ui.button("Copy name").clicked() { + ui.ctx().copy_text(node.name.clone()); + ui.close(); + } + }); + }); + if expanded { + ui.indent(("cols", &node.id), |ui| match tree.children.get(&node.id) { + Some(children) => { + for column in children { + column_row(ui, column); + } + } + None => { + ui.horizontal(|ui| { + ui.spinner(); + ui.label(RichText::new("Loading…").weak().small()); + }); + } + }); + } + } + }; + if grouped { + egui::CollapsingHeader::new(RichText::new(if schema.is_empty() { + "(default)".to_owned() + } else { + schema.clone() + })) + .id_salt(("schema-group", connection_id, &schema)) + .default_open(true) + .show(ui, body); + } else { + body(ui); + } + } + }); + self.selected = selected; + for id in to_load { + let request = MetadataRequest { + connection_id, + parent_id: Some(id.clone()), + }; + if let Some(tree) = self.trees.get_mut(&connection_id) { + tree.loading + .insert(Some(id), workspace.load_metadata(request)); + } + } + if reload { + self.load_root(workspace, connection_id); + } + } +} + +fn column_row(ui: &mut Ui, column: &MetadataNode) { + ui.horizontal(|ui| { + ui.label(RichText::new(&column.name).monospace().small()); + if let Some(data_type) = &column.data_type { + ui.label(RichText::new(data_type).small().weak()); + } + if column.nullable == Some(false) { + ui.label(RichText::new("not null").small().color(theme::CAUTION)); + } + }); +} + +/// `schema`/`table` out of a root node id (`table:.` or `table:`, +/// `collection:`), the way the engine addresses them. +pub fn split_table_id(node: &MetadataNode, kind: DatabaseKind) -> (Option, String) { + let schema = node + .attributes + .get("schema") + .cloned() + .filter(|s| !s.is_empty()); + match kind { + DatabaseKind::PostgreSql | DatabaseKind::MySql => (schema, node.name.clone()), + _ => (None, node.name.clone()), + } +} + +/// A bounded SELECT (or find) on a table, for a preview tab. +pub fn preview_query(kind: DatabaseKind, node: &MetadataNode) -> String { + let (schema, table) = split_table_id(node, kind); + match kind { + DatabaseKind::MongoDb => format!( + "{{\n \"collection\": {},\n \"operation\": \"find\",\n \"filter\": {{}},\n \"limit\": 50\n}}", + serde_json::Value::String(table) + ), + DatabaseKind::MySql => { + let target = match schema { + Some(s) => format!("`{}`.`{}`", s.replace('`', "``"), table.replace('`', "``")), + None => format!("`{}`", table.replace('`', "``")), + }; + format!("SELECT *\nFROM {target}\nLIMIT 200;") + } + _ => { + let quote = |n: &str| format!("\"{}\"", n.replace('"', "\"\"")); + let target = match schema { + Some(s) => format!("{}.{}", quote(&s), quote(&table)), + None => quote(&table), + }; + format!("SELECT *\nFROM {target}\nLIMIT 200;") + } + } +} diff --git a/apps/redrob-query/src/services.rs b/apps/redrob-query/src/services.rs new file mode 100644 index 0000000..0330fa4 --- /dev/null +++ b/apps/redrob-query/src/services.rs @@ -0,0 +1,65 @@ +//! Where Redrob Query keeps its per-user data, and the names every platform shows for it. +//! +//! One config directory for the whole app (see [`config_dir`]): `connections.json` (profiles, no +//! secrets; the engine's own file), `account.json` (this app's own sign-in, if the launcher's +//! Connect wrote one), `workspace.json` (open tabs and history) and eframe's `ui.ron` (window +//! geometry, panel sizes). Passwords are in the OS keyring, never here. + +use std::ffi::OsString; +use std::path::PathBuf; + +/// Desktop app ID: the Linux `.desktop` file name, hicolor icon name and Wayland app id. +pub const APP_ID: &str = "ai.redrob.query"; +/// The window title and the brand text in the top bar. +pub const APP_NAME: &str = "Redrob Query"; +/// `REDROB_QUERY_CONFIG_DIR` overrides the platform directory (tests, agents, custom setups). +pub const CONFIG_DIR_ENV: &str = "REDROB_QUERY_CONFIG_DIR"; +/// `REDROB_QUERY_NO_PREFS`: read and write no workspace or window state (tests, demos). Profiles +/// are still stored: they are the engine's, and a demo without them would have nothing to connect. +pub const NO_PREFS_ENV: &str = "REDROB_QUERY_NO_PREFS"; + +/// Per-user settings directory: `REDROB_QUERY_CONFIG_DIR`, else the platform convention +/// (macOS `~/Library/Application Support/Redrob Query`, Windows `%APPDATA%\Redrob Query`, +/// elsewhere `$XDG_CONFIG_HOME/redrob-query` or `~/.config/redrob-query`). +pub fn config_dir() -> Option { + config_dir_with(std::env::consts::OS, |key| std::env::var_os(key)) +} + +/// [`config_dir`] from an explicit environment, so tests can fake `HOME` / `APPDATA`. +pub fn config_dir_with(os: &str, env: impl Fn(&str) -> Option) -> Option { + if let Some(root) = env(CONFIG_DIR_ENV) { + // An explicitly empty override must not fall back to the user's profile. + return (!root.is_empty()).then(|| PathBuf::from(root)); + } + let home = env("HOME").filter(|h| !h.is_empty()).map(PathBuf::from); + match os { + "macos" => home.map(|h| h.join("Library/Application Support").join(APP_NAME)), + "windows" => env("APPDATA") + .filter(|a| !a.is_empty()) + .map(|a| PathBuf::from(a).join(APP_NAME)), + _ => env("XDG_CONFIG_HOME") + .filter(|x| !x.is_empty()) + .map(PathBuf::from) + .or_else(|| home.map(|h| h.join(".config"))) + .map(|c| c.join("redrob-query")), + } +} + +/// Runs without preferences neither read nor write the workspace or window state. +pub fn prefs_enabled() -> bool { + std::env::var_os(NO_PREFS_ENV).is_none() +} + +/// eframe's own persistence file (window geometry, panel sizes). +pub fn persistence_path() -> Option { + config_dir() + .filter(|_| prefs_enabled()) + .map(|d| d.join("ui.ron")) +} + +/// The open tabs and the query history (`workspace.rs`). +pub fn workspace_path() -> Option { + config_dir() + .filter(|_| prefs_enabled()) + .map(|d| d.join("workspace.json")) +} diff --git a/apps/redrob-query/src/structure.rs b/apps/redrob-query/src/structure.rs new file mode 100644 index 0000000..3d44025 --- /dev/null +++ b/apps/redrob-query/src/structure.rs @@ -0,0 +1,535 @@ +//! The Structure panel: the selected table's columns, primary key, foreign keys and indexes, and +//! the table-change form (create a table, add a column) that runs through the engine's `ddl`. + +use egui::{RichText, Ui}; +use redrob_query_core::{ + DatabaseKind, MetadataNode, MetadataRequest, Pending, Workspace, + ddl::{ColumnSpec, ColumnType, TableChange}, + keys::TableKeys, +}; +use uuid::Uuid; + +use crate::schema::split_table_id; +use crate::theme; + +pub enum StructureEvent { + /// A DDL change was applied: reload the schema tree of this connection. + Changed(Uuid), + Notice(String, bool), +} + +#[derive(Default)] +pub struct StructurePanel { + pub target: Option, + columns: Option>, + keys: Option, + columns_load: Option>>, + keys_load: Option>, + form: Option, + apply: Option>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Target { + pub connection_id: Uuid, + pub kind: DatabaseKind, + pub node: MetadataNode, +} + +impl StructurePanel { + pub fn show_table( + &mut self, + workspace: &Workspace, + connection_id: Uuid, + kind: DatabaseKind, + node: MetadataNode, + ) { + let (schema, table) = split_table_id(&node, kind); + self.columns = None; + self.keys = None; + self.columns_load = Some(workspace.load_metadata(MetadataRequest { + connection_id, + parent_id: Some(node.id.clone()), + })); + self.keys_load = Some(workspace.table_keys(connection_id, schema, table)); + self.target = Some(Target { + connection_id, + kind, + node, + }); + } + + pub fn forget(&mut self, connection_id: Uuid) { + if self + .target + .as_ref() + .is_some_and(|t| t.connection_id == connection_id) + { + *self = Self::default(); + } + } + + pub fn poll(&mut self, events: &mut Vec) { + if let Some(load) = &mut self.columns_load + && let Some(result) = load.poll() + { + self.columns_load = None; + match result { + Ok(columns) => self.columns = Some(columns), + Err(error) => events.push(StructureEvent::Notice(error.to_string(), true)), + } + } + if let Some(load) = &mut self.keys_load + && let Some(result) = load.poll() + { + self.keys_load = None; + match result { + Ok(keys) => self.keys = Some(keys), + // Keys are a nicety: a dialect without them still shows its columns. + Err(error) => { + log::info!("table keys unavailable: {error}"); + self.keys = Some(TableKeys::default()); + } + } + } + if let Some(apply) = &mut self.apply + && let Some(result) = apply.poll() + { + self.apply = None; + match result { + Ok(()) => { + if let Some(form) = self.form.take() { + events.push(StructureEvent::Notice( + format!("Applied: {}", form.describe()), + false, + )); + events.push(StructureEvent::Changed(form.connection_id)); + } + } + Err(error) => { + if let Some(form) = &mut self.form { + form.error = Some(error.to_string()); + } + } + } + } + } + + pub fn ui(&mut self, ui: &mut Ui, workspace: &Workspace, read_only_profile: bool) { + ui.horizontal(|ui| { + ui.heading("Structure"); + if let Some(target) = &self.target { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if target.kind != DatabaseKind::MongoDb + && ui + .small_button("+ Column") + .on_hover_text("Add a column to this table") + .clicked() + { + let (schema, table) = split_table_id(&target.node, target.kind); + self.form = Some(TableChangeForm::add_column( + target.connection_id, + target.kind, + schema, + table, + )); + } + }); + } + }); + let Some(target) = self.target.clone() else { + ui.label(RichText::new("Select a table in the schema browser.").weak()); + return; + }; + ui.label(RichText::new(&target.node.name).strong().monospace()); + if let Some(schema) = target.node.attributes.get("schema") { + ui.label(RichText::new(format!("schema {schema}")).small().weak()); + } + egui::ScrollArea::vertical() + .id_salt("structure") + .auto_shrink([false, false]) + .show(ui, |ui| { + ui.label(RichText::new("Columns").small().strong()); + match &self.columns { + None => { + ui.spinner(); + } + Some(columns) if columns.is_empty() => { + ui.label(RichText::new("No columns reported.").weak()); + } + Some(columns) => { + let keys = self.keys.clone().unwrap_or_default(); + egui::Grid::new("structure-columns") + .num_columns(3) + .striped(true) + .spacing([12.0, 2.0]) + .show(ui, |ui| { + for column in columns { + let is_key = keys.primary.contains(&column.name); + let name = RichText::new(&column.name).monospace(); + ui.label(if is_key { + name.color(theme::REDROB_BLUE) + } else { + name + }); + ui.label( + RichText::new(column.data_type.clone().unwrap_or_default()) + .small() + .weak(), + ); + let mut flags = Vec::new(); + if is_key { + flags.push("PK"); + } + if column.nullable == Some(false) { + flags.push("not null"); + } + if keys + .foreign + .iter() + .any(|fk| fk.columns.contains(&column.name)) + { + flags.push("FK"); + } + ui.label(RichText::new(flags.join(" · ")).small()); + ui.end_row(); + } + }); + } + } + if let Some(keys) = &self.keys { + if !keys.foreign.is_empty() { + ui.add_space(6.0); + ui.label(RichText::new("Foreign keys").small().strong()); + for fk in &keys.foreign { + let mut line = format!( + "{} → {} ({})", + fk.columns.join(", "), + fk.referenced_table, + fk.referenced_columns.join(", ") + ); + if !fk.on_delete.is_empty() && fk.on_delete != "NO ACTION" { + line.push_str(&format!( + " on delete {}", + fk.on_delete.to_lowercase() + )); + } + ui.label(RichText::new(line).monospace().small()) + .on_hover_text(if fk.name.is_empty() { + "unnamed constraint".to_owned() + } else { + fk.name.clone() + }); + } + } + if !keys.indexes.is_empty() { + ui.add_space(6.0); + ui.label(RichText::new("Indexes").small().strong()); + for index in &keys.indexes { + let mut tags = Vec::new(); + if index.primary { + tags.push("primary"); + } else if index.unique { + tags.push("unique"); + } + ui.horizontal(|ui| { + ui.label(RichText::new(&index.name).monospace().small()); + ui.label( + RichText::new(format!("({})", index.columns.join(", "))) + .small() + .weak(), + ); + if !tags.is_empty() { + ui.label( + RichText::new(tags.join(", ")) + .small() + .color(theme::REDROB_BLUE), + ); + } + }); + } + } + } else if self.keys_load.is_some() { + ui.spinner(); + } + }); + if let Some(form) = &mut self.form { + let mut keep = true; + let mut submit = false; + form.ui( + ui.ctx(), + &mut keep, + &mut submit, + read_only_profile, + self.apply.is_some(), + ); + if submit { + match form.change() { + Ok(change) => { + form.error = None; + self.apply = Some(workspace.apply_table_change(form.connection_id, change)); + } + Err(reason) => form.error = Some(reason), + } + } + if !keep { + self.form = None; + } + } + } +} + +#[derive(Clone)] +struct ColumnRow { + name: String, + column_type: ColumnType, + nullable: bool, + primary_key: bool, +} + +impl Default for ColumnRow { + fn default() -> Self { + Self { + name: String::new(), + column_type: ColumnType::Text, + nullable: true, + primary_key: false, + } + } +} + +pub struct TableChangeForm { + pub connection_id: Uuid, + kind: DatabaseKind, + schema: String, + table: String, + create: bool, + columns: Vec, + pub error: Option, +} + +impl TableChangeForm { + fn add_column( + connection_id: Uuid, + kind: DatabaseKind, + schema: Option, + table: String, + ) -> Self { + Self { + connection_id, + kind, + schema: schema.unwrap_or_default(), + table, + create: false, + columns: vec![ColumnRow::default()], + error: None, + } + } + + fn describe(&self) -> String { + if self.create { + format!("created table {}", self.table) + } else { + format!( + "added column {} to {}", + self.columns.first().map_or("", |c| c.name.as_str()), + self.table + ) + } + } + + fn specs(&self) -> Vec { + self.columns + .iter() + .map(|c| ColumnSpec { + name: c.name.trim().to_owned(), + column_type: c.column_type, + nullable: c.nullable && !c.primary_key, + primary_key: c.primary_key, + }) + .collect() + } + + fn change(&self) -> Result { + let schema = Some(self.schema.trim().to_owned()).filter(|s| !s.is_empty()); + let table = self.table.trim().to_owned(); + if table.is_empty() { + return Err("name the table".to_owned()); + } + if self.columns.iter().any(|c| c.name.trim().is_empty()) { + return Err("every column needs a name".to_owned()); + } + let change = if self.create { + TableChange::CreateTable { + schema, + table, + columns: self.specs(), + } + } else { + let column = self + .specs() + .into_iter() + .next() + .ok_or_else(|| "name the column".to_owned())?; + TableChange::AddColumn { + schema, + table, + column, + } + }; + // The engine renders the statement; a refusal here is the same one it would give. + change.statement(self.kind).map_err(|e| e.to_string())?; + Ok(change) + } + + fn ui( + &mut self, + ctx: &egui::Context, + keep: &mut bool, + submit: &mut bool, + read_only: bool, + busy: bool, + ) { + let title = if self.create { + "Create table" + } else { + "Add column" + }; + let mut open = true; + egui::Window::new(title) + .collapsible(false) + .resizable(false) + .default_width(520.0) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .open(&mut open) + .show(ctx, |ui| { + egui::Grid::new("ddl-head") + .num_columns(2) + .spacing([12.0, 6.0]) + .show(ui, |ui| { + if matches!(self.kind, DatabaseKind::PostgreSql | DatabaseKind::MySql) { + ui.label("Schema"); + ui.add( + egui::TextEdit::singleline(&mut self.schema) + .desired_width(200.0) + .hint_text("default"), + ); + ui.end_row(); + } + ui.label("Table"); + ui.add_enabled( + self.create, + egui::TextEdit::singleline(&mut self.table).desired_width(300.0), + ); + ui.end_row(); + }); + ui.add_space(6.0); + ui.label( + RichText::new(if self.create { "Columns" } else { "Column" }) + .small() + .strong(), + ); + let mut remove: Option = None; + let create = self.create; + let count = self.columns.len(); + egui::Grid::new("ddl-columns") + .num_columns(5) + .spacing([10.0, 4.0]) + .show(ui, |ui| { + ui.label(RichText::new("Name").small().weak()); + ui.label(RichText::new("Type").small().weak()); + ui.label(RichText::new("Nullable").small().weak()); + ui.label(RichText::new("Primary").small().weak()); + ui.label(""); + ui.end_row(); + for (index, column) in self.columns.iter_mut().enumerate() { + ui.add( + egui::TextEdit::singleline(&mut column.name).desired_width(160.0), + ); + egui::ComboBox::from_id_salt(("ddl-type", index)) + .selected_text(type_name(column.column_type)) + .show_ui(ui, |ui| { + for t in [ + ColumnType::Text, + ColumnType::Integer, + ColumnType::Decimal, + ColumnType::Boolean, + ColumnType::Date, + ColumnType::Timestamp, + ] { + ui.selectable_value( + &mut column.column_type, + t, + type_name(t), + ); + } + }); + ui.add_enabled( + !column.primary_key, + egui::Checkbox::without_text(&mut column.nullable), + ); + ui.add_enabled( + create, + egui::Checkbox::without_text(&mut column.primary_key), + ); + if create && count > 1 && ui.small_button("✕").clicked() { + remove = Some(index); + } else if !create { + ui.label(""); + } + ui.end_row(); + } + }); + if let Some(index) = remove { + self.columns.remove(index); + } + if self.create && ui.small_button("+ column").clicked() { + self.columns.push(ColumnRow::default()); + } + if let Ok(change) = self.change() + && let Ok(statement) = change.statement(self.kind) + { + ui.add_space(6.0); + ui.label(RichText::new(statement).monospace().small().weak()); + } + if read_only { + ui.label( + RichText::new("This profile is read-only; the change will be refused.") + .color(theme::CAUTION), + ); + } + if let Some(error) = &self.error { + ui.label(RichText::new(error).color(theme::DANGER)); + } + ui.add_space(6.0); + ui.horizontal(|ui| { + if ui + .add_enabled(!busy && !read_only, theme::primary_button("Apply")) + .clicked() + { + *submit = true; + } + if ui.button("Cancel").clicked() { + *keep = false; + } + if busy { + ui.spinner(); + } + }); + }); + if !open { + *keep = false; + } + } +} + +fn type_name(t: ColumnType) -> &'static str { + match t { + ColumnType::Text => "text", + ColumnType::Integer => "integer", + ColumnType::Decimal => "decimal", + ColumnType::Boolean => "boolean", + ColumnType::Date => "date", + ColumnType::Timestamp => "timestamp", + } +} diff --git a/apps/redrob-query/src/theme.rs b/apps/redrob-query/src/theme.rs new file mode 100644 index 0000000..d2f0061 --- /dev/null +++ b/apps/redrob-query/src/theme.rs @@ -0,0 +1,54 @@ +//! The shell's look: egui's dark and light themes with the design system's tokens where they are +//! what distinguishes a Redrob window (redrob-design-system `20-color.md`: "Blue acts", ground +//! `deep` is Redrob Black). Every colour literal in the shell lives here, named after its token. + +use egui::Color32; + +/// `--blue-6`, Redrob Blue: the one accent. Buttons that act, the selected tab, the caret. +pub const REDROB_BLUE: Color32 = Color32::from_rgb(0x2B, 0x52, 0xFF); +/// `--redrob-black`: the deep ground behind the brand lockup (kept for the About surface). +#[allow(dead_code)] +pub const REDROB_BLACK: Color32 = Color32::from_rgb(0x0A, 0x0B, 0x0C); +/// `--red-6`, danger: destructive classifications and failed runs. +pub const DANGER: Color32 = Color32::from_rgb(0xE5, 0x3E, 0x3E); +/// `--green-6`, success: a connected profile, an applied change set. +pub const SUCCESS: Color32 = Color32::from_rgb(0x1F, 0xA9, 0x71); +/// `--amber-6`, caution: an elevated-risk mutation, a truncated result. +pub const CAUTION: Color32 = Color32::from_rgb(0xE0, 0x9A, 0x1F); + +/// Applies the shell's style on top of egui's theme: the accent, slightly roomier spacing, and +/// a monospace size that reads as code without crowding the grid. +pub fn apply(ctx: &egui::Context) { + ctx.all_styles_mut(|style| { + style.visuals.selection.bg_fill = REDROB_BLUE.linear_multiply(0.35); + style.visuals.selection.stroke.color = REDROB_BLUE; + style.visuals.hyperlink_color = REDROB_BLUE; + style.visuals.text_cursor.stroke.color = REDROB_BLUE; + style.visuals.widgets.active.bg_fill = REDROB_BLUE.linear_multiply(0.6); + style.spacing.item_spacing = egui::vec2(8.0, 6.0); + style.spacing.button_padding = egui::vec2(10.0, 4.0); + if let Some(mono) = style.text_styles.get_mut(&egui::TextStyle::Monospace) { + mono.size = 13.0; + } + }); +} + +/// A filled accent button (the primary action of a panel). +pub fn primary_button(text: &str) -> egui::Button<'_> { + egui::Button::new(egui::RichText::new(text).color(Color32::WHITE)).fill(REDROB_BLUE) +} + +/// A small status dot: green connected, grey not, red unsupported. +pub fn status_dot(ui: &mut egui::Ui, colour: Color32) { + let (rect, _) = ui.allocate_exact_size(egui::vec2(8.0, 8.0), egui::Sense::hover()); + ui.painter().circle_filled(rect.center(), 4.0, colour); +} + +/// The caution/danger/neutral colour for a message that may be a warning. +pub fn tone_for_error(ui: &egui::Ui, is_error: bool) -> Color32 { + if is_error { + DANGER + } else { + ui.visuals().text_color() + } +} diff --git a/apps/redrob-query/src/workspace.rs b/apps/redrob-query/src/workspace.rs new file mode 100644 index 0000000..f5f27d8 --- /dev/null +++ b/apps/redrob-query/src/workspace.rs @@ -0,0 +1,125 @@ +//! Workspace persistence: the open query tabs and the run history, in `workspace.json` under the +//! config directory. At most [`MAX_TABS`] tabs and [`MAX_HISTORY`] history entries are kept; with +//! `REDROB_QUERY_NO_PREFS` set nothing is read or written. Written atomically (temp file, rename). + +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::services; + +pub const MAX_TABS: usize = 30; +pub const MAX_HISTORY: usize = 50; +/// A tab's text past this is not persisted (the tab is kept, truncated); the editor is for +/// queries, not for pasting a dump. +const MAX_TAB_BYTES: usize = 256 * 1024; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default)] +pub struct SavedTab { + pub title: String, + pub text: String, + pub connection_id: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default)] +pub struct HistoryEntry { + pub connection_id: Option, + pub connection_name: String, + pub query: String, + /// RFC 3339, local time is not needed: the list shows relative order. + pub ran_at_unix: u64, + pub rows: usize, + pub elapsed_ms: u64, + pub ok: bool, +} + +impl Default for HistoryEntry { + fn default() -> Self { + Self { + connection_id: None, + connection_name: String::new(), + query: String::new(), + ran_at_unix: 0, + rows: 0, + elapsed_ms: 0, + ok: true, + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default)] +pub struct WorkspaceFile { + pub schema: u32, + pub tabs: Vec, + pub active_tab: usize, + pub history: Vec, + /// The AI dock and the structure panel are remembered open or closed. + pub ai_dock_open: bool, + pub structure_open: bool, +} + +impl WorkspaceFile { + pub fn load() -> Option { + let path = services::workspace_path()?; + let bytes = std::fs::read(path).ok()?; + let mut file: Self = serde_json::from_slice(&bytes).ok()?; + file.clamp(); + Some(file) + } + + /// Writes the file; a failure is logged, not shown -- losing the tab list is not worth a dialog. + pub fn save(&self) { + let Some(path) = services::workspace_path() else { + return; + }; + let mut copy = self.clone(); + copy.schema = 1; + copy.clamp(); + if let Err(error) = write_atomic(&path, ©) { + log::warn!("workspace not saved: {error}"); + } + } + + fn clamp(&mut self) { + if self.tabs.len() > MAX_TABS { + self.tabs.truncate(MAX_TABS); + } + for tab in &mut self.tabs { + if tab.text.len() > MAX_TAB_BYTES { + let mut end = MAX_TAB_BYTES; + while !tab.text.is_char_boundary(end) { + end -= 1; + } + tab.text.truncate(end); + } + } + if self.history.len() > MAX_HISTORY { + let drop = self.history.len() - MAX_HISTORY; + self.history.drain(..drop); + } + if self.active_tab >= self.tabs.len() { + self.active_tab = 0; + } + } +} + +fn write_atomic(path: &Path, file: &WorkspaceFile) -> std::io::Result<()> { + if let Some(dir) = path.parent() { + std::fs::create_dir_all(dir)?; + } + let json = serde_json::to_vec_pretty(file).map_err(std::io::Error::other)?; + let temp = path.with_extension("json.tmp"); + std::fs::write(&temp, json)?; + std::fs::rename(&temp, path) +} + +/// Seconds since the Unix epoch, for history stamps. +pub fn now_unix() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_or(0, |d| d.as_secs()) +} diff --git a/brand/SOURCES.json b/brand/SOURCES.json new file mode 100644 index 0000000..235ac1c --- /dev/null +++ b/brand/SOURCES.json @@ -0,0 +1,54 @@ +{ + "//": "Written by scripts/build-brand-assets.py; checked by scripts/check-brand.py. Do not edit.", + "design_system": "redrob-design-system (the published distribution)", + "sources": { + "assets/Favicons/favicon-16x16.png": "da56595c43ae2a707c9c910aaf7c62624a9f2a6593c5386c098a923c5c574926", + "assets/Favicons/favicon-32x32.png": "e15e5d44332ebd83806de82691926ce69e829779957f70dfb1f3a102eea20e86", + "assets/Favicons/favicon-48x48.png": "cea52085a4011e2ad18a8b3ec2f41b9e4dbb3fdafd3d3223bd66721e4423351f", + "assets/Favicons/favicon-64x64.png": "113869795269f386b7f601dd83febd99d196fcb9414593d97745c2d8be0f9bd0", + "assets/Favicons/icon-512.png": "9c168d6215a4e1957456d9aff3041eb8e33c4c8c9189663bc77ff505c2401e17", + "assets/Favicons/favicon.svg": "fd971f9e388afffa3c0f21153be36e23abb242f335c91ee7d8d1522acf88e09b", + "assets/Logos/redrob-logo-primary.png": "08ebbff1bf720d22fd9fdf60d0b31163276b27a5cc9deeb8358ae35e4f618df4", + "assets/Logos/redrob-logo-primary-white.png": "aefab23bcb9f9184e08775ebad80768039d9187e370e67b73b1b124a7613c7a8", + "assets/Products/redrob-browser-lockup-white.png": "c63bad2a52944e64deafbeda48467f8e2fd88f9ab8266dba6ab8a6e94328553c", + "fonts/Pretendard-SemiBold.woff2": "837b16488ab5264761a8e8bf871ec1be16a2997d157ace3cc7355d0a809d8639", + "tokens.css": "846a49dab8397554f1fb2505252de675ab2964cbfd44e6f3a6e03b17bd961ba9" + }, + "symbol_path_sha256": "bff33e6b4993876bde64dbd20861f620332b0294607569299450cfce9d05e08d", + "tokens": { + "blue-6": "#2b52ff", + "redrob-black": "#0a0b0c", + "ink-primary": "#0a0b0c" + }, + "copied_unchanged": { + "resources/icons/redrob-query.svg": "assets/Favicons/favicon.svg", + "resources/icons/hicolor/16x16/apps/redrob-query.png": "assets/Favicons/favicon-16x16.png", + "resources/icons/hicolor/32x32/apps/redrob-query.png": "assets/Favicons/favicon-32x32.png", + "resources/icons/hicolor/48x48/apps/redrob-query.png": "assets/Favicons/favicon-48x48.png", + "resources/icons/hicolor/64x64/apps/redrob-query.png": "assets/Favicons/favicon-64x64.png", + "resources/icons/hicolor/512x512/apps/redrob-query.png": "assets/Favicons/icon-512.png" + }, + "owned_dirs": [ + "resources/icons", + "resources/brand" + ], + "outputs": { + "installer/macos/res/icon.png": "26ebd3ba903656f2b3bc39ae9a8fd40160a7e088ad864f9c2816930509653050", + "installer/windows/res/icon.png": "26ebd3ba903656f2b3bc39ae9a8fd40160a7e088ad864f9c2816930509653050", + "installer/windows/res/lockup.png": "47875fa45360dbe16c3365c5abe4cf3f9deecc9d63d13b6675b6af48319bb3f1", + "resources/brand/redrob-query-lockup-white.png": "ffbb2641ad2a81161380c5ffcf489f10bb07f3a34e033564e294421ad5720863", + "resources/brand/redrob-query-lockup.png": "cc3e3e5d7a065a9a032dc7bed01ff68d841094a78ced490ddf577a15d3ebe9e7", + "resources/brand/redrob-query-splash.png": "0f5740de5c1730eeb3f5fade9f88d05191c861f48ac2aae159b2c13adb84b8db", + "resources/icons/hicolor/128x128/apps/redrob-query.png": "ddd419d662f512151e577eb066d8d11fffe6231e6b60cd1248de8535b2b21afb", + "resources/icons/hicolor/16x16/apps/redrob-query.png": "da56595c43ae2a707c9c910aaf7c62624a9f2a6593c5386c098a923c5c574926", + "resources/icons/hicolor/256x256/apps/redrob-query.png": "26ebd3ba903656f2b3bc39ae9a8fd40160a7e088ad864f9c2816930509653050", + "resources/icons/hicolor/32x32/apps/redrob-query.png": "e15e5d44332ebd83806de82691926ce69e829779957f70dfb1f3a102eea20e86", + "resources/icons/hicolor/48x48/apps/redrob-query.png": "cea52085a4011e2ad18a8b3ec2f41b9e4dbb3fdafd3d3223bd66721e4423351f", + "resources/icons/hicolor/512x512/apps/redrob-query.png": "9c168d6215a4e1957456d9aff3041eb8e33c4c8c9189663bc77ff505c2401e17", + "resources/icons/hicolor/64x64/apps/redrob-query.png": "113869795269f386b7f601dd83febd99d196fcb9414593d97745c2d8be0f9bd0", + "resources/icons/redrob-query-small.svg": "91726578f96b8cf36af6ea1cb261aeb7cbf4d3f17c9c5008f30177fa828c9550", + "resources/icons/redrob-query.icns": "2610d619fecd51000bde5b12385393873988bc371ff5bcfddea5b09704371e8f", + "resources/icons/redrob-query.ico": "c4dd7386998aefbd2dd6356742503e3d46e80e2b97229bd5fa07e8a77ad778d7", + "resources/icons/redrob-query.svg": "fd971f9e388afffa3c0f21153be36e23abb242f335c91ee7d8d1522acf88e09b" + } +} diff --git a/crates/redrob-core/Cargo.toml b/crates/redrob-core/Cargo.toml deleted file mode 100644 index c3ffef1..0000000 --- a/crates/redrob-core/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "redrob-core" -version.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true -authors.workspace = true -repository.workspace = true -homepage.workspace = true -description = "Secure Rust core for Redrob Query database connectivity, metadata, querying, and AI transport" -readme = "../../README.md" -publish = false - -[dependencies] -bigdecimal = "0.4" -chrono = { version = "0.4", features = ["serde"] } -futures-util = "0.3" -fs2 = "0.4" -base64 = "0.22" -keyring = { version = "3", features = ["apple-native", "windows-native", "linux-native"] } -mongodb = "3" -# The shared Redrob Code daemon, from redrob-code's own crate (docs/LOCAL-ENGINE-API.md section 3): -# finds or starts the one engine on this machine and hands back its record. The AI client talks to -# that daemon only; the daemon holds the Redrob console key. -redrob-engine-client = { git = "https://github.com/redrob-labs/redrob-code", tag = "v0.6.3" } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] } -secrecy = { version = "0.10", features = ["serde"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls-ring", "postgres", "mysql", "sqlite", "chrono", "bigdecimal", "uuid", "json"] } -tempfile = "3" -thiserror = "2" -tokio = { version = "1", features = ["sync", "time", "rt-multi-thread", "macros"] } -url = { version = "2", features = ["serde"] } -uuid = { version = "1", features = ["v4", "serde"] } - -[lints] -workspace = true diff --git a/crates/redrob-core/src/ai.rs b/crates/redrob-core/src/ai.rs deleted file mode 100644 index 971dc38..0000000 --- a/crates/redrob-core/src/ai.rs +++ /dev/null @@ -1,1277 +0,0 @@ -//! Redrob chat-completion client, through the shared Redrob Code daemon. -//! -//! One `redrob serve --register` runs per user and every Redrob app attaches to it (redrob-code -//! `docs/LOCAL-ENGINE-API.md`, section 3). The daemon holds the Redrob console key and calls the -//! console with its own credential; this app sends the daemon's Basic secret, its app name and a -//! session id on every chat request and never sees a console key on that path. There is -//! deliberately no direct path to the console: two paths would hide whether masking applied. - -use std::{ - sync::{Arc, Mutex}, - time::Duration, -}; - -use futures_util::StreamExt; -use redrob_engine_client::{Record, Unavailable, app_headers, openai_base}; -use reqwest::{Client, StatusCode, header::HeaderValue}; -use secrecy::ExposeSecret; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use tokio::time::sleep; -use uuid::Uuid; - -use crate::{ - AiAssistantRequest, AiChatRequest, AiChatResponse, AiMessage, AiRole, AiUsage, DataError, - DatabaseKind, QueryLanguage, Result, secret::SecretStore, -}; - -/// The name the daemon labels this app's requests with (`x-redrob-app`). -pub const ENGINE_APP: &str = "query"; -/// How long a daemon started by this app stays up with nobody attached. -pub const DEFAULT_IDLE_EXIT_MINUTES: u32 = 10; -/// Where a person gets Redrob Code when it is not installed: the launcher installs it. -pub const INSTALL_URL: &str = "https://github.com/redrob-labs/redrob-launcher/releases/latest"; -/// The sentence shown when no `redrob` binary exists. The link is [`INSTALL_URL`]. -pub const NOT_INSTALLED_MESSAGE: &str = - "Redrob Code is not installed. Install it with Redrob Launcher."; -/// The provider id the daemon lists the Redrob console under (`GET /provider`, `PUT /auth/redrob`). -pub const REDROB_PROVIDER_ID: &str = "redrob"; -/// The daemon's own error code when it holds no console credential (LOCAL-ENGINE-API.md, Errors). -const ENGINE_NOT_AUTHENTICATED: &str = "engine_not_authenticated"; -/// The daemon lists a credential-less Redrob provider with this placeholder; it is not a key. -const PUBLIC_SENTINEL: &str = "public"; -/// The keyring slot an earlier version of this app saved its own console key in. It is only read -/// to hand that key to the engine once, then deleted; profile storage still reserves the id. -pub const AI_SECRET_ID: Uuid = Uuid::from_u128(0x7265_6472_6f62_4169_0000_0000_0000_0001); -pub const MAX_AI_PROMPT_BYTES: usize = 8 * 1024; -pub const MAX_AI_ACTIVE_QUERY_BYTES: usize = 64 * 1024; -const MAX_ATTEMPTS: usize = 3; -const AI_TEMPERATURE: f32 = 0.2; -const MAX_ERROR_BODY_BYTES: usize = 64 * 1024; - -/// What the app renders for a missing engine: the install sentence with its link, or the start -/// failure's own reason. No fallback is named because there is none. -#[must_use] -pub fn unavailable_message(unavailable: &Unavailable) -> String { - match unavailable { - Unavailable::NotInstalled => format!("{NOT_INSTALLED_MESSAGE} {INSTALL_URL}"), - Unavailable::StartFailed(reason) => { - format!("The Redrob Code engine could not be started: {reason}") - } - } -} - -/// Builds the provider request from the narrow, renderer-facing assistant contract. -pub fn build_assistant_chat_request(input: &AiAssistantRequest) -> Result { - if input.prompt.trim().is_empty() { - return Err(DataError::AiRejected("prompt cannot be empty".to_owned())); - } - if input.prompt.len() > MAX_AI_PROMPT_BYTES { - return Err(DataError::AiRejected(format!( - "prompt exceeds {MAX_AI_PROMPT_BYTES} bytes" - ))); - } - if input - .active_query - .as_ref() - .is_some_and(|query| query.len() > MAX_AI_ACTIVE_QUERY_BYTES) - { - return Err(DataError::AiRejected(format!( - "active query exceeds {MAX_AI_ACTIVE_QUERY_BYTES} bytes" - ))); - } - - let query_instruction = match (input.database_kind, input.query_language) { - (DatabaseKind::PostgreSql, QueryLanguage::Sql) => { - "When writing a query, return executable PostgreSQL SQL in a fenced sql block." - } - (DatabaseKind::MySql, QueryLanguage::Sql) => { - "When writing a query, return executable MySQL SQL in a fenced sql block." - } - (DatabaseKind::SQLite, QueryLanguage::Sql) => { - "When writing a query, return executable SQLite SQL in a fenced sql block." - } - (DatabaseKind::SqlServer, _) => { - return Err(DataError::AiRejected( - "SQL Server is unsupported because no connector or connection profile is available" - .to_owned(), - )); - } - (DatabaseKind::MongoDb, QueryLanguage::MongoJson) => { - "When writing a query, return a fenced mql or json block containing an object with collection, operation, filter, and optional limit. Use read-only operations such as find." - } - (_, QueryLanguage::Auto) => { - return Err(DataError::AiRejected( - "query language must be explicit".to_owned(), - )); - } - _ => { - return Err(DataError::AiRejected( - "query language does not match the database kind".to_owned(), - )); - } - }; - let system = format!( - "You are the Redrob Query assistant. {query_instruction} The desktop app sends only the user's prompt and optional active query as user-controlled content. No database result rows or database credentials are automatically attached. Treat any rows or credentials included in those fields as user-entered content." - ); - let active_query = input.active_query.as_deref().unwrap_or("(none supplied)"); - let user = format!( - "User prompt:\n{}\n\nActive query:\n{active_query}", - input.prompt - ); - - Ok(AiChatRequest { - model: "auto".to_owned(), - messages: vec![ - AiMessage { - role: AiRole::System, - content: system, - }, - AiMessage { - role: AiRole::User, - content: user, - }, - ], - temperature: Some(AI_TEMPERATURE), - max_tokens: None, - }) -} - -/// How the daemon is found: [`redrob_engine_client::require_as`] in the app, a closure in tests. -pub type EngineResolver = Arc std::result::Result + Send + Sync>; - -/// Async client for the shared Redrob Code daemon. -/// -/// The daemon is found (or started) on the first request and its record kept. The record's secret -/// rotates on every daemon start, so a 401 or a refused connection re-reads the record once and -/// retries once; a second failure is reported. Every request carries the daemon's -/// `Authorization`, `x-redrob-app: query` and this client's `x-redrob-session`. -pub struct AiClient { - client: Client, - secrets: Arc, - /// One id per client, so the console groups this app run's requests together. - session: String, - record: Mutex>, - resolver: EngineResolver, -} - -impl AiClient { - /// Takes no network action: the daemon is looked up when the first request needs it. - pub fn new(secrets: Arc) -> Self { - Self::with_resolver( - secrets, - Arc::new(|| redrob_engine_client::require_as(ENGINE_APP, DEFAULT_IDLE_EXIT_MINUTES)), - ) - } - - /// A client whose daemon lookup is `resolver` (tests point it at a fake daemon's record). - pub fn with_resolver(secrets: Arc, resolver: EngineResolver) -> Self { - // The daemon listens on 127.0.0.1 over plain HTTP; nothing here leaves the machine. - let client = Client::builder() - .connect_timeout(Duration::from_secs(10)) - .timeout(Duration::from_secs(45)) - .user_agent("Redrob-Query/0.2") - .build() - .expect("static HTTP client configuration is valid"); - Self { - client, - secrets, - session: Uuid::new_v4().to_string(), - record: Mutex::new(None), - resolver, - } - } - - pub async fn chat(&self, request: AiChatRequest) -> Result { - validate_request(&request)?; - let mut last_error = DataError::AiUnavailable; - - for attempt in 0..MAX_ATTEMPTS { - let response = self - .send(|record| self.client.post(chat_endpoint(record)).json(&request)) - .await; - match response { - Ok(response) if response.status().is_success() => { - let bytes = bounded_response(response, 2 * 1024 * 1024).await?; - return parse_response(&bytes); - } - Ok(response) => { - let status = response.status(); - last_error = classify_status(status); - if !retryable_status(status) || attempt + 1 == MAX_ATTEMPTS { - return Err(last_error); - } - } - // A missing engine or a daemon that is not signed in does not get better by asking - // again; a transport failure might. - Err(error @ (DataError::EngineUnavailable(_) | DataError::AiAuthentication)) => { - return Err(error); - } - Err(error) => { - last_error = error; - if attempt + 1 == MAX_ATTEMPTS { - return Err(last_error); - } - } - } - sleep(Duration::from_millis(200 * (1_u64 << attempt))).await; - } - Err(last_error) - } - - /// Whether the daemon is signed in to the Redrob console (`GET /provider`), and with whose key. - /// Read fresh on every call: the person may sign in or out elsewhere while this app is open. - pub async fn account_status(&self) -> Result { - let response = self - .send(|record| self.client.get(daemon_endpoint(record, "provider"))) - .await?; - if !response.status().is_success() { - return Err(classify_status(response.status())); - } - let bytes = bounded_response(response, 2 * 1024 * 1024).await?; - let list: Value = - serde_json::from_slice(&bytes).map_err(|_| DataError::AiInvalidResponse)?; - Ok(AiKeyStatus::from_provider_list(&list)) - } - - /// Hands a console key to the daemon (`PUT /auth/redrob`), which keeps it from here on. A second - /// call rotates it. Nothing in this app holds the key afterwards. - pub async fn sign_in(&self, key: &str) -> Result<()> { - if key.trim().is_empty() { - return Err(DataError::SecretUnavailable); - } - let response = self.send(|record| self.auth_request(record, key)).await?; - accepted_key(response.status()) - } - - /// Finds or starts the daemon, off the async runtime: the client crate shells out to - /// `redrob service start` and probes with blocking HTTP. - async fn connect(&self) -> Result { - if let Some(record) = self.lock_record().clone() { - return Ok(record); - } - let resolver = Arc::clone(&self.resolver); - let record = tokio::task::spawn_blocking(move || resolver()) - .await - .map_err(|_| DataError::AiUnavailable)? - .map_err(|unavailable| { - DataError::EngineUnavailable(unavailable_message(&unavailable)) - })?; - *self.lock_record() = Some(record.clone()); - self.migrate_legacy_key(&record).await; - Ok(record) - } - - /// A key an earlier version of this app saved in the OS keyring is handed to the engine once - /// and deleted locally; the engine keeps it from then on. A failure leaves it for the next run. - async fn migrate_legacy_key(&self, record: &Record) { - let Ok(Some(key)) = self.secrets.get(AI_SECRET_ID) else { - return; - }; - let accepted = self - .headers(record, self.auth_request(record, key.expose_secret())) - .send() - .await - .is_ok_and(|response| response.status().is_success()); - if accepted { - let _ = self.secrets.delete(AI_SECRET_ID); - } - } - - fn auth_request(&self, record: &Record, key: &str) -> reqwest::RequestBuilder { - self.client - .put(daemon_endpoint( - record, - &format!("auth/{REDROB_PROVIDER_ID}"), - )) - .json(&serde_json::json!({"type": "api", "key": key})) - } - - fn lock_record(&self) -> std::sync::MutexGuard<'_, Option> { - self.record - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - } - - /// Sends one request to the daemon with its headers, retrying once with a fresh record when the - /// daemon has evidently restarted (connection refused, or 401 against the secret in hand). A 401 - /// whose code says the daemon itself is not signed in is reported as such, not retried. - async fn send( - &self, - build: impl Fn(&Record) -> reqwest::RequestBuilder, - ) -> Result { - let record = self.connect().await?; - let first = self.headers(&record, build(&record)).send().await; - match first { - Ok(response) if response.status() != StatusCode::UNAUTHORIZED => return Ok(response), - Ok(response) => { - let body = bounded_response(response, MAX_ERROR_BODY_BYTES) - .await - .unwrap_or_default(); - if names_engine_not_authenticated(&body) { - return Err(DataError::AiAuthentication); - } - } - Err(error) if !error.is_connect() => return Err(transport_error(&error)), - Err(_) => {} - } - *self.lock_record() = None; - let record = self.connect().await?; - self.headers(&record, build(&record)) - .send() - .await - .map_err(|error| transport_error(&error)) - } - - fn headers( - &self, - record: &Record, - builder: reqwest::RequestBuilder, - ) -> reqwest::RequestBuilder { - let mut builder = builder; - for (name, value) in app_headers(record, ENGINE_APP, Some(&self.session)) { - let Ok(mut header) = HeaderValue::from_str(&value) else { - continue; - }; - if name == "Authorization" { - header.set_sensitive(true); - } - builder = builder.header(name, header); - } - builder - } -} - -/// `/v1/chat/completions`: the OpenAI-compatible route. -fn chat_endpoint(record: &Record) -> String { - format!("{}/chat/completions", openai_base(record)) -} - -/// `/`: the daemon's own routes (`provider`, `auth/`). -fn daemon_endpoint(record: &Record, path: &str) -> String { - format!("{}/{path}", record.url.trim_end_matches('/')) -} - -fn transport_error(error: &reqwest::Error) -> DataError { - if error.is_timeout() { - DataError::Timeout(Duration::from_secs(45)) - } else { - DataError::AiUnavailable - } -} - -/// The daemon's failure body could echo the payload; only the status is kept. -fn accepted_key(status: StatusCode) -> Result<()> { - if status.is_success() { - Ok(()) - } else { - Err(DataError::AiRejected( - "the engine did not accept the Redrob key".to_owned(), - )) - } -} - -/// Branches on the daemon's error `code`, never on its message, which is localized. -fn names_engine_not_authenticated(body: &[u8]) -> bool { - serde_json::from_slice::(body) - .ok() - .and_then(|value| { - value - .pointer("/error/code") - .and_then(Value::as_str) - .map(str::to_owned) - }) - .is_some_and(|code| code == ENGINE_NOT_AUTHENTICATED) -} - -/// Whether the engine is signed in to the Redrob console, and with whose key. Never a key. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AiKeyStatus { - pub source: AiKeySource, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum AiKeySource { - /// The engine holds a console key handed to it by an app, its environment or its config. - Engine, - /// The engine uses Redrob Launcher's shared sign-in. Signing out happens there. - Launcher, - /// The engine has no console key. - None, -} - -impl AiKeyStatus { - #[must_use] - pub const fn signed_out() -> Self { - Self { - source: AiKeySource::None, - } - } - - /// Reads `GET /provider`: a `redrob` entry with a credential means signed in. The shape is the - /// one the other Redrob apps read (`{"all": [{"id", "source", "key", "options": {"apiKey"}}]}`). - #[must_use] - pub fn from_provider_list(list: &Value) -> Self { - let Some(entry) = list.get("all").and_then(Value::as_array).and_then(|all| { - all.iter() - .find(|entry| entry.get("id").and_then(Value::as_str) == Some(REDROB_PROVIDER_ID)) - }) else { - return Self::signed_out(); - }; - if entry - .pointer("/options/apiKey") - .and_then(Value::as_str) - .is_some_and(|key| key == PUBLIC_SENTINEL) - { - return Self::signed_out(); - } - let source = entry.get("source").and_then(Value::as_str); - let has_key = entry - .get("key") - .and_then(Value::as_str) - .is_some_and(|key| !key.trim().is_empty()); - match source { - Some("launcher") => Self { - source: AiKeySource::Launcher, - }, - Some("api" | "env") => Self { - source: AiKeySource::Engine, - }, - _ if has_key => Self { - source: AiKeySource::Engine, - }, - _ => Self::signed_out(), - } - } -} - -async fn bounded_response(response: reqwest::Response, limit: usize) -> Result> { - if response - .content_length() - .is_some_and(|length| length > u64::try_from(limit).unwrap_or(u64::MAX)) - { - return Err(DataError::AiInvalidResponse); - } - let mut body = Vec::with_capacity(response.content_length().map_or(0, |length| { - usize::try_from(length).unwrap_or(limit).min(limit) - })); - let mut stream = response.bytes_stream(); - while let Some(chunk) = stream.next().await { - let chunk = chunk.map_err(|_| DataError::AiInvalidResponse)?; - if body.len().saturating_add(chunk.len()) > limit { - return Err(DataError::AiInvalidResponse); - } - body.extend_from_slice(&chunk); - } - Ok(body) -} - -fn validate_request(request: &AiChatRequest) -> Result<()> { - if request.messages.is_empty() || request.messages.len() > 100 { - return Err(DataError::AiRejected( - "provide between 1 and 100 messages".to_owned(), - )); - } - if request.model.trim().is_empty() || request.model.len() > 100 { - return Err(DataError::AiRejected("model is invalid".to_owned())); - } - if request - .messages - .iter() - .any(|message| message.content.len() > 100_000) - { - return Err(DataError::AiRejected("message is too large".to_owned())); - } - if request - .temperature - .is_some_and(|value| !(0.0..=2.0).contains(&value)) - { - return Err(DataError::AiRejected( - "temperature must be between 0 and 2".to_owned(), - )); - } - Ok(()) -} - -fn retryable_status(status: StatusCode) -> bool { - status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error() -} - -fn classify_status(status: StatusCode) -> DataError { - match status { - StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => DataError::AiAuthentication, - StatusCode::TOO_MANY_REQUESTS => DataError::AiUnavailable, - value if value.is_server_error() => DataError::AiUnavailable, - _ => DataError::AiRejected(format!("service returned HTTP {status}")), - } -} - -#[derive(Deserialize)] -struct WireResponse { - id: String, - model: String, - choices: Vec, - usage: Option, -} - -#[derive(Deserialize)] -struct WireChoice { - message: WireMessage, -} - -#[derive(Deserialize)] -struct WireMessage { - role: String, - content: String, -} - -#[derive(Deserialize)] -struct WireUsage { - #[serde(rename = "prompt_tokens")] - prompt: u32, - #[serde(rename = "completion_tokens")] - completion: u32, - #[serde(rename = "total_tokens")] - total: u32, -} - -fn parse_response(bytes: &[u8]) -> Result { - let wire: WireResponse = - serde_json::from_slice(bytes).map_err(|_| DataError::AiInvalidResponse)?; - let choice = wire - .choices - .into_iter() - .next() - .ok_or(DataError::AiInvalidResponse)?; - if choice.message.content.is_empty() { - return Err(DataError::AiInvalidResponse); - } - let role = match choice.message.role.as_str() { - "assistant" => AiRole::Assistant, - "system" => AiRole::System, - "user" => AiRole::User, - _ => return Err(DataError::AiInvalidResponse), - }; - Ok(AiChatResponse { - id: wire.id, - model: wire.model, - message: AiMessage { - role, - content: choice.message.content, - }, - usage: wire.usage.map(|usage| AiUsage { - prompt_tokens: usage.prompt, - completion_tokens: usage.completion, - total_tokens: usage.total, - }), - }) -} - -#[cfg(test)] -mod tests { - use std::{ - collections::{HashMap, VecDeque}, - io::{BufRead, BufReader, Read, Write}, - net::{TcpListener, TcpStream}, - sync::{ - Arc, Mutex, - atomic::{AtomicUsize, Ordering}, - }, - thread, - time::Duration as StdDuration, - }; - - use serde_json::json; - - use crate::secret::MemorySecretStore; - - use super::*; - - /// One request as the fake daemon saw it. - #[derive(Clone, Debug)] - struct Seen { - method: String, - path: String, - headers: HashMap, - body: String, - } - - impl Seen { - fn header(&self, name: &str) -> Option<&str> { - self.headers - .get(&name.to_ascii_lowercase()) - .map(String::as_str) - } - } - - /// What the fake daemon answers with. `declared_length` lies in `Content-Length` when set. - struct Answer { - status: u16, - body: String, - declared_length: Option, - } - - fn reply(status: u16, body: impl Into) -> Answer { - Answer { - status, - body: body.into(), - declared_length: None, - } - } - - fn completion(id: &str, content: &str) -> Answer { - reply( - 200, - json!({ - "id": id, "model": "mock-model", - "choices": [{"message": {"role": "assistant", "content": content}}], - "usage": {"prompt_tokens": 4, "completion_tokens": 3, "total_tokens": 7} - }) - .to_string(), - ) - } - - /// A tiny stand-in for the shared daemon on 127.0.0.1: the wrong secret is 401 everywhere, as - /// the real daemon does; `/api/health` answers; everything else is up to `answer`. - struct FakeDaemon { - url: String, - secret: String, - seen: Arc>>, - } - - impl FakeDaemon { - fn start(secret: &str, answer: impl Fn(&Seen) -> Answer + Send + Sync + 'static) -> Self { - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let port = listener.local_addr().unwrap().port(); - let seen = Arc::new(Mutex::new(Vec::new())); - let expected = redrob_engine_client::authorization(secret); - let log = Arc::clone(&seen); - thread::spawn(move || { - for stream in listener.incoming() { - let Ok(mut stream) = stream else { break }; - let Some(request) = read_request(&mut stream) else { - continue; - }; - log.lock().unwrap().push(request.clone()); - let answer = if request.header("authorization") != Some(expected.as_str()) { - reply( - 401, - json!({"error": {"message": "unauthorized"}}).to_string(), - ) - } else if request.path == "/api/health" { - reply(200, json!({"healthy": true}).to_string()) - } else { - answer(&request) - }; - let length = answer.declared_length.unwrap_or(answer.body.len()); - let _ = write!( - stream, - "HTTP/1.1 {} X\r\nContent-Type: application/json\r\nContent-Length: {length}\r\nConnection: close\r\n\r\n{}", - answer.status, answer.body - ); - let _ = stream.flush(); - } - }); - Self { - url: format!("http://127.0.0.1:{port}"), - secret: secret.into(), - seen, - } - } - - fn record(&self) -> Record { - self.record_with_secret(&self.secret) - } - - fn record_with_secret(&self, secret: &str) -> Record { - Record { - id: format!("id-{secret}"), - version: "0.6.3".into(), - url: self.url.clone(), - port: self.url.rsplit(':').next().unwrap().parse().unwrap(), - pid: std::process::id(), - secret: secret.into(), - } - } - - fn seen(&self) -> Vec { - self.seen.lock().unwrap().clone() - } - } - - fn read_request(stream: &mut TcpStream) -> Option { - stream - .set_read_timeout(Some(StdDuration::from_secs(5))) - .ok()?; - let mut reader = BufReader::new(stream); - let mut line = String::new(); - reader.read_line(&mut line).ok()?; - let mut parts = line.split_whitespace(); - let method = parts.next()?.to_owned(); - let path = parts.next()?.to_owned(); - let mut headers = HashMap::new(); - loop { - let mut header = String::new(); - reader.read_line(&mut header).ok()?; - let header = header.trim_end(); - if header.is_empty() { - break; - } - let (name, value) = header.split_once(':')?; - headers.insert(name.trim().to_ascii_lowercase(), value.trim().to_owned()); - } - let length: usize = headers - .get("content-length") - .and_then(|value| value.parse().ok()) - .unwrap_or(0); - let mut body = vec![0; length]; - reader.read_exact(&mut body).ok()?; - Some(Seen { - method, - path, - headers, - body: String::from_utf8_lossy(&body).into_owned(), - }) - } - - /// A resolver that answers from a script, repeating its last entry, and counts its calls. - fn scripted( - script: Vec>, - ) -> (EngineResolver, Arc) { - let calls = Arc::new(AtomicUsize::new(0)); - let counter = Arc::clone(&calls); - let script = Mutex::new(VecDeque::from(script)); - let resolver: EngineResolver = Arc::new(move || { - counter.fetch_add(1, Ordering::SeqCst); - let mut script = script.lock().unwrap(); - if script.len() > 1 { - script.pop_front().unwrap() - } else { - script.front().cloned().unwrap() - } - }); - (resolver, calls) - } - - fn client_for(daemon: &FakeDaemon) -> (AiClient, Arc) { - let (resolver, calls) = scripted(vec![Ok(daemon.record())]); - ( - AiClient::with_resolver(Arc::new(MemorySecretStore::default()), resolver), - calls, - ) - } - - fn test_chat_request() -> AiChatRequest { - AiChatRequest { - model: "auto".to_owned(), - messages: vec![AiMessage { - role: AiRole::User, - content: "show indexes".to_owned(), - }], - temperature: Some(0.2), - max_tokens: Some(128), - } - } - - #[tokio::test] - async fn chat_hits_the_daemon_v1_route_with_its_authorization_app_and_session() { - let daemon = FakeDaemon::start("s3cret", |request| { - assert_eq!(request.method, "POST"); - completion("chat-local", "Use an index.") - }); - let (client, calls) = client_for(&daemon); - - let response = client.chat(test_chat_request()).await.unwrap(); - assert_eq!(response.id, "chat-local"); - assert_eq!(response.model, "mock-model"); - assert_eq!(response.message.content, "Use an index."); - assert_eq!(response.usage.unwrap().total_tokens, 7); - assert_eq!(calls.load(Ordering::SeqCst), 1); - - let seen = daemon.seen(); - assert_eq!(seen.len(), 1, "{seen:?}"); - let request = &seen[0]; - assert_eq!(request.path, "/v1/chat/completions"); - assert_eq!( - request.header("authorization"), - Some(redrob_engine_client::authorization("s3cret").as_str()) - ); - assert_eq!(request.header("x-redrob-app"), Some("query")); - let session = request.header("x-redrob-session").unwrap(); - assert!(Uuid::parse_str(session).is_ok(), "{session}"); - assert!( - request - .header("content-type") - .is_some_and(|value| value.starts_with("application/json")) - ); - // No console key travels on this path: the daemon's Basic secret is the only credential. - assert!( - !request - .header("authorization") - .unwrap() - .starts_with("Bearer") - ); - - let body: Value = serde_json::from_str(&request.body).unwrap(); - assert_eq!(body["model"], "auto"); - assert_eq!(body["messages"][0]["role"], "user"); - assert_eq!(body["messages"][0]["content"], "show indexes"); - assert_eq!(body["temperature"], 0.2); - assert_eq!(body["maxTokens"], 128); - } - - #[tokio::test] - async fn a_401_against_a_stale_secret_re_reads_the_record_once_and_retries() { - let daemon = FakeDaemon::start("fresh", |_| completion("chat-fresh", "ok")); - // The record in hand names the secret of a daemon that has since restarted. - let (resolver, calls) = scripted(vec![ - Ok(daemon.record_with_secret("stale")), - Ok(daemon.record()), - ]); - let client = AiClient::with_resolver(Arc::new(MemorySecretStore::default()), resolver); - - let response = client.chat(test_chat_request()).await.unwrap(); - assert_eq!(response.id, "chat-fresh"); - assert_eq!(calls.load(Ordering::SeqCst), 2); - let seen = daemon.seen(); - assert_eq!(seen.len(), 2, "{seen:?}"); - assert_eq!( - seen[0].header("authorization"), - Some(redrob_engine_client::authorization("stale").as_str()) - ); - assert_eq!( - seen[1].header("authorization"), - Some(redrob_engine_client::authorization("fresh").as_str()) - ); - - // The fresh record is kept: the next request needs no lookup. - client.chat(test_chat_request()).await.unwrap(); - assert_eq!(calls.load(Ordering::SeqCst), 2); - assert_eq!(daemon.seen().len(), 3); - } - - #[tokio::test] - async fn a_401_that_persists_is_reported_not_retried_forever() { - let daemon = FakeDaemon::start("fresh", |_| completion("never", "no")); - let (resolver, calls) = scripted(vec![Ok(daemon.record_with_secret("stale"))]); - let client = AiClient::with_resolver(Arc::new(MemorySecretStore::default()), resolver); - - assert!(matches!( - client.chat(test_chat_request()).await, - Err(DataError::AiAuthentication) - )); - assert_eq!(calls.load(Ordering::SeqCst), 2); - assert_eq!(daemon.seen().len(), 2); - } - - #[tokio::test] - async fn the_daemon_without_a_console_key_is_told_apart_by_its_code() { - let daemon = FakeDaemon::start("s", |_| { - reply( - 401, - json!({"error": {"message": "로그인이 필요합니다", "code": "engine_not_authenticated"}}) - .to_string(), - ) - }); - let (client, calls) = client_for(&daemon); - - assert!(matches!( - client.chat(test_chat_request()).await, - Err(DataError::AiAuthentication) - )); - // Not a stale secret: no record re-read, no second request. - assert_eq!(calls.load(Ordering::SeqCst), 1); - assert_eq!(daemon.seen().len(), 1); - } - - #[tokio::test] - async fn a_missing_engine_renders_the_install_sentence_with_the_launcher_link() { - let (resolver, calls) = scripted(vec![Err(Unavailable::NotInstalled)]); - let client = AiClient::with_resolver(Arc::new(MemorySecretStore::default()), resolver); - - let error = client.chat(test_chat_request()).await.unwrap_err(); - assert_eq!( - error.to_string(), - "Redrob Code is not installed. Install it with Redrob Launcher. https://github.com/redrob-labs/redrob-launcher/releases/latest" - ); - assert!(matches!(error, DataError::EngineUnavailable(_))); - assert_eq!( - calls.load(Ordering::SeqCst), - 1, - "no retry against a missing engine" - ); - assert_eq!( - client.account_status().await.unwrap_err().to_string(), - format!("{NOT_INSTALLED_MESSAGE} {INSTALL_URL}") - ); - - let (resolver, _) = scripted(vec![Err(Unavailable::StartFailed("port busy".into()))]); - let client = AiClient::with_resolver(Arc::new(MemorySecretStore::default()), resolver); - assert_eq!( - client - .chat(test_chat_request()) - .await - .unwrap_err() - .to_string(), - "The Redrob Code engine could not be started: port busy" - ); - } - - /// The real lookup against a real (empty) environment: `REDROB_CODE_BIN` names a file that does - /// not exist and `REDROB_RUN_DIR` an empty directory. The process environment is shared and the - /// crate forbids `unsafe`, so the check runs in a child test process with that environment. - #[test] - fn not_installed_in_a_real_environment_reports_the_install_sentence() { - let dir = tempfile::tempdir().unwrap(); - let output = std::process::Command::new(std::env::current_exe().unwrap()) - .args([ - "--exact", - "ai::tests::child_without_an_engine_sees_the_install_sentence", - "--ignored", - "--nocapture", - ]) - .env("REDROB_CODE_BIN", dir.path().join("missing-redrob")) - .env("REDROB_RUN_DIR", dir.path()) - .output() - .unwrap(); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!(output.status.success(), "{stdout}\n{stderr}"); - assert!( - stdout.contains("test result: ok. 1 passed"), - "{stdout}\n{stderr}" - ); - } - - #[tokio::test] - #[ignore = "run by not_installed_in_a_real_environment_reports_the_install_sentence"] - async fn child_without_an_engine_sees_the_install_sentence() { - assert!(std::env::var_os("REDROB_CODE_BIN").is_some()); - let client = AiClient::new(Arc::new(MemorySecretStore::default())); - let error = client.chat(test_chat_request()).await.unwrap_err(); - assert_eq!( - error.to_string(), - format!("{NOT_INSTALLED_MESSAGE} {INSTALL_URL}") - ); - } - - #[tokio::test] - async fn sign_in_puts_the_key_to_the_daemon_and_status_reads_the_provider_list() { - let daemon = FakeDaemon::start("s", |request| { - match (request.method.as_str(), request.path.as_str()) { - ("PUT", "/auth/redrob") => { - let body: Value = serde_json::from_str(&request.body).unwrap(); - assert_eq!(body, json!({"type": "api", "key": "rrk_test_key"})); - reply(200, "true") - } - ("GET", "/provider") => reply( - 200, - json!({"all": [{"id": "openai"}, {"id": "redrob", "source": "launcher"}]}) - .to_string(), - ), - other => panic!("unexpected {other:?}"), - } - }); - let (client, _) = client_for(&daemon); - - client.sign_in("rrk_test_key").await.unwrap(); - assert!(matches!( - client.sign_in(" ").await, - Err(DataError::SecretUnavailable) - )); - assert_eq!( - client.account_status().await.unwrap(), - AiKeyStatus { - source: AiKeySource::Launcher - } - ); - let seen = daemon.seen(); - assert_eq!(seen.len(), 2, "{seen:?}"); - assert_eq!( - (seen[0].method.as_str(), seen[0].path.as_str()), - ("PUT", "/auth/redrob") - ); - assert_eq!(seen[0].header("x-redrob-app"), Some("query")); - assert_eq!( - (seen[1].method.as_str(), seen[1].path.as_str()), - ("GET", "/provider") - ); - assert!(!seen[1].body.contains("rrk_test_key")); - } - - #[tokio::test] - async fn the_engine_refusing_a_key_is_reported_without_echoing_it() { - let daemon = FakeDaemon::start("s", |_| { - reply( - 400, - json!({"error": {"message": "rrk_bad echoed"}}).to_string(), - ) - }); - let (client, _) = client_for(&daemon); - let error = client.sign_in("rrk_bad").await.unwrap_err(); - assert!( - matches!(error, DataError::AiRejected(ref message) if !message.contains("rrk_bad")), - "{error}" - ); - } - - #[tokio::test] - async fn a_legacy_keyring_key_is_handed_to_the_engine_once_and_deleted() { - let daemon = FakeDaemon::start("s", |request| match request.path.as_str() { - "/auth/redrob" => reply(200, "true"), - _ => completion("chat-after-migration", "ok"), - }); - let secrets = Arc::new(MemorySecretStore::default()); - secrets.set(AI_SECRET_ID, "rrk_legacy").unwrap(); - let (resolver, _) = scripted(vec![Ok(daemon.record())]); - let client = - AiClient::with_resolver(Arc::clone(&secrets) as Arc, resolver); - - client.chat(test_chat_request()).await.unwrap(); - let seen = daemon.seen(); - assert_eq!(seen.len(), 2, "{seen:?}"); - assert_eq!( - (seen[0].method.as_str(), seen[0].path.as_str()), - ("PUT", "/auth/redrob") - ); - assert_eq!( - serde_json::from_str::(&seen[0].body).unwrap(), - json!({"type": "api", "key": "rrk_legacy"}) - ); - assert_eq!(seen[0].header("x-redrob-app"), Some("query")); - assert_eq!(seen[1].path, "/v1/chat/completions"); - assert!(!seen[1].body.contains("rrk_legacy")); - assert!( - secrets.get(AI_SECRET_ID).unwrap().is_none(), - "the migrated key is gone from the keyring" - ); - - // A second chat migrates nothing: the record is cached and the slot is empty. - client.chat(test_chat_request()).await.unwrap(); - assert_eq!(daemon.seen().len(), 3); - } - - #[test] - fn the_provider_list_tells_signed_in_from_public_and_absent() { - assert_eq!( - AiKeyStatus::from_provider_list(&json!({"all": []})), - AiKeyStatus::signed_out() - ); - assert_eq!( - AiKeyStatus::from_provider_list( - &json!({"all": [{"id": "redrob", "options": {"apiKey": "public"}}]}) - ), - AiKeyStatus::signed_out() - ); - assert_eq!( - AiKeyStatus::from_provider_list( - &json!({"all": [{"id": "redrob", "source": "config"}]}) - ), - AiKeyStatus::signed_out(), - "a config entry without a key is not a sign-in" - ); - assert_eq!( - AiKeyStatus::from_provider_list( - &json!({"all": [{"id": "redrob", "source": "launcher"}]}) - ) - .source, - AiKeySource::Launcher - ); - assert_eq!( - AiKeyStatus::from_provider_list( - &json!({"all": [{"id": "openai"}, {"id": "redrob", "source": "api", "key": "rrk_x"}]}) - ) - .source, - AiKeySource::Engine - ); - assert_eq!( - AiKeyStatus::from_provider_list(&json!({"all": [{"id": "redrob", "key": "rrk_x"}]})) - .source, - AiKeySource::Engine - ); - assert_eq!( - serde_json::to_value(AiKeyStatus::signed_out()).unwrap(), - json!({"source": "none"}) - ); - } - - #[tokio::test] - async fn ai_client_rejects_declared_oversized_response() { - let daemon = FakeDaemon::start("s", |_| Answer { - status: 200, - body: String::new(), - declared_length: Some(2 * 1024 * 1024 + 1), - }); - let (client, _) = client_for(&daemon); - - assert!(matches!( - client.chat(test_chat_request()).await, - Err(DataError::AiInvalidResponse) - )); - assert_eq!(daemon.seen().len(), 1); - } - - #[test] - fn the_daemon_sign_in_code_is_read_from_the_error_code_not_the_message() { - assert!(names_engine_not_authenticated( - br#"{"error":{"message":"localized","code":"engine_not_authenticated"}}"# - )); - assert!(!names_engine_not_authenticated( - br#"{"error":{"message":"engine_not_authenticated"}}"# - )); - assert!(!names_engine_not_authenticated(b"not json")); - } - - #[test] - fn parses_openai_response() { - let response = parse_response( - br#"{ - "id":"chat-1","model":"auto", - "choices":[{"message":{"role":"assistant","content":"Use an index."}}], - "usage":{"prompt_tokens":5,"completion_tokens":4,"total_tokens":9} - }"#, - ) - .unwrap(); - assert_eq!(response.message.content, "Use an index."); - assert_eq!(response.usage.unwrap().total_tokens, 9); - } - - #[test] - fn rejects_malformed_or_empty_responses() { - assert!(matches!( - parse_response(br#"{"id":"x","model":"m","choices":[]}"#), - Err(DataError::AiInvalidResponse) - )); - assert!(matches!( - parse_response(b"not json"), - Err(DataError::AiInvalidResponse) - )); - } - - #[test] - fn classifies_auth_client_and_retryable_errors() { - assert!(matches!( - classify_status(StatusCode::UNAUTHORIZED), - DataError::AiAuthentication - )); - assert!(matches!( - classify_status(StatusCode::BAD_REQUEST), - DataError::AiRejected(_) - )); - assert!(matches!( - classify_status(StatusCode::SERVICE_UNAVAILABLE), - DataError::AiUnavailable - )); - assert!(retryable_status(StatusCode::TOO_MANY_REQUESTS)); - assert!(!retryable_status(StatusCode::BAD_REQUEST)); - } - - #[test] - fn builds_fixed_chat_request_from_minimal_sql_context() { - let input = AiAssistantRequest { - prompt: "write the customer query".to_owned(), - active_query: Some("SELECT id FROM customers;".to_owned()), - database_kind: DatabaseKind::SQLite, - query_language: QueryLanguage::Sql, - }; - - let request = build_assistant_chat_request(&input).unwrap(); - assert_eq!(request.model, "auto"); - assert_eq!(request.temperature, Some(0.2)); - assert_eq!(request.max_tokens, None); - assert_eq!(request.messages.len(), 2); - assert_eq!(request.messages[0].role, AiRole::System); - assert!(request.messages[0].content.contains("SQLite SQL")); - assert!( - request.messages[0] - .content - .contains("only the user's prompt and optional active query") - ); - assert!(request.messages[0].content.contains( - "No database result rows or database credentials are automatically attached" - )); - assert!(!request.messages[0].content.contains(&input.prompt)); - assert!(!request.messages[0].content.contains("SELECT id")); - assert_eq!(request.messages[1].role, AiRole::User); - assert!(request.messages[1].content.contains(&input.prompt)); - assert!( - request.messages[1] - .content - .contains("SELECT id FROM customers;") - ); - } - - #[test] - fn builds_mongo_instruction_and_rejects_mismatched_language() { - let mongo = AiAssistantRequest { - prompt: "find customers".to_owned(), - active_query: None, - database_kind: DatabaseKind::MongoDb, - query_language: QueryLanguage::MongoJson, - }; - let request = build_assistant_chat_request(&mongo).unwrap(); - assert!(request.messages[0].content.contains("fenced mql or json")); - assert!(request.messages[1].content.contains("(none supplied)")); - - let mismatched = AiAssistantRequest { - query_language: QueryLanguage::Sql, - ..mongo - }; - assert!(matches!( - build_assistant_chat_request(&mismatched), - Err(DataError::AiRejected(message)) if message.contains("does not match") - )); - - let sql_server = AiAssistantRequest { - prompt: "write a query".to_owned(), - active_query: None, - database_kind: DatabaseKind::SqlServer, - query_language: QueryLanguage::Sql, - }; - assert!(matches!( - build_assistant_chat_request(&sql_server), - Err(DataError::AiRejected(message)) - if message.contains("no connector or connection profile") - )); - } - - #[test] - fn enforces_context_limits_and_rejects_extra_renderer_fields() { - let oversized_prompt = AiAssistantRequest { - prompt: "x".repeat(MAX_AI_PROMPT_BYTES + 1), - active_query: None, - database_kind: DatabaseKind::PostgreSql, - query_language: QueryLanguage::Sql, - }; - assert!(matches!( - build_assistant_chat_request(&oversized_prompt), - Err(DataError::AiRejected(message)) if message.contains("prompt exceeds") - )); - - let oversized_query = AiAssistantRequest { - prompt: "explain".to_owned(), - active_query: Some("q".repeat(MAX_AI_ACTIVE_QUERY_BYTES + 1)), - database_kind: DatabaseKind::PostgreSql, - query_language: QueryLanguage::Sql, - }; - assert!(matches!( - build_assistant_chat_request(&oversized_query), - Err(DataError::AiRejected(message)) if message.contains("active query exceeds") - )); - - for extra in ["messages", "resultRows", "credentials", "model", "baseUrl"] { - let mut value = serde_json::json!({ - "prompt": "help", - "databaseKind": "postgre_sql", - "queryLanguage": "sql" - }); - value - .as_object_mut() - .unwrap() - .insert(extra.to_owned(), serde_json::json!([])); - assert!(serde_json::from_value::(value).is_err()); - } - } -} diff --git a/crates/redrob-query-core/Cargo.toml b/crates/redrob-query-core/Cargo.toml new file mode 100644 index 0000000..033b226 --- /dev/null +++ b/crates/redrob-query-core/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "redrob-query-core" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +description = "Redrob Query engine: database connectivity, metadata, bounded querying, reviewed edits and the console AI transport" +readme = "../../README.md" +publish = false + +[dependencies] +base64 = { workspace = true } +bigdecimal = { workspace = true } +chrono = { workspace = true } +fs2 = { workspace = true } +futures-util = { workspace = true } +keyring = { workspace = true } +mongodb = { workspace = true } +reqwest = { workspace = true } +secrecy = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sqlx = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +url = { workspace = true } +uuid = { workspace = true } + +[lints] +workspace = true diff --git a/crates/redrob-query-core/src/account.rs b/crates/redrob-query-core/src/account.rs new file mode 100644 index 0000000..5eca982 --- /dev/null +++ b/crates/redrob-query-core/src/account.rs @@ -0,0 +1,209 @@ +//! The Redrob account this app spends: the launcher's shared `account.json`, read on demand. +//! +//! Redrob Launcher signs a computer in once and writes the file described in its +//! `docs/shared-account.md`; every Redrob app reads it instead of running a sign-in of its own. +//! The rules for readers, as that contract states them, are the rules here: +//! +//! 1. Read, never write. Only the launcher writes the shared file. +//! 2. Read it when it is needed, not once at start: the person may sign in or out while the app +//! is open. A missing file, unreadable JSON or an empty `apiKey` all mean "not signed in". +//! 3. Never log, display or copy the key. Only `accountName` and `apiKeyName` are shown. +//! 4. The app's own key wins: `REDROB_API_KEY` in the environment, then an `account.json` of the +//! same shape in this app's own config directory, then the shared file. + +use std::{ + ffi::OsString, + fmt, + path::{Path, PathBuf}, + time::SystemTime, +}; + +use secrecy::{ExposeSecret, SecretString}; +use serde::{Deserialize, Serialize}; + +/// Where a key is spent when the file names no `apiBaseUrl`. +pub const DEFAULT_API_BASE_URL: &str = "https://console.redrob.ai/api/backend/v1"; +/// Replaces the shared directory on every platform (tests, portable setups). +pub const SHARED_DIR_ENV: &str = "REDROB_SHARED_DIR"; +/// A key exported in a terminal wins over every file (rule 4 of the contract). +pub const OWN_KEY_ENV: &str = "REDROB_API_KEY"; +/// Where a person signs in: Redrob Launcher's latest release. +pub const LAUNCHER_URL: &str = "https://github.com/redrob-labs/redrob-launcher/releases/latest"; +const FILE_NAME: &str = "account.json"; +/// The file is a few hundred bytes; anything larger is not the launcher's. +const MAX_FILE_BYTES: u64 = 64 * 1024; + +/// Which file (or variable) the account in use came from. Shown in the status line; never the key. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum AccountSource { + /// `REDROB_API_KEY` in this process's environment. + Environment, + /// This app's own `account.json`, written by the launcher's Connect or by hand. + Own, + /// The launcher's shared `account.json`. + Launcher, +} + +/// A signed-in Redrob account. `Debug` and `Display` never show the key. +#[derive(Clone)] +pub struct SharedAccount { + pub api_key: SecretString, + pub api_key_name: String, + pub account_name: String, + pub api_base_url: String, + pub source: AccountSource, +} + +impl fmt::Debug for SharedAccount { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SharedAccount") + .field("api_key", &"[redacted]") + .field("api_key_name", &self.api_key_name) + .field("account_name", &self.account_name) + .field("api_base_url", &self.api_base_url) + .field("source", &self.source) + .finish() + } +} + +impl SharedAccount { + /// `/chat/completions`, the OpenAI-compatible route the console serves. + #[must_use] + pub fn chat_completions_url(&self) -> String { + format!( + "{}/chat/completions", + self.api_base_url.trim_end_matches('/') + ) + } + + /// What the status line shows: the account, and whose key it is. Never the key. + #[must_use] + pub fn label(&self) -> String { + match (self.account_name.trim(), self.api_key_name.trim()) { + ("", "") => "Signed in".to_owned(), + (account, "") => format!("Signed in as {account}"), + ("", key) => format!("Signed in with {key}"), + (account, key) => format!("{account} ({key})"), + } + } +} + +/// The wire shape of `account.json`. Unknown fields are ignored so the launcher can grow it. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct AccountFile { + #[serde(default)] + api_key: String, + #[serde(default)] + api_key_name: String, + #[serde(default)] + account_name: String, + #[serde(default)] + api_base_url: String, +} + +/// The shared directory for `os` given an environment: `REDROB_SHARED_DIR`, else the platform +/// convention (Linux `$XDG_CONFIG_HOME/redrob-shared`, macOS +/// `~/Library/Application Support/ai.redrob.shared`, Windows `%APPDATA%\Redrob\Shared`). +#[must_use] +pub fn shared_dir_with(os: &str, env: impl Fn(&str) -> Option) -> Option { + if let Some(dir) = env(SHARED_DIR_ENV).filter(|d| !d.is_empty()) { + return Some(PathBuf::from(dir)); + } + let home = env("HOME").filter(|h| !h.is_empty()).map(PathBuf::from); + match os { + "macos" => home.map(|h| h.join("Library/Application Support/ai.redrob.shared")), + "windows" => env("APPDATA") + .filter(|a| !a.is_empty()) + .map(|a| PathBuf::from(a).join("Redrob").join("Shared")), + _ => env("XDG_CONFIG_HOME") + .filter(|x| !x.is_empty()) + .map(PathBuf::from) + .or_else(|| home.map(|h| h.join(".config"))) + .map(|c| c.join("redrob-shared")), + } +} + +/// The shared `account.json` on this machine, or `None` when no home is known. +#[must_use] +pub fn shared_account_path() -> Option { + shared_dir_with(std::env::consts::OS, |key| std::env::var_os(key)).map(|d| d.join(FILE_NAME)) +} + +/// Reads one `account.json`. `None` for a missing file, unreadable JSON or an empty key. +#[must_use] +pub fn read_account_file(path: &Path, source: AccountSource) -> Option { + let meta = std::fs::metadata(path).ok()?; + if !meta.is_file() || meta.len() > MAX_FILE_BYTES { + return None; + } + let bytes = std::fs::read(path).ok()?; + let file: AccountFile = serde_json::from_slice(&bytes).ok()?; + let api_key = file.api_key.trim(); + if api_key.is_empty() { + return None; + } + let api_base_url = match file.api_base_url.trim() { + "" => DEFAULT_API_BASE_URL.to_owned(), + url => url.trim_end_matches('/').to_owned(), + }; + Some(SharedAccount { + api_key: SecretString::from(api_key.to_owned()), + api_key_name: file.api_key_name.trim().to_owned(), + account_name: file.account_name.trim().to_owned(), + api_base_url, + source, + }) +} + +/// The account in force right now, by the contract's precedence: `REDROB_API_KEY`, then +/// `own_file` (this app's own `account.json`, when it has one), then the launcher's shared file. +#[must_use] +pub fn current_account(own_file: Option<&Path>) -> Option { + if let Some(key) = std::env::var(OWN_KEY_ENV) + .ok() + .map(|k| k.trim().to_owned()) + .filter(|k| !k.is_empty()) + { + let api_base_url = std::env::var("REDROB_CONSOLE_API") + .ok() + .map(|u| u.trim().trim_end_matches('/').to_owned()) + .filter(|u| !u.is_empty()) + .unwrap_or_else(|| DEFAULT_API_BASE_URL.to_owned()); + return Some(SharedAccount { + api_key: SecretString::from(key), + api_key_name: String::new(), + account_name: String::new(), + api_base_url, + source: AccountSource::Environment, + }); + } + if let Some(account) = own_file.and_then(|p| read_account_file(p, AccountSource::Own)) { + return Some(account); + } + shared_account_path().and_then(|p| read_account_file(&p, AccountSource::Launcher)) +} + +/// A cheap fingerprint of the account files, so a caller can notice a sign-in or sign-out +/// without parsing: the modification time and size of each file that exists. +#[must_use] +pub fn account_files_stamp(own_file: Option<&Path>) -> Vec<(PathBuf, Option<(SystemTime, u64)>)> { + own_file + .map(Path::to_path_buf) + .into_iter() + .chain(shared_account_path()) + .map(|path| { + let stamp = std::fs::metadata(&path) + .ok() + .and_then(|m| m.modified().ok().map(|t| (t, m.len()))); + (path, stamp) + }) + .collect() +} + +/// `true` when the key is non-empty; a convenience for callers that only need the yes/no. +#[must_use] +pub fn has_key(account: Option<&SharedAccount>) -> bool { + account.is_some_and(|a| !a.api_key.expose_secret().is_empty()) +} diff --git a/crates/redrob-query-core/src/ai.rs b/crates/redrob-query-core/src/ai.rs new file mode 100644 index 0000000..abbaf75 --- /dev/null +++ b/crates/redrob-query-core/src/ai.rs @@ -0,0 +1,953 @@ +//! Redrob chat-completion client, straight to the Redrob console with the shared account. +//! +//! The launcher signs the computer in once and writes `account.json` (`crate::account`); this +//! client reads that account when a request is made and posts to `{apiBaseUrl}/chat/completions` +//! with `Authorization: Bearer ` and model `auto`. There is no daemon in between any more: +//! the Redrob Code engine path answered `auto` with "model not available" and swallowed the +//! console's error body, so a person saw neither the real reason nor a way to sign in. +//! +//! The console's own `error.message` is surfaced verbatim (bounded, with the key never echoed by +//! construction: it travels only in the header). A missing account is `DataError::AiAuthentication`, +//! which the shell renders as "sign in with Redrob Launcher". + +use std::{ + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; + +use futures_util::StreamExt; +use reqwest::{Client, StatusCode, header::HeaderValue}; +use secrecy::ExposeSecret; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tokio::time::sleep; +use uuid::Uuid; + +use crate::{ + AiAssistantRequest, AiChatRequest, AiChatResponse, AiMessage, AiRole, AiUsage, DataError, + DatabaseKind, QueryLanguage, Result, + account::{self, AccountSource, SharedAccount}, +}; + +/// Where a person gets Redrob Launcher, which is where signing in happens. +pub const INSTALL_URL: &str = account::LAUNCHER_URL; +/// The sentence shown when no account is signed in. The link is [`INSTALL_URL`]. +pub const NOT_SIGNED_IN_MESSAGE: &str = + "Not signed in. Sign in with Redrob Launcher and this app will use that account."; +/// The keyring slot an earlier version of this app saved its own console key in. Profile storage +/// still reserves the id; nothing reads it any more. +pub const AI_SECRET_ID: Uuid = Uuid::from_u128(0x7265_6472_6f62_4169_0000_0000_0000_0001); +pub const MAX_AI_PROMPT_BYTES: usize = 8 * 1024; +pub const MAX_AI_ACTIVE_QUERY_BYTES: usize = 64 * 1024; +const MAX_ATTEMPTS: usize = 3; +const AI_TEMPERATURE: f32 = 0.2; +const MAX_ERROR_BODY_BYTES: usize = 64 * 1024; +const MAX_RESPONSE_BYTES: usize = 2 * 1024 * 1024; +/// How much of the console's `error.message` is shown; longer is not a message, it is a dump. +const MAX_ERROR_MESSAGE_CHARS: usize = 400; + +/// Builds the provider request from the narrow assistant contract. +pub fn build_assistant_chat_request(input: &AiAssistantRequest) -> Result { + if input.prompt.trim().is_empty() { + return Err(DataError::AiRejected("prompt cannot be empty".to_owned())); + } + if input.prompt.len() > MAX_AI_PROMPT_BYTES { + return Err(DataError::AiRejected(format!( + "prompt exceeds {MAX_AI_PROMPT_BYTES} bytes" + ))); + } + if input + .active_query + .as_ref() + .is_some_and(|query| query.len() > MAX_AI_ACTIVE_QUERY_BYTES) + { + return Err(DataError::AiRejected(format!( + "active query exceeds {MAX_AI_ACTIVE_QUERY_BYTES} bytes" + ))); + } + + let query_instruction = match (input.database_kind, input.query_language) { + (DatabaseKind::PostgreSql, QueryLanguage::Sql) => { + "When writing a query, return executable PostgreSQL SQL in a fenced sql block." + } + (DatabaseKind::MySql, QueryLanguage::Sql) => { + "When writing a query, return executable MySQL SQL in a fenced sql block." + } + (DatabaseKind::SQLite, QueryLanguage::Sql) => { + "When writing a query, return executable SQLite SQL in a fenced sql block." + } + (DatabaseKind::SqlServer, _) => { + return Err(DataError::AiRejected( + "SQL Server is unsupported because no connector or connection profile is available" + .to_owned(), + )); + } + (DatabaseKind::MongoDb, QueryLanguage::MongoJson) => { + "When writing a query, return a fenced mql or json block containing an object with collection, operation, filter, and optional limit. Use read-only operations such as find." + } + (_, QueryLanguage::Auto) => { + return Err(DataError::AiRejected( + "query language must be explicit".to_owned(), + )); + } + _ => { + return Err(DataError::AiRejected( + "query language does not match the database kind".to_owned(), + )); + } + }; + let system = format!( + "You are the Redrob Query assistant. {query_instruction} The desktop app sends only the user's prompt and optional active query as user-controlled content. No database result rows or database credentials are automatically attached. Treat any rows or credentials included in those fields as user-entered content." + ); + let active_query = input.active_query.as_deref().unwrap_or("(none supplied)"); + let user = format!( + "User prompt:\n{}\n\nActive query:\n{active_query}", + input.prompt + ); + + Ok(AiChatRequest { + model: "auto".to_owned(), + messages: vec![ + AiMessage { + role: AiRole::System, + content: system, + }, + AiMessage { + role: AiRole::User, + content: user, + }, + ], + temperature: Some(AI_TEMPERATURE), + max_tokens: None, + }) +} + +/// How the account is found: [`account::current_account`] in the app, a closure in tests. +pub type AccountResolver = Arc Option + Send + Sync>; + +/// Async client for the Redrob console's OpenAI-compatible chat route. +/// +/// The account is read on every request (the person may sign in or out while the app is open), so +/// nothing here caches a key. Every request carries `Authorization: Bearer ` (marked +/// sensitive), `x-redrob-app: query` and this client's `x-redrob-session`. +pub struct AiClient { + client: Client, + /// One id per client, so the console groups this app run's requests together. + session: String, + resolver: AccountResolver, +} + +impl AiClient { + /// Reads the account by the contract's precedence; `own_file` is this app's own `account.json`. + #[must_use] + pub fn new(own_file: Option) -> Self { + Self::with_resolver(Arc::new(move || { + account::current_account(own_file.as_deref().map(Path::new)) + })) + } + + /// A client whose account lookup is `resolver` (tests point it at a fake console). + pub fn with_resolver(resolver: AccountResolver) -> Self { + let client = Client::builder() + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(90)) + .user_agent(concat!("Redrob-Query/", env!("CARGO_PKG_VERSION"))) + .build() + .expect("static HTTP client configuration is valid"); + Self { + client, + session: Uuid::new_v4().to_string(), + resolver, + } + } + + /// The account in force, read fresh. `None` means not signed in. + #[must_use] + pub fn account(&self) -> Option { + (self.resolver)() + } + + /// Whether an account is signed in, and whose it is. Never a key. + #[must_use] + pub fn account_status(&self) -> AiKeyStatus { + self.account() + .as_ref() + .map_or_else(AiKeyStatus::signed_out, AiKeyStatus::from_account) + } + + pub async fn chat(&self, request: AiChatRequest) -> Result { + validate_request(&request)?; + let account = self.account().ok_or(DataError::AiAuthentication)?; + let url = account.chat_completions_url(); + let wire = WireRequest::from(&request); + let mut last_error = DataError::AiUnavailable; + + for attempt in 0..MAX_ATTEMPTS { + let response = self + .authorised(&account, self.client.post(&url).json(&wire)) + .send() + .await; + match response { + Ok(response) if response.status().is_success() => { + let bytes = bounded_response(response, MAX_RESPONSE_BYTES).await?; + return parse_response(&bytes); + } + Ok(response) => { + let status = response.status(); + let body = bounded_response(response, MAX_ERROR_BODY_BYTES) + .await + .unwrap_or_default(); + last_error = classify_failure(status, &body); + if !retryable_status(status) || attempt + 1 == MAX_ATTEMPTS { + return Err(last_error); + } + } + Err(error) => { + last_error = transport_error(&error); + if attempt + 1 == MAX_ATTEMPTS { + return Err(last_error); + } + } + } + sleep(Duration::from_millis(200 * (1_u64 << attempt))).await; + } + Err(last_error) + } + + fn authorised( + &self, + account: &SharedAccount, + builder: reqwest::RequestBuilder, + ) -> reqwest::RequestBuilder { + let mut bearer = + HeaderValue::from_str(&format!("Bearer {}", account.api_key.expose_secret())) + .unwrap_or_else(|_| HeaderValue::from_static("Bearer")); + bearer.set_sensitive(true); + builder + .header(reqwest::header::AUTHORIZATION, bearer) + .header("x-redrob-app", "query") + .header("x-redrob-session", &self.session) + } +} + +fn transport_error(error: &reqwest::Error) -> DataError { + if error.is_timeout() { + DataError::Timeout(Duration::from_secs(90)) + } else { + DataError::AiUnavailable + } +} + +/// The console's `error.message`, when its body carries one, trimmed to a sentence or two. +fn error_message(body: &[u8]) -> Option { + let value: Value = serde_json::from_slice(body).ok()?; + let message = value + .pointer("/error/message") + .or_else(|| value.get("message")) + .and_then(Value::as_str)? + .trim(); + if message.is_empty() { + return None; + } + let mut text: String = message.chars().take(MAX_ERROR_MESSAGE_CHARS).collect(); + if text.len() < message.len() { + text.push('…'); + } + Some(text) +} + +/// Whether an account is signed in, and whose it is. Never a key. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AiKeyStatus { + pub source: AiKeySource, + /// `accountName` and `apiKeyName` as the status line shows them; empty when signed out. + #[serde(default)] + pub label: String, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum AiKeySource { + /// `REDROB_API_KEY` in the environment, or this app's own `account.json`. + Own, + /// Redrob Launcher's shared sign-in. Signing out happens there. + Launcher, + /// No account is signed in. + None, +} + +impl Default for AiKeyStatus { + fn default() -> Self { + Self::signed_out() + } +} + +impl AiKeyStatus { + #[must_use] + pub fn signed_out() -> Self { + Self { + source: AiKeySource::None, + label: String::new(), + } + } + + #[must_use] + pub fn from_account(account: &SharedAccount) -> Self { + Self { + source: match account.source { + AccountSource::Launcher => AiKeySource::Launcher, + AccountSource::Environment | AccountSource::Own => AiKeySource::Own, + }, + label: account.label(), + } + } + + #[must_use] + pub fn signed_in(&self) -> bool { + self.source != AiKeySource::None + } +} + +async fn bounded_response(response: reqwest::Response, limit: usize) -> Result> { + if response + .content_length() + .is_some_and(|length| length > u64::try_from(limit).unwrap_or(u64::MAX)) + { + return Err(DataError::AiInvalidResponse); + } + let mut body = Vec::with_capacity(response.content_length().map_or(0, |length| { + usize::try_from(length).unwrap_or(limit).min(limit) + })); + let mut stream = response.bytes_stream(); + while let Some(chunk) = stream.next().await { + let chunk = chunk.map_err(|_| DataError::AiInvalidResponse)?; + if body.len().saturating_add(chunk.len()) > limit { + return Err(DataError::AiInvalidResponse); + } + body.extend_from_slice(&chunk); + } + Ok(body) +} + +fn validate_request(request: &AiChatRequest) -> Result<()> { + if request.messages.is_empty() || request.messages.len() > 100 { + return Err(DataError::AiRejected( + "provide between 1 and 100 messages".to_owned(), + )); + } + if request.model.trim().is_empty() || request.model.len() > 100 { + return Err(DataError::AiRejected("model is invalid".to_owned())); + } + if request + .messages + .iter() + .any(|message| message.content.len() > 100_000) + { + return Err(DataError::AiRejected("message is too large".to_owned())); + } + if request + .temperature + .is_some_and(|value| !(0.0..=2.0).contains(&value)) + { + return Err(DataError::AiRejected( + "temperature must be between 0 and 2".to_owned(), + )); + } + Ok(()) +} + +fn retryable_status(status: StatusCode) -> bool { + status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error() +} + +/// The console's own sentence when it gave one; the status class otherwise. +fn classify_failure(status: StatusCode, body: &[u8]) -> DataError { + match (status, error_message(body)) { + (StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN, _) => DataError::AiAuthentication, + (StatusCode::TOO_MANY_REQUESTS, _) => DataError::AiUnavailable, + (value, Some(message)) if value.is_server_error() => { + DataError::AiRejected(format!("the console answered HTTP {status}: {message}")) + } + (value, None) if value.is_server_error() => DataError::AiUnavailable, + (_, Some(message)) => DataError::AiRejected(message), + (_, None) => DataError::AiRejected(format!("service returned HTTP {status}")), + } +} + +/// The OpenAI-compatible request body: `snake_case` on the wire, whatever the models use in-app. +#[derive(Serialize)] +struct WireRequest<'a> { + model: &'a str, + messages: &'a [AiMessage], + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + max_tokens: Option, +} + +impl<'a> From<&'a AiChatRequest> for WireRequest<'a> { + fn from(request: &'a AiChatRequest) -> Self { + Self { + model: &request.model, + messages: &request.messages, + temperature: request.temperature, + max_tokens: request.max_tokens, + } + } +} + +#[derive(Deserialize)] +struct WireResponse { + #[serde(default)] + id: String, + #[serde(default)] + model: String, + choices: Vec, + usage: Option, +} + +#[derive(Deserialize)] +struct WireChoice { + message: WireMessage, +} + +#[derive(Deserialize)] +struct WireMessage { + role: String, + /// Some providers answer `null` content alongside tool calls; that is not an answer here. + #[serde(default)] + content: Option, +} + +#[derive(Deserialize)] +struct WireUsage { + #[serde(rename = "prompt_tokens", default)] + prompt: u32, + #[serde(rename = "completion_tokens", default)] + completion: u32, + #[serde(rename = "total_tokens", default)] + total: u32, +} + +fn parse_response(bytes: &[u8]) -> Result { + let wire: WireResponse = + serde_json::from_slice(bytes).map_err(|_| DataError::AiInvalidResponse)?; + let choice = wire + .choices + .into_iter() + .next() + .ok_or(DataError::AiInvalidResponse)?; + let content = choice.message.content.unwrap_or_default(); + if content.is_empty() { + return Err(DataError::AiInvalidResponse); + } + let role = match choice.message.role.as_str() { + "assistant" => AiRole::Assistant, + "system" => AiRole::System, + "user" => AiRole::User, + _ => return Err(DataError::AiInvalidResponse), + }; + Ok(AiChatResponse { + id: wire.id, + model: wire.model, + message: AiMessage { role, content }, + usage: wire.usage.map(|usage| AiUsage { + prompt_tokens: usage.prompt, + completion_tokens: usage.completion, + total_tokens: usage.total, + }), + }) +} + +#[cfg(test)] +mod tests { + use std::{ + collections::HashMap, + io::{BufRead, BufReader, Read, Write}, + net::{TcpListener, TcpStream}, + sync::{Arc, Mutex}, + thread, + time::Duration as StdDuration, + }; + + use secrecy::SecretString; + use serde_json::json; + + use super::*; + + /// One request as the fake console saw it. + #[derive(Clone, Debug)] + struct Seen { + method: String, + path: String, + headers: HashMap, + body: String, + } + + impl Seen { + fn header(&self, name: &str) -> Option<&str> { + self.headers + .get(&name.to_ascii_lowercase()) + .map(String::as_str) + } + } + + /// What the fake console answers with. `declared_length` lies in `Content-Length` when set. + struct Answer { + status: u16, + body: String, + declared_length: Option, + } + + fn reply(status: u16, body: impl Into) -> Answer { + Answer { + status, + body: body.into(), + declared_length: None, + } + } + + fn completion(id: &str, content: &str) -> Answer { + reply( + 200, + json!({ + "id": id, "model": "mock-model", + "choices": [{"message": {"role": "assistant", "content": content}}], + "usage": {"prompt_tokens": 4, "completion_tokens": 3, "total_tokens": 7} + }) + .to_string(), + ) + } + + /// A tiny stand-in for the console on 127.0.0.1: the wrong bearer is 401 everywhere, as the + /// real console does; everything else is up to `answer`. + struct FakeConsole { + base_url: String, + key: String, + seen: Arc>>, + } + + impl FakeConsole { + fn start(key: &str, answer: impl Fn(&Seen) -> Answer + Send + Sync + 'static) -> Self { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + let seen = Arc::new(Mutex::new(Vec::new())); + let expected = format!("Bearer {key}"); + let log = Arc::clone(&seen); + thread::spawn(move || { + for stream in listener.incoming() { + let Ok(mut stream) = stream else { break }; + let Some(request) = read_request(&mut stream) else { + continue; + }; + log.lock().unwrap().push(request.clone()); + let answer = if request.header("authorization") == Some(expected.as_str()) { + answer(&request) + } else { + reply( + 401, + json!({"error": {"message": "invalid api key"}}).to_string(), + ) + }; + let length = answer.declared_length.unwrap_or(answer.body.len()); + let _ = write!( + stream, + "HTTP/1.1 {} X\r\nContent-Type: application/json\r\nContent-Length: {length}\r\nConnection: close\r\n\r\n{}", + answer.status, answer.body + ); + let _ = stream.flush(); + } + }); + Self { + base_url: format!("http://127.0.0.1:{port}/api/backend/v1"), + key: key.into(), + seen, + } + } + + fn account(&self) -> SharedAccount { + self.account_with_key(&self.key) + } + + fn account_with_key(&self, key: &str) -> SharedAccount { + SharedAccount { + api_key: SecretString::from(key.to_owned()), + api_key_name: "Redrob Launcher".to_owned(), + account_name: "Acme".to_owned(), + api_base_url: self.base_url.clone(), + source: AccountSource::Launcher, + } + } + + fn seen(&self) -> Vec { + self.seen.lock().unwrap().clone() + } + } + + fn read_request(stream: &mut TcpStream) -> Option { + stream + .set_read_timeout(Some(StdDuration::from_secs(5))) + .ok()?; + let mut reader = BufReader::new(stream); + let mut line = String::new(); + reader.read_line(&mut line).ok()?; + let mut parts = line.split_whitespace(); + let method = parts.next()?.to_owned(); + let path = parts.next()?.to_owned(); + let mut headers = HashMap::new(); + loop { + let mut header = String::new(); + reader.read_line(&mut header).ok()?; + let header = header.trim_end(); + if header.is_empty() { + break; + } + let (name, value) = header.split_once(':')?; + headers.insert(name.trim().to_ascii_lowercase(), value.trim().to_owned()); + } + let length: usize = headers + .get("content-length") + .and_then(|value| value.parse().ok()) + .unwrap_or(0); + let mut body = vec![0; length]; + reader.read_exact(&mut body).ok()?; + Some(Seen { + method, + path, + headers, + body: String::from_utf8_lossy(&body).into_owned(), + }) + } + + fn client_with(account: Option) -> AiClient { + AiClient::with_resolver(Arc::new(move || account.clone())) + } + + fn test_chat_request() -> AiChatRequest { + AiChatRequest { + model: "auto".to_owned(), + messages: vec![AiMessage { + role: AiRole::User, + content: "show indexes".to_owned(), + }], + temperature: Some(0.2), + max_tokens: Some(128), + } + } + + #[tokio::test] + async fn chat_hits_the_console_route_with_the_bearer_app_and_session() { + let console = FakeConsole::start("rk_s3cret", |request| { + assert_eq!(request.method, "POST"); + completion("chat-1", "Use an index.") + }); + let client = client_with(Some(console.account())); + + let response = client.chat(test_chat_request()).await.unwrap(); + assert_eq!(response.id, "chat-1"); + assert_eq!(response.model, "mock-model"); + assert_eq!(response.message.content, "Use an index."); + assert_eq!(response.usage.unwrap().total_tokens, 7); + + let seen = console.seen(); + assert_eq!(seen.len(), 1, "{seen:?}"); + let request = &seen[0]; + assert_eq!(request.path, "/api/backend/v1/chat/completions"); + assert_eq!(request.header("authorization"), Some("Bearer rk_s3cret")); + assert_eq!(request.header("x-redrob-app"), Some("query")); + let session = request.header("x-redrob-session").unwrap(); + assert!(Uuid::parse_str(session).is_ok(), "{session}"); + assert!( + request + .header("content-type") + .is_some_and(|value| value.starts_with("application/json")) + ); + + // The OpenAI-compatible body: snake_case, the key nowhere in it. + let body: Value = serde_json::from_str(&request.body).unwrap(); + assert_eq!(body["model"], "auto"); + assert_eq!(body["messages"][0]["role"], "user"); + assert_eq!(body["messages"][0]["content"], "show indexes"); + assert_eq!(body["temperature"], 0.2); + assert_eq!(body["max_tokens"], 128); + assert!(body.get("maxTokens").is_none()); + assert!(!request.body.contains("rk_s3cret")); + } + + #[tokio::test] + async fn the_account_is_read_on_every_request_so_a_sign_in_counts_at_once() { + let console = FakeConsole::start("rk_fresh", |_| completion("chat-fresh", "ok")); + let slot: Arc>> = Arc::new(Mutex::new(None)); + let read = Arc::clone(&slot); + let client = AiClient::with_resolver(Arc::new(move || read.lock().unwrap().clone())); + + assert!(matches!( + client.chat(test_chat_request()).await, + Err(DataError::AiAuthentication) + )); + assert_eq!(client.account_status(), AiKeyStatus::signed_out()); + assert!(console.seen().is_empty(), "no request without an account"); + + *slot.lock().unwrap() = Some(console.account()); + let response = client.chat(test_chat_request()).await.unwrap(); + assert_eq!(response.id, "chat-fresh"); + let status = client.account_status(); + assert_eq!(status.source, AiKeySource::Launcher); + assert_eq!(status.label, "Acme (Redrob Launcher)"); + assert!(status.signed_in()); + } + + #[tokio::test] + async fn a_401_is_not_signed_in_and_is_not_retried() { + let console = FakeConsole::start("rk_fresh", |_| completion("never", "no")); + let client = client_with(Some(console.account_with_key("rk_stale"))); + + assert!(matches!( + client.chat(test_chat_request()).await, + Err(DataError::AiAuthentication) + )); + assert_eq!(console.seen().len(), 1); + } + + #[tokio::test] + async fn the_console_error_message_is_surfaced_without_the_key() { + let console = FakeConsole::start("rk_s", |_| { + reply( + 400, + json!({"error": {"message": "model auto is not available for this workspace", "type": "invalid_request_error"}}) + .to_string(), + ) + }); + let client = client_with(Some(console.account())); + + let error = client.chat(test_chat_request()).await.unwrap_err(); + assert_eq!( + error.to_string(), + "AI request was rejected: model auto is not available for this workspace" + ); + assert!(!error.to_string().contains("rk_s")); + assert_eq!(console.seen().len(), 1, "a 400 is not retried"); + } + + #[tokio::test] + async fn a_server_error_is_retried_then_reported_with_its_message() { + let console = FakeConsole::start("rk_s", |_| { + reply( + 503, + json!({"error": {"message": "upstream provider timed out"}}).to_string(), + ) + }); + let client = client_with(Some(console.account())); + + let error = client.chat(test_chat_request()).await.unwrap_err(); + assert!( + matches!(error, DataError::AiRejected(ref message) if message.contains("HTTP 503") && message.contains("upstream provider timed out")), + "{error}" + ); + assert_eq!(console.seen().len(), MAX_ATTEMPTS); + } + + #[test] + fn a_missing_account_renders_the_sign_in_sentence_with_the_launcher_link() { + let client = client_with(None); + assert_eq!(client.account_status(), AiKeyStatus::signed_out()); + assert!(!client.account_status().signed_in()); + assert!(NOT_SIGNED_IN_MESSAGE.contains("Redrob Launcher")); + assert!(INSTALL_URL.starts_with("https://github.com/redrob-labs/redrob-launcher")); + assert_eq!( + serde_json::to_value(AiKeyStatus::signed_out()).unwrap(), + json!({"source": "none", "label": ""}) + ); + } + + #[tokio::test] + async fn ai_client_rejects_declared_oversized_response() { + let console = FakeConsole::start("rk_s", |_| Answer { + status: 200, + body: String::new(), + declared_length: Some(MAX_RESPONSE_BYTES + 1), + }); + let client = client_with(Some(console.account())); + + assert!(matches!( + client.chat(test_chat_request()).await, + Err(DataError::AiInvalidResponse) + )); + assert_eq!(console.seen().len(), 1); + } + + #[test] + fn the_error_message_is_read_from_the_body_and_bounded() { + assert_eq!( + error_message(br#"{"error":{"message":"localized","code":"x"}}"#).as_deref(), + Some("localized") + ); + assert_eq!( + error_message(br#"{"message":"flat"}"#).as_deref(), + Some("flat") + ); + assert_eq!(error_message(b"not json"), None); + assert_eq!(error_message(br#"{"error":{"message":" "}}"#), None); + let long = format!(r#"{{"error":{{"message":"{}"}}}}"#, "x".repeat(1000)); + let shown = error_message(long.as_bytes()).unwrap(); + assert_eq!(shown.chars().count(), MAX_ERROR_MESSAGE_CHARS + 1); + assert!(shown.ends_with('…')); + } + + #[test] + fn parses_openai_response() { + let response = parse_response( + br#"{ + "id":"chat-1","model":"auto", + "choices":[{"message":{"role":"assistant","content":"Use an index."}}], + "usage":{"prompt_tokens":5,"completion_tokens":4,"total_tokens":9} + }"#, + ) + .unwrap(); + assert_eq!(response.message.content, "Use an index."); + assert_eq!(response.usage.unwrap().total_tokens, 9); + } + + #[test] + fn rejects_malformed_or_empty_responses() { + assert!(matches!( + parse_response(br#"{"id":"x","model":"m","choices":[]}"#), + Err(DataError::AiInvalidResponse) + )); + assert!(matches!( + parse_response(br#"{"choices":[{"message":{"role":"assistant","content":null}}]}"#), + Err(DataError::AiInvalidResponse) + )); + assert!(matches!( + parse_response(b"not json"), + Err(DataError::AiInvalidResponse) + )); + } + + #[test] + fn classifies_auth_client_and_retryable_errors() { + assert!(matches!( + classify_failure(StatusCode::UNAUTHORIZED, b""), + DataError::AiAuthentication + )); + assert!(matches!( + classify_failure(StatusCode::BAD_REQUEST, b""), + DataError::AiRejected(_) + )); + assert!(matches!( + classify_failure(StatusCode::SERVICE_UNAVAILABLE, b""), + DataError::AiUnavailable + )); + assert!(retryable_status(StatusCode::TOO_MANY_REQUESTS)); + assert!(!retryable_status(StatusCode::BAD_REQUEST)); + } + + #[test] + fn builds_fixed_chat_request_from_minimal_sql_context() { + let input = AiAssistantRequest { + prompt: "write the customer query".to_owned(), + active_query: Some("SELECT id FROM customers;".to_owned()), + database_kind: DatabaseKind::SQLite, + query_language: QueryLanguage::Sql, + }; + + let request = build_assistant_chat_request(&input).unwrap(); + assert_eq!(request.model, "auto"); + assert_eq!(request.temperature, Some(0.2)); + assert_eq!(request.max_tokens, None); + assert_eq!(request.messages.len(), 2); + assert_eq!(request.messages[0].role, AiRole::System); + assert!(request.messages[0].content.contains("SQLite SQL")); + assert!( + request.messages[0] + .content + .contains("only the user's prompt and optional active query") + ); + assert!(request.messages[0].content.contains( + "No database result rows or database credentials are automatically attached" + )); + assert!(!request.messages[0].content.contains(&input.prompt)); + assert!(!request.messages[0].content.contains("SELECT id")); + assert_eq!(request.messages[1].role, AiRole::User); + assert!(request.messages[1].content.contains(&input.prompt)); + assert!( + request.messages[1] + .content + .contains("SELECT id FROM customers;") + ); + } + + #[test] + fn builds_mongo_instruction_and_rejects_mismatched_language() { + let mongo = AiAssistantRequest { + prompt: "find customers".to_owned(), + active_query: None, + database_kind: DatabaseKind::MongoDb, + query_language: QueryLanguage::MongoJson, + }; + let request = build_assistant_chat_request(&mongo).unwrap(); + assert!(request.messages[0].content.contains("fenced mql or json")); + assert!(request.messages[1].content.contains("(none supplied)")); + + let mismatched = AiAssistantRequest { + query_language: QueryLanguage::Sql, + ..mongo + }; + assert!(matches!( + build_assistant_chat_request(&mismatched), + Err(DataError::AiRejected(message)) if message.contains("does not match") + )); + + let sql_server = AiAssistantRequest { + prompt: "write a query".to_owned(), + active_query: None, + database_kind: DatabaseKind::SqlServer, + query_language: QueryLanguage::Sql, + }; + assert!(matches!( + build_assistant_chat_request(&sql_server), + Err(DataError::AiRejected(message)) + if message.contains("no connector or connection profile") + )); + } + + #[test] + fn enforces_context_limits_and_rejects_extra_renderer_fields() { + let oversized_prompt = AiAssistantRequest { + prompt: "x".repeat(MAX_AI_PROMPT_BYTES + 1), + active_query: None, + database_kind: DatabaseKind::PostgreSql, + query_language: QueryLanguage::Sql, + }; + assert!(matches!( + build_assistant_chat_request(&oversized_prompt), + Err(DataError::AiRejected(message)) if message.contains("prompt exceeds") + )); + + let oversized_query = AiAssistantRequest { + prompt: "explain".to_owned(), + active_query: Some("q".repeat(MAX_AI_ACTIVE_QUERY_BYTES + 1)), + database_kind: DatabaseKind::PostgreSql, + query_language: QueryLanguage::Sql, + }; + assert!(matches!( + build_assistant_chat_request(&oversized_query), + Err(DataError::AiRejected(message)) if message.contains("active query exceeds") + )); + + for extra in ["messages", "resultRows", "credentials", "model", "baseUrl"] { + let mut value = serde_json::json!({ + "prompt": "help", + "databaseKind": "postgre_sql", + "queryLanguage": "sql" + }); + value + .as_object_mut() + .unwrap() + .insert(extra.to_owned(), serde_json::json!([])); + assert!(serde_json::from_value::(value).is_err()); + } + } +} diff --git a/crates/redrob-core/src/cell_edits.rs b/crates/redrob-query-core/src/cell_edits.rs similarity index 100% rename from crates/redrob-core/src/cell_edits.rs rename to crates/redrob-query-core/src/cell_edits.rs diff --git a/crates/redrob-core/src/connection.rs b/crates/redrob-query-core/src/connection.rs similarity index 99% rename from crates/redrob-core/src/connection.rs rename to crates/redrob-query-core/src/connection.rs index 64265e1..ed59429 100644 --- a/crates/redrob-core/src/connection.rs +++ b/crates/redrob-query-core/src/connection.rs @@ -35,6 +35,7 @@ use crate::{ MetadataNode, MetadataRequest, MutationPlan, MutationResult, MutationRisk, QueryClassification, QueryColumn, QueryLanguage, QueryRequest, QueryResultPage, QueryStats, Result, ai::{AI_SECRET_ID, AiClient, AiKeyStatus, build_assistant_chat_request}, + keys::{self, TableKeys}, profile_store::{ JournalOperation, JournalPhase, PersistencePhase, ProfileJournal, ProfileRepository, RepositoryOpenError, deserialize_journal, profiles_from_map, reconcile, remove_journal, @@ -127,7 +128,13 @@ impl DataService { /// produces a Demo-only, fail-closed service. #[must_use] pub fn persistent(path: impl Into) -> Self { - Self::build_persistent(Arc::new(KeyringSecretStore), path.into()) + let path = path.into(); + // This app's own `account.json`, beside the profile file: the launcher's Connect (or a + // person) may write one; it wins over the shared file (`account` module, rule 4). + let own_account = path.parent().map(|dir| dir.join("account.json")); + let mut service = Self::build_persistent(Arc::new(KeyringSecretStore), path); + service.ai = AiClient::new(own_account); + service } /// Creates an ephemeral service with an injectable secret store for tests. @@ -186,7 +193,7 @@ impl DataService { profile_load_warnings, recovery_blocked, persistent_storage_unavailable, - ai: AiClient::new(Arc::clone(&secrets)), + ai: AiClient::new(None), secrets, } } @@ -743,12 +750,6 @@ impl DataService { } } - /// Hands a Redrob console key to the shared Redrob Code engine, which keeps it. Nothing is - /// written to this app's own storage. - pub async fn ai_sign_in(&self, secret: &str) -> Result<()> { - self.ai.sign_in(secret).await - } - async fn connection_test_secret( &self, profile: &ConnectionProfile, @@ -1001,6 +1002,28 @@ impl DataService { } } + /// The primary key, foreign keys and indexes of one table (or the indexes of one `MongoDB` + /// collection), for the structure panel. Names are bound values where the dialect allows it. + pub async fn table_keys( + &self, + connection_id: Uuid, + schema: Option, + table: String, + ) -> Result { + match self.ensure_connected(connection_id).await? { + ActiveConnection::PostgreSql(pool) => { + keys::postgres_table_keys(&pool, schema.as_deref(), &table).await + } + ActiveConnection::MySql(pool) => { + keys::mysql_table_keys(&pool, schema.as_deref(), &table).await + } + ActiveConnection::SQLite(pool) => keys::sqlite_table_keys(&pool, &table).await, + ActiveConnection::Mongo { client, database } => { + keys::mongo_collection_keys(&client, &database, &table).await + } + } + } + /// Create a table or add a column from the form (see `ddl`). Refused on a read-only profile. pub async fn apply_table_change(&self, connection_id: Uuid, change: TableChange) -> Result<()> { self.ensure_persistent_storage_available()?; @@ -1086,9 +1109,11 @@ impl DataService { self.ai.chat(build_assistant_chat_request(&input)?).await } - /// Whether the engine is signed in to the Redrob console, for the settings surface. Never a key. - pub async fn ai_key_status(&self) -> Result { - self.ai.account_status().await + /// Whether a Redrob account is signed in (the launcher's shared file, or this app's own), for + /// the status line. Read fresh on every call; never a key. + #[must_use] + pub fn ai_key_status(&self) -> AiKeyStatus { + self.ai.account_status() } async fn profile(&self, id: Uuid) -> Result { diff --git a/crates/redrob-core/src/ddl.rs b/crates/redrob-query-core/src/ddl.rs similarity index 100% rename from crates/redrob-core/src/ddl.rs rename to crates/redrob-query-core/src/ddl.rs diff --git a/crates/redrob-core/src/error.rs b/crates/redrob-query-core/src/error.rs similarity index 93% rename from crates/redrob-core/src/error.rs rename to crates/redrob-query-core/src/error.rs index bace175..5dd3e68 100644 --- a/crates/redrob-core/src/error.rs +++ b/crates/redrob-query-core/src/error.rs @@ -40,10 +40,6 @@ pub enum DataError { InvalidApproval, #[error("AI authentication is not configured")] AiAuthentication, - /// The shared Redrob Code engine is missing or would not start. The message is the sentence the - /// app shows verbatim (`ai::unavailable_message`); there is no fallback path to name. - #[error("{0}")] - EngineUnavailable(String), #[error("AI service is temporarily unavailable")] AiUnavailable, #[error("AI request was rejected: {0}")] diff --git a/crates/redrob-query-core/src/keys.rs b/crates/redrob-query-core/src/keys.rs new file mode 100644 index 0000000..81da016 --- /dev/null +++ b/crates/redrob-query-core/src/keys.rs @@ -0,0 +1,405 @@ +//! Key and index metadata of one table (or collection): primary key, foreign keys, indexes. +//! +//! The structure panel reads these. They used to be Beekeeper-ported TypeScript querying through +//! the Tauri bridge; here they are plain dialect queries against the live pool, with every +//! identifier passed as a bound value and resolved by the database itself where the dialect +//! allows it (`PostgreSQL`, `SQLite`'s table-valued pragmas, `information_schema` on `MySQL`). +//! `MongoDB` lists the collection's indexes; it has no keys to speak of beyond `_id`. + +use futures_util::TryStreamExt as _; +use mongodb::{Client as MongoClient, bson::Document}; +use serde::{Deserialize, Serialize}; +use sqlx::{MySqlPool, PgPool, Row, SqlitePool}; + +use crate::{DataError, Result}; + +/// One foreign-key constraint, column by column. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ForeignKey { + /// The constraint name; empty on `SQLite`, which names none. + pub name: String, + /// Columns of this table, in constraint order. + pub columns: Vec, + /// The table the columns point at, schema-qualified when the dialect has schemas. + pub referenced_table: String, + /// Columns of the referenced table, parallel to `columns`. + pub referenced_columns: Vec, + #[serde(default)] + pub on_update: String, + #[serde(default)] + pub on_delete: String, +} + +/// One index: its columns in order, whether it is unique, and whether it is the primary key. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct IndexInfo { + pub name: String, + pub columns: Vec, + pub unique: bool, + pub primary: bool, +} + +/// What the structure panel shows under the columns. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TableKeys { + /// Primary-key columns in key order; empty when the table has none. + pub primary: Vec, + pub foreign: Vec, + pub indexes: Vec, +} + +fn failed(error: &impl std::fmt::Display) -> DataError { + DataError::database("key metadata lookup failed", error) +} + +/// Folds `(group, member)` rows, which arrive sorted by group then position, into groups. +fn group_rows(rows: Vec<(K, V)>) -> Vec<(K, Vec)> { + let mut groups: Vec<(K, Vec)> = Vec::new(); + for (key, value) in rows { + match groups.last_mut() { + Some((last, members)) if *last == key => members.push(value), + _ => groups.push((key, vec![value])), + } + } + groups +} + +pub(crate) async fn postgres_table_keys( + pool: &PgPool, + schema: Option<&str>, + table: &str, +) -> Result { + let primary = sqlx::query_scalar::<_, String>( + "SELECT a.attname::text FROM pg_index i \ + JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) \ + WHERE i.indisprimary AND i.indrelid = to_regclass(CASE WHEN $1::text IS NULL THEN quote_ident($2) ELSE quote_ident($1) || '.' || quote_ident($2) END) \ + ORDER BY array_position(i.indkey, a.attnum)", + ) + .bind(schema) + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + + // One row per (constraint, column position): the constraint's columns and the referenced + // table's columns are parallel arrays in pg_constraint, unnested side by side. + let fk_rows = sqlx::query( + "SELECT c.conname::text, a.attname::text, \ + quote_ident(rn.nspname) || '.' || quote_ident(rc.relname) AS referenced_table, \ + ra.attname::text AS referenced_column, \ + c.confupdtype::text, c.confdeltype::text \ + FROM pg_constraint c \ + JOIN unnest(c.conkey, c.confkey) WITH ORDINALITY AS k(attnum, refattnum, position) ON true \ + JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = k.attnum \ + JOIN pg_class rc ON rc.oid = c.confrelid \ + JOIN pg_namespace rn ON rn.oid = rc.relnamespace \ + JOIN pg_attribute ra ON ra.attrelid = c.confrelid AND ra.attnum = k.refattnum \ + WHERE c.contype = 'f' AND c.conrelid = to_regclass(CASE WHEN $1::text IS NULL THEN quote_ident($2) ELSE quote_ident($1) || '.' || quote_ident($2) END) \ + ORDER BY c.conname, k.position", + ) + .bind(schema) + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + let mut fk_pairs = Vec::with_capacity(fk_rows.len()); + for row in &fk_rows { + let name: String = row.try_get(0).map_err(|e| failed(&e))?; + let column: String = row.try_get(1).map_err(|e| failed(&e))?; + let referenced_table: String = row.try_get(2).map_err(|e| failed(&e))?; + let referenced_column: String = row.try_get(3).map_err(|e| failed(&e))?; + let on_update: String = row.try_get(4).map_err(|e| failed(&e))?; + let on_delete: String = row.try_get(5).map_err(|e| failed(&e))?; + fk_pairs.push(( + (name, referenced_table, on_update, on_delete), + (column, referenced_column), + )); + } + let foreign = group_rows(fk_pairs) + .into_iter() + .map(|((name, referenced_table, on_update, on_delete), pairs)| { + let (columns, referenced_columns) = pairs.into_iter().unzip(); + ForeignKey { + name, + columns, + referenced_table, + referenced_columns, + on_update: postgres_action(&on_update), + on_delete: postgres_action(&on_delete), + } + }) + .collect(); + + let index_rows = sqlx::query( + "SELECT ic.relname::text, a.attname::text, i.indisunique, i.indisprimary \ + FROM pg_index i \ + JOIN pg_class ic ON ic.oid = i.indexrelid \ + JOIN unnest(i.indkey) WITH ORDINALITY AS k(attnum, position) ON true \ + LEFT JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum \ + WHERE i.indrelid = to_regclass(CASE WHEN $1::text IS NULL THEN quote_ident($2) ELSE quote_ident($1) || '.' || quote_ident($2) END) \ + ORDER BY ic.relname, k.position", + ) + .bind(schema) + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + let mut index_pairs = Vec::with_capacity(index_rows.len()); + for row in &index_rows { + let name: String = row.try_get(0).map_err(|e| failed(&e))?; + // An expression index has no attribute for the position; it shows as "(expression)". + let column: Option = row.try_get(1).map_err(|e| failed(&e))?; + let unique: bool = row.try_get(2).map_err(|e| failed(&e))?; + let is_primary: bool = row.try_get(3).map_err(|e| failed(&e))?; + index_pairs.push(( + (name, unique, is_primary), + column.unwrap_or_else(|| "(expression)".to_owned()), + )); + } + let indexes = group_rows(index_pairs) + .into_iter() + .map(|((name, unique, primary), columns)| IndexInfo { + name, + columns, + unique, + primary, + }) + .collect(); + + Ok(TableKeys { + primary, + foreign, + indexes, + }) +} + +/// `pg_constraint.confupdtype` / `confdeltype` letters, spelled out. +fn postgres_action(code: &str) -> String { + match code { + "a" => "NO ACTION", + "r" => "RESTRICT", + "c" => "CASCADE", + "n" => "SET NULL", + "d" => "SET DEFAULT", + _ => "", + } + .to_owned() +} + +pub(crate) async fn mysql_table_keys( + pool: &MySqlPool, + schema: Option<&str>, + table: &str, +) -> Result { + // information_schema with the schema bound (DATABASE() when the profile names none). Column + // lists arrive one row per position, in SEQ_IN_INDEX / ORDINAL_POSITION order. + let schema_expr = schema.filter(|s| !s.is_empty()); + let stat_rows = sqlx::query( + "SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE FROM information_schema.STATISTICS \ + WHERE TABLE_SCHEMA = COALESCE(?, DATABASE()) AND TABLE_NAME = ? \ + ORDER BY INDEX_NAME, SEQ_IN_INDEX", + ) + .bind(schema_expr) + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + let mut index_pairs = Vec::with_capacity(stat_rows.len()); + for row in &stat_rows { + let name: String = row.try_get(0).map_err(|e| failed(&e))?; + let column: Option = row.try_get(1).map_err(|e| failed(&e))?; + let non_unique: i64 = row.try_get(2).map_err(|e| failed(&e))?; + let primary = name == "PRIMARY"; + index_pairs.push(( + (name, non_unique == 0, primary), + column.unwrap_or_else(|| "(expression)".to_owned()), + )); + } + let indexes: Vec = group_rows(index_pairs) + .into_iter() + .map(|((name, unique, primary), columns)| IndexInfo { + name, + columns, + unique, + primary, + }) + .collect(); + let primary = indexes + .iter() + .find(|index| index.primary) + .map(|index| index.columns.clone()) + .unwrap_or_default(); + + let fk_rows = sqlx::query( + "SELECT k.CONSTRAINT_NAME, k.COLUMN_NAME, \ + CONCAT(k.REFERENCED_TABLE_SCHEMA, '.', k.REFERENCED_TABLE_NAME), k.REFERENCED_COLUMN_NAME, \ + r.UPDATE_RULE, r.DELETE_RULE \ + FROM information_schema.KEY_COLUMN_USAGE k \ + JOIN information_schema.REFERENTIAL_CONSTRAINTS r \ + ON r.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA AND r.CONSTRAINT_NAME = k.CONSTRAINT_NAME \ + WHERE k.TABLE_SCHEMA = COALESCE(?, DATABASE()) AND k.TABLE_NAME = ? AND k.REFERENCED_TABLE_NAME IS NOT NULL \ + ORDER BY k.CONSTRAINT_NAME, k.ORDINAL_POSITION", + ) + .bind(schema_expr) + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + let mut fk_pairs = Vec::with_capacity(fk_rows.len()); + for row in &fk_rows { + let name: String = row.try_get(0).map_err(|e| failed(&e))?; + let column: String = row.try_get(1).map_err(|e| failed(&e))?; + let referenced_table: String = row.try_get(2).map_err(|e| failed(&e))?; + let referenced_column: String = row.try_get(3).map_err(|e| failed(&e))?; + let on_update: String = row.try_get(4).map_err(|e| failed(&e))?; + let on_delete: String = row.try_get(5).map_err(|e| failed(&e))?; + fk_pairs.push(( + (name, referenced_table, on_update, on_delete), + (column, referenced_column), + )); + } + let foreign = group_rows(fk_pairs) + .into_iter() + .map(|((name, referenced_table, on_update, on_delete), pairs)| { + let (columns, referenced_columns) = pairs.into_iter().unzip(); + ForeignKey { + name, + columns, + referenced_table, + referenced_columns, + on_update, + on_delete, + } + }) + .collect(); + + Ok(TableKeys { + primary, + foreign, + indexes, + }) +} + +pub(crate) async fn sqlite_table_keys(pool: &SqlitePool, table: &str) -> Result { + // Table-valued pragmas take the name as a bound value, unlike `PRAGMA x(name)`. + let primary = sqlx::query_scalar::<_, String>( + "SELECT name FROM pragma_table_info(?) WHERE pk > 0 ORDER BY pk", + ) + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + + let fk_rows = sqlx::query( + "SELECT id, seq, \"table\", \"from\", \"to\", on_update, on_delete FROM pragma_foreign_key_list(?) ORDER BY id, seq", + ) + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + let mut fk_pairs = Vec::with_capacity(fk_rows.len()); + for row in &fk_rows { + let id: i64 = row.try_get(0).map_err(|e| failed(&e))?; + let referenced_table: String = row.try_get(2).map_err(|e| failed(&e))?; + let column: String = row.try_get(3).map_err(|e| failed(&e))?; + // `to` is NULL when the constraint points at the parent's primary key implicitly. + let referenced_column: Option = row.try_get(4).map_err(|e| failed(&e))?; + let on_update: String = row.try_get(5).map_err(|e| failed(&e))?; + let on_delete: String = row.try_get(6).map_err(|e| failed(&e))?; + fk_pairs.push(( + (id, referenced_table, on_update, on_delete), + (column, referenced_column.unwrap_or_default()), + )); + } + let foreign = group_rows(fk_pairs) + .into_iter() + .map(|((_, referenced_table, on_update, on_delete), pairs)| { + let (columns, referenced_columns) = pairs.into_iter().unzip(); + ForeignKey { + name: String::new(), + columns, + referenced_table, + referenced_columns, + on_update, + on_delete, + } + }) + .collect(); + + let index_rows = + sqlx::query("SELECT name, \"unique\", origin FROM pragma_index_list(?) ORDER BY seq") + .bind(table) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))?; + let mut indexes = Vec::with_capacity(index_rows.len()); + for row in &index_rows { + let name: String = row.try_get(0).map_err(|e| failed(&e))?; + let unique: i64 = row.try_get(1).map_err(|e| failed(&e))?; + let origin: String = row.try_get(2).map_err(|e| failed(&e))?; + let columns = sqlx::query_scalar::<_, Option>( + "SELECT name FROM pragma_index_info(?) ORDER BY seqno", + ) + .bind(&name) + .fetch_all(pool) + .await + .map_err(|e| failed(&e))? + .into_iter() + .map(|column| column.unwrap_or_else(|| "(expression)".to_owned())) + .collect(); + indexes.push(IndexInfo { + name, + columns, + unique: unique != 0, + primary: origin == "pk", + }); + } + + Ok(TableKeys { + primary, + foreign, + indexes, + }) +} + +pub(crate) async fn mongo_collection_keys( + client: &MongoClient, + database: &str, + collection: &str, +) -> Result { + let mut cursor = client + .database(database) + .collection::(collection) + .list_indexes() + .await + .map_err(|e| failed(&e))?; + let mut indexes = Vec::new(); + while let Some(model) = cursor.try_next().await.map_err(|e| failed(&e))? { + let name = model + .options + .as_ref() + .and_then(|options| options.name.clone()) + .unwrap_or_default(); + let unique = model + .options + .as_ref() + .and_then(|options| options.unique) + .unwrap_or(false); + let columns = model.keys.keys().cloned().collect::>(); + let primary = name == "_id_"; + indexes.push(IndexInfo { + name, + columns, + unique: unique || primary, + primary, + }); + } + Ok(TableKeys { + primary: vec!["_id".to_owned()], + foreign: Vec::new(), + indexes, + }) +} diff --git a/crates/redrob-core/src/lib.rs b/crates/redrob-query-core/src/lib.rs similarity index 50% rename from crates/redrob-core/src/lib.rs rename to crates/redrob-query-core/src/lib.rs index a09c700..86e3628 100644 --- a/crates/redrob-core/src/lib.rs +++ b/crates/redrob-query-core/src/lib.rs @@ -1,15 +1,20 @@ -//! Database and AI runtime for Redrob Query. +//! Redrob Query engine: database connectivity, metadata, bounded querying, reviewed edits and the +//! console AI transport. The desktop shell (`apps/redrob-query`) drives it through [`Workspace`]. +pub mod account; pub mod ai; pub mod cell_edits; pub mod connection; pub mod ddl; pub mod error; +pub mod keys; pub mod models; pub(crate) mod profile_store; pub mod query; pub mod secret; +pub mod workspace; pub use connection::{DataService, RemoveProfileOutcome, SaveProfileOutcome}; pub use error::{DataError, Result}; pub use models::*; +pub use workspace::{Pending, Workspace}; diff --git a/crates/redrob-core/src/models.rs b/crates/redrob-query-core/src/models.rs similarity index 100% rename from crates/redrob-core/src/models.rs rename to crates/redrob-query-core/src/models.rs diff --git a/crates/redrob-core/src/profile_store.rs b/crates/redrob-query-core/src/profile_store.rs similarity index 100% rename from crates/redrob-core/src/profile_store.rs rename to crates/redrob-query-core/src/profile_store.rs diff --git a/crates/redrob-core/src/query.rs b/crates/redrob-query-core/src/query.rs similarity index 100% rename from crates/redrob-core/src/query.rs rename to crates/redrob-query-core/src/query.rs diff --git a/crates/redrob-core/src/secret.rs b/crates/redrob-query-core/src/secret.rs similarity index 100% rename from crates/redrob-core/src/secret.rs rename to crates/redrob-query-core/src/secret.rs diff --git a/crates/redrob-query-core/src/workspace.rs b/crates/redrob-query-core/src/workspace.rs new file mode 100644 index 0000000..7ca6f10 --- /dev/null +++ b/crates/redrob-query-core/src/workspace.rs @@ -0,0 +1,294 @@ +//! The `Workspace`: the engine's surface for the desktop shell, one method per former command. +//! +//! The Tauri shell exposed `DataService` as fifteen thin commands (list / save / remove profiles and +//! secrets, load warnings, test / connect / disconnect, metadata, query, primary keys, table changes, +//! cell edits, AI status and chat). The egui shell keeps that surface, as plain Rust: each call hands +//! the work to a tokio runtime the workspace owns and returns a [`Pending`] the UI polls once per +//! frame, so a slow database never blocks a paint. Synchronous reads (load warnings, batch splitting, +//! the account status) answer at once. +//! +//! Signing in is not here: the account comes from Redrob Launcher's shared file (`crate::account`), +//! read on every request. The one thing this app does about sign-in is open the launcher's page. + +use std::{ + future::Future, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use tokio::sync::oneshot; +use uuid::Uuid; + +use crate::{ + AiAssistantRequest, AiChatResponse, ConnectionProfile, ConnectionStatus, DataError, + DataService, MetadataNode, MetadataRequest, MutationResult, QueryRequest, QueryResultPage, + RemoveProfileOutcome, Result, SaveProfileOutcome, + account::{self, SharedAccount}, + ai::{AiKeyStatus, build_assistant_chat_request}, + cell_edits::CellEditSet, + ddl::TableChange, + keys::TableKeys, + query::read_only_statements, +}; + +/// A result that is on its way. Poll it once per frame; it answers exactly once. +pub struct Pending { + receiver: Option>>, +} + +impl Pending { + /// An already-answered pending, for paths that fail before any work is scheduled. + pub fn ready(value: Result) -> Self { + let (sender, receiver) = oneshot::channel(); + let _ = sender.send(value); + Self { + receiver: Some(receiver), + } + } + + /// The answer, the first time it is available; `None` while the work is still running and + /// forever after the answer was taken. + pub fn poll(&mut self) -> Option> { + let receiver = self.receiver.as_mut()?; + match receiver.try_recv() { + Ok(value) => { + self.receiver = None; + Some(value) + } + Err(oneshot::error::TryRecvError::Empty) => None, + Err(oneshot::error::TryRecvError::Closed) => { + self.receiver = None; + Some(Err(DataError::Unsupported( + "the operation was dropped before it answered".to_owned(), + ))) + } + } + } + + /// Whether an answer is still owed. + #[must_use] + pub fn in_flight(&self) -> bool { + self.receiver.is_some() + } +} + +type Wake = Arc; + +/// The shell's handle on the engine. +pub struct Workspace { + service: Arc, + runtime: tokio::runtime::Runtime, + own_account_file: Option, + wake: Mutex>, +} + +impl Workspace { + /// A workspace over a persistent service: `connections.json` and `account.json` in `data_dir`, + /// passwords in the OS keyring. + pub fn persistent(data_dir: &std::path::Path) -> std::io::Result { + std::fs::create_dir_all(data_dir)?; + let service = DataService::persistent(data_dir.join("connections.json")); + Self::over(service, Some(data_dir.join("account.json"))) + } + + /// A workspace over an ephemeral service: nothing is written to disk. + pub fn ephemeral() -> std::io::Result { + Self::over(DataService::new(), None) + } + + fn over(service: DataService, own_account_file: Option) -> std::io::Result { + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .thread_name("redrob-query-engine") + .enable_all() + .build()?; + Ok(Self { + service: Arc::new(service), + runtime, + own_account_file, + wake: Mutex::new(None), + }) + } + + /// Called when a pending answer lands, so the UI repaints without a timer. + pub fn set_wake(&self, wake: impl Fn() + Send + Sync + 'static) { + if let Ok(mut slot) = self.wake.lock() { + *slot = Some(Arc::new(wake)); + } + } + + fn spawn(&self, make: impl FnOnce(Arc) -> F) -> Pending + where + T: Send + 'static, + F: Future> + Send + 'static, + { + let (sender, receiver) = oneshot::channel(); + let future = make(Arc::clone(&self.service)); + let wake = self.wake.lock().ok().and_then(|slot| slot.clone()); + self.runtime.spawn(async move { + let value = future.await; + let _ = sender.send(value); + if let Some(wake) = wake { + wake(); + } + }); + Pending { + receiver: Some(receiver), + } + } + + // ---- profiles --------------------------------------------------------------------------- + + pub fn list_profiles(&self) -> Pending> { + self.spawn(|service| async move { Ok(service.list_profiles().await) }) + } + + /// Warnings produced while loading stored profiles (a corrupt file, storage owned by another + /// instance). Sanitised; safe to show. + #[must_use] + pub fn profile_load_warnings(&self) -> Vec { + self.service.profile_load_warnings() + } + + /// Save a profile and, when given, its password (into the keyring, never the profile file). + pub fn save_profile( + &self, + profile: ConnectionProfile, + secret: Option, + ) -> Pending { + self.spawn(|service| async move { + service + .save_profile_with_secret_outcome(profile, secret.as_deref()) + .await + }) + } + + pub fn remove_profile(&self, id: Uuid) -> Pending { + self.spawn(move |service| async move { service.remove_profile_outcome(id).await }) + } + + // ---- connections ------------------------------------------------------------------------ + + /// Try the profile as entered in the form, with the password typed there (or the stored one). + pub fn test_connection( + &self, + profile: ConnectionProfile, + secret: Option, + ) -> Pending { + self.spawn( + |service| async move { service.test_connection(&profile, secret.as_deref()).await }, + ) + } + + pub fn connect(&self, id: Uuid) -> Pending { + self.spawn(move |service| async move { service.connect(id).await }) + } + + pub fn disconnect(&self, id: Uuid) -> Pending<()> { + self.spawn(move |service| async move { service.disconnect(id).await }) + } + + // ---- metadata --------------------------------------------------------------------------- + + pub fn load_metadata(&self, request: MetadataRequest) -> Pending> { + self.spawn(|service| async move { service.load_metadata(request).await }) + } + + /// The primary-key columns of a table; empty when it has none (the result stays read-only). + pub fn primary_key_columns( + &self, + connection_id: Uuid, + schema: Option, + table: String, + ) -> Pending> { + self.spawn(move |service| async move { + service + .primary_key_columns(connection_id, schema, table) + .await + }) + } + + /// Primary key, foreign keys and indexes, for the structure panel. + pub fn table_keys( + &self, + connection_id: Uuid, + schema: Option, + table: String, + ) -> Pending { + self.spawn( + move |service| async move { service.table_keys(connection_id, schema, table).await }, + ) + } + + // ---- queries ---------------------------------------------------------------------------- + + /// Run one statement through the engine's read-only gate (writes go through the changes panel + /// and `apply_cell_edits`, or the table-change form and `apply_table_change`). + pub fn execute_query(&self, request: QueryRequest) -> Pending { + self.spawn(|service| async move { service.execute_query(request).await }) + } + + /// The statements of a batch the editor may run one by one, each through `execute_query` and + /// its own single-statement gate. `None` unless every statement is read-only, so a batch never + /// widens what may run; it only lets the grid show each statement's result. + #[must_use] + pub fn split_read_only_batch(query: &str) -> Option> { + read_only_statements(query) + } + + // ---- writes ----------------------------------------------------------------------------- + + /// Create a table or add a column from the form. Types come from a fixed list (`ddl`). + pub fn apply_table_change(&self, connection_id: Uuid, change: TableChange) -> Pending<()> { + self.spawn( + move |service| async move { service.apply_table_change(connection_id, change).await }, + ) + } + + /// Apply a change set the person reviewed in the changes panel. The engine checks the profile + /// is writable, binds every value, and commits only if every row update hits exactly one row. + pub fn apply_cell_edits( + &self, + connection_id: Uuid, + edits: CellEditSet, + ) -> Pending { + self.spawn( + move |service| async move { service.apply_cell_edits(connection_id, edits).await }, + ) + } + + // ---- AI and account --------------------------------------------------------------------- + + /// Whether a Redrob account is signed in, and whose. Read fresh; never a key. + #[must_use] + pub fn ai_key_status(&self) -> AiKeyStatus { + self.service.ai_key_status() + } + + /// The account in force, by the contract's precedence (environment, own file, shared file). + #[must_use] + pub fn account(&self) -> Option { + account::current_account(self.own_account_file.as_deref()) + } + + /// A fingerprint of the account files, so the shell can notice a sign-in or sign-out. + #[must_use] + pub fn account_stamp(&self) -> Vec<(PathBuf, Option<(std::time::SystemTime, u64)>)> { + account::account_files_stamp(self.own_account_file.as_deref()) + } + + /// One assistant turn: the prompt and the active query, nothing else, to the console with the + /// shared account. The request is validated before anything leaves the machine. + pub fn ai_chat(&self, request: AiAssistantRequest) -> Pending { + if let Err(error) = build_assistant_chat_request(&request) { + return Pending::ready(Err(error)); + } + self.spawn(|service| async move { service.ai_chat(request).await }) + } + + /// Where a person signs in: the launcher's latest release page. + #[must_use] + pub fn launcher_url() -> &'static str { + account::LAUNCHER_URL + } +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a7a7898..49435f1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,5 +1,14 @@ # Architecture +> **0.3.0.** The shell is now a native Rust egui/wgpu app (`apps/redrob-query`) over the engine crate +> `crates/redrob-query-core`; the React renderer, the Tauri IPC layer and the browser demo described +> below no longer exist. The shell drives the engine through `redrob_query_core::Workspace` (one +> method per former Tauri command, answered through `Pending` handles polled once per frame), AI +> goes straight to the Redrob console with Redrob Launcher's shared account (`account.rs`, `ai.rs`), +> and key / index metadata lives in `keys.rs`. The engine sections below (`DataService`, query +> classification, profile storage, secrets, reviewed edits, Mongo bounds) still describe the code as +> it is; the renderer and IPC sections are history. See the README for the current layout. + Redrob Query 0.1 separates browser-demo behavior, renderer state, native IPC, and database/AI runtime responsibilities. Java and the JVM are not part of the product. ## Layers diff --git a/docs/DEMO.md b/docs/DEMO.md deleted file mode 100644 index 6e6bb1e..0000000 --- a/docs/DEMO.md +++ /dev/null @@ -1,67 +0,0 @@ -# Demo guide - -Redrob Query has two distinct demo experiences. - -## Browser sample-data demo - -Run: - -```bash -npm ci -npm run dev -``` - -Open `http://127.0.0.1:1420`. This uses `DemoBridge`, not native drivers. The title bar says **Interactive browser workspace · sample data only**, and query tabs/history display **In-memory demo**. - -### Demonstrable flow - -1. Start on the connected **Acme Warehouse** PostgreSQL sample profile. -2. Expand `commerce → public → customers` or focus navigator search from the activity rail. -3. Run the starter query. The default page contains rows 1–50; use the next control for the remaining rows 51–64 or switch among 25/50/100/250 page sizes. -4. Sort or filter the current page, hide/show columns, inspect execution messages, and export the currently visible rows/columns to CSV. -5. Edit a supported relational sample cell, inspect staged changes, and apply it. The whole batch is checked against exact previous values before the profile's in-memory fixture changes. -6. Open local query history, restore the successful first-page query, or clear history. Browser history disappears on reload. -7. Ask Redrob AI for monthly revenue, insert the generated SQL, and run it. -8. Create, test, edit, disconnect/reconnect, and remove a user sample profile. Built-in profiles cannot be edited or removed. -9. Add a MongoDB profile with a target database and optional `authSource`; switch to its Mongo-owned MQL tab and run the deterministic read-only customer `find`. - -Connection tests and lifecycle transitions are simulated; no host is contacted. Redrob responses are local deterministic fixtures. An entered AI key is neither stored nor sent. Added profiles, queries, history, and edits disappear when the page reloads. - -Browser editing is intentionally limited to sample relational fixtures. Mongo sample results and all native desktop results are read-only. A stale or partly invalid mutation batch is rejected atomically. - -## Desktop built-in demo - -The native core always includes non-removable **Demo SQLite**, backed by `:memory:`. Desktop startup does not select or connect automatically. Select the profile and invoke **Connect**; the core then seeds four customers and five orders and exposes native SQLite metadata, guarded reads, typed rows, and paging. - -Desktop results remain read-only and do not expose cell editing. Desktop query tabs/history can be restored locally, but restoration never auto-selects, connects, or runs a query. - -## Recorded acceptance demo - -Demo-recorder configuration and output are intentionally excluded by `.gitignore`. A validated run produces: - -- `video/demo.mp4` — H.264 deliverable; -- `video/demo.webm` — original browser recording; -- per-step screenshots and accessibility snapshots; -- `action-log.json` and `review-report.md`. - -A passing review requires every scenario assertion to pass, no unexpected console errors, no failed network response, and a decodable MP4. The recording is evidence only for the browser sample-data workflow; it is not evidence that external databases, the native WebKit shell, or Redrob were contacted. - -## Live connector tests - -The three external connector tests are marked `#[ignore]`. A default Rust run executes hermetic tests and reports those three as ignored; it never silently treats a missing service as a live pass. - -Export all disposable URLs and explicitly run ignored tests: - -```bash -export REDROB_TEST_POSTGRES_URL='postgresql://…' -export REDROB_TEST_MYSQL_URL='mysql://…' -export REDROB_TEST_MONGO_URL='mongodb://…' -export REDROB_TEST_MONGO_DATABASE='redrob_test' # optional -cargo test --locked -p redrob-core --all-features -- --ignored -``` - -Or run one named gate with `-- --ignored`. An explicitly selected gate fails immediately if its required URL is missing. - -The PostgreSQL gate covers native scalar and parameter behavior. The MySQL gate covers scalars, unsigned values, BIT/TINYINT, parameters, and file-output rejection. The Mongo gate creates one UUID-named collection and covers find/aggregate pagination, explain modes, BSON fidelity, and bounded metadata sampling before dropping that collection. - -Use only non-production databases and least-privilege credentials. Rust does not automatically load `.env` files. diff --git a/e2e/run.mjs b/e2e/run.mjs deleted file mode 100644 index ead9265..0000000 --- a/e2e/run.mjs +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env node -// End-to-end: the real desktop build, driven over W3C WebDriver through tauri-driver. -// -// add a SQLite connection -> "Query this table" on people -> Run -> edit row 1's name -// -> Apply -> Run again -> the new name is on screen (and the CI step then reads it from the file) -// -// Every step is a click or a keystroke a person could make; nothing calls a native command -// directly. Elements are reached with `execute/sync` page scripts, as in redrob-recall's E2E: on -// WebKitWebDriver, element lookup by XPath or CSS failed for elements present in the page source. -// -// No test framework: a handful of HTTP calls, and the WebdriverIO dependency tree carried -// high-severity advisories that no override reached. -// -// Needs: tauri-driver on WEBDRIVER_URL (default http://127.0.0.1:4444), the debug app at APP, and -// REDROB_E2E_DB pointing at a SQLite file with a `people` table (see e2e/seed.sql). Screenshots go -// to SHOTS (default e2e/screenshots). -// -// Step names, cited from docs/compatibility.md as `e2e:e2e/run.mjs::` (the matrix guard checks -// that this file contains the name): edit_cell_apply, delete_row_apply, insert_row_apply, -// no_primary_key_read_only, create_table_add_column. -import { mkdirSync, writeFileSync } from 'node:fs'; -import { join } from 'node:path'; - -const DRIVER = process.env.WEBDRIVER_URL ?? 'http://127.0.0.1:4444'; -const APP = process.env.APP; -const DB = process.env.REDROB_E2E_DB; -const SHOTS = process.env.SHOTS ?? 'e2e/screenshots'; -const NEW_NAME = 'Ann Lee-Kim'; -if (!APP || !DB) { - console.error('APP and REDROB_E2E_DB must be set'); - process.exit(2); -} -mkdirSync(SHOTS, { recursive: true }); - -async function wd(method, path, body) { - const response = await fetch(`${DRIVER}${path}`, { - method, - headers: { 'content-type': 'application/json' }, - body: body === undefined ? undefined : JSON.stringify(body), - }); - const json = await response.json(); - if (!response.ok || json.value?.error) throw new Error(`${method} ${path}: ${json.value?.error} ${json.value?.message ?? ''}`); - return json.value; -} - -let session; -const js = (body, ...args) => wd('POST', `/session/${session}/execute/sync`, { script: body, args }); - -/** Click the element with this data-testid; false when it is not on screen or disabled. */ -const click = (id) => js("const el = document.querySelector('[data-testid=\"' + arguments[0] + '\"]'); if (!el || el.disabled) return false; el.click(); return true;", id); -/** Click the button or menu item whose accessible name or visible text is exactly `label`. Exact on - * purpose: "Connect" must not hit "Connection actions", which a contains-match did on the first run. */ -const clickLabel = (label) => js( - `const want = arguments[0]; - const els = [...document.querySelectorAll('button, [role=menuitem], [role=tab]')]; - const el = els.find((e) => !e.disabled && (e.getAttribute('aria-label') === want || (e.textContent ?? '').trim() === want)); - if (!el) return false; el.click(); return true;`, label); -/** True when some element's visible text includes `fragment`. */ -const shows = (fragment) => js("return (document.body.innerText ?? '').includes(arguments[0]);", fragment); -/** Type into a React-controlled field: set through the native setter, then fire `input`. */ -const fill = (id, value) => js( - `const el = document.querySelector('[data-testid="' + arguments[0] + '"]'); - if (!el) return false; - const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; - Object.getOwnPropertyDescriptor(proto, 'value').set.call(el, arguments[1]); - el.dispatchEvent(new Event('input', { bubbles: true })); - return true;`, id, value); -/** Text of the grid cell with this accessible name, or null. */ -const cell = (name) => js("const el = document.querySelector('[role=cell][aria-label=\"' + arguments[0] + '\"]'); return el ? el.textContent : null;", name); - -async function shot(name) { - const png = await wd('GET', `/session/${session}/screenshot`); - writeFileSync(join(SHOTS, `${name}.png`), Buffer.from(png, 'base64')); -} -async function until(what, check, seconds = 60) { - const deadline = Date.now() + seconds * 1000; - let last = 'the check returned nothing'; - for (;;) { - try { - const value = await check(); - if (value) return value; - } catch (error) { - if (error.fatal) throw error; - last = error.message; - } - if (Date.now() > deadline) throw new Error(`timed out waiting for ${what}; last: ${last}`); - await new Promise((resolve) => setTimeout(resolve, 500)); - } -} - -let failed = false; -try { - session = (await wd('POST', '/session', { capabilities: { alwaysMatch: { 'tauri:options': { application: APP } } } })).sessionId; - - // 1. Add the SQLite file as a connection. - await until('the add-connection button', () => click('open-connection-modal')); - await until('the SQLite choice', () => click('database-sqlite')); - await until('the name field', () => fill('connection-name', 'E2E people')); - await until('the file field', () => fill('connection-file', DB)); - // New connections are read-only; editing needs the person's explicit yes. - await until('the Allow edits switch', () => click('connection-allow-edits')); - if (!(await js("return document.querySelector('[data-testid=connection-allow-edits]').checked;"))) throw new Error('Allow edits did not turn on'); - await shot('1-connection'); - await until('Save to accept a click', () => click('save-connection')); - // The saved connection becomes the active one; connect it from the sidebar's actions menu. - await until('the new connection to be active', () => shows('E2E people')); - await until('the connection actions menu', async () => (await clickLabel('Connection actions')) && clickLabel('Connect')); - await until('the connection to connect', () => shows('E2E people · connected')); - - // 2. "Query this table" on people writes and opens the SELECT. - // SQLite puts tables under a schema node; open collapsed nodes until the table's menu is there. - await until('the people table and its menu', async () => { - if (await clickLabel('Actions for people')) return clickLabel('Query this table'); - await js("const el = document.querySelector('[data-testid=navigator] button[aria-expanded=false]'); if (el) el.click(); return true;"); - return false; - }); - await until('Run to accept a click', () => click('run-query')); - const before = await until("row 1's name", () => cell('name, result row 1')); - if (before !== 'Ann') throw new Error(`row 1 name before the edit is ${JSON.stringify(before)}, not "Ann"`); - await shot('2-result'); - - // 3. Edit the cell in place, as a double-click and typing would, then leave it. [edit_cell_apply] - await until('the name cell to be editable', () => js( - `const el = document.querySelector('[role=cell][aria-label="name, result row 1"]'); - if (!el || el.getAttribute('contenteditable') !== 'true') return false; - el.focus(); el.textContent = arguments[0]; el.dispatchEvent(new FocusEvent('blur')); el.blur(); return true;`, NEW_NAME)); - await until('the staged change', () => js("return [...document.querySelectorAll('button')].some((b) => /1 staged change/.test(b.textContent ?? ''));")); - await shot('3-staged'); - - // 4. Apply, then read the table again. - await until('the changes panel', async () => (await text('changes-panel')) !== null || clickLabel('1 staged change')); - await until('Apply to accept a click', () => click('apply-changes')); - // Apply reports a failure as a toast that fades in seconds, so read it while waiting. - await until('the staged change to clear', async () => { - const failure = await js("const t = document.body.innerText ?? ''; const i = t.indexOf('Could not apply changes'); return i < 0 ? null : t.slice(i, i + 300);"); - if (failure) throw Object.assign(new Error(`Apply failed: ${failure}`), { fatal: true }); - return js("return ![...document.querySelectorAll('button')].some((b) => /staged change/.test(b.textContent ?? ''));"); - }); - await until('Run to accept a click', () => click('run-query')); - await until('the new name after a fresh run', async () => (await cell('name, result row 1')) === NEW_NAME); - await shot('4-applied'); - - // 5. Delete row 2 from its menu, Apply, and read the table again. [delete_row_apply] - await until("row 2's menu and Delete row", async () => (await clickLabel('Row 2 actions')) && clickLabel('Delete row')); - await until('the staged delete', () => shows('Delete this row')); - await until('Apply to accept a click', () => click('apply-changes')); - await until('the staged delete to clear', async () => { - const failure = await js("const t = document.body.innerText ?? ''; const i = t.indexOf('Could not apply changes'); return i < 0 ? null : t.slice(i, i + 300);"); - if (failure) throw Object.assign(new Error(`Apply failed: ${failure}`), { fatal: true }); - return !(await shows('Delete this row')); - }); - await until('Run to accept a click', () => click('run-query')); - await until('row 2 gone after a fresh run', async () => (await cell('name, result row 1')) === NEW_NAME && (await cell('name, result row 2')) === null); - await shot('5-deleted'); - - // 6. Add a row, leaving the key for the database to assign, Apply, and read the table again. [insert_row_apply] - await until('Add row to accept a click', () => click('add-row')); - await until('the new-row name field', () => fill('new-row-name', 'Cy')); - await until('the new-row plan field', () => fill('new-row-plan', 'Free')); - await until('Stage new row to accept a click', () => click('stage-new-row')); - await until('the staged new row', () => shows('name = Cy')); - await until('Apply to accept a click', () => click('apply-changes')); - await until('the staged new row to clear', async () => { - const failure = await js("const t = document.body.innerText ?? ''; const i = t.indexOf('Could not apply changes'); return i < 0 ? null : t.slice(i, i + 300);"); - if (failure) throw Object.assign(new Error(`Apply failed: ${failure}`), { fatal: true }); - return !(await shows('name = Cy')); - }); - await until('Run to accept a click', () => click('run-query')); - await until('the new row after a fresh run', async () => (await cell('name, result row 2')) === 'Cy'); - await shot('6-inserted'); - - // 7. [no_primary_key_read_only] A table without a primary key: Query this table works, but nothing can be edited, and the - // grid says why. - await until('the notes table and its menu', async () => (await clickLabel('Actions for notes')) && clickLabel('Query this table')); - await until('Run to accept a click', () => click('run-query')); - await until('the notes rows', async () => (await cell('body, result row 1')) === 'first'); - await until('the reason it is read-only', () => shows('notes has no primary key')); - const locked = await js( - `const cell = document.querySelector('[role=cell][aria-label="body, result row 1"]'); - const add = document.querySelector('[data-testid=add-row]'); - return cell.getAttribute('contenteditable') !== 'true' && add.disabled;`); - if (!locked) throw new Error('the key-less table offered editing'); - await shot('7-no-key'); - - // 8. Create a table from the form, then add a column to it from its row menu. [create_table_add_column] - await until('New table to accept a click', () => clickLabel('New table')); - await until('the table name field', () => fill('table-change-table', 'pets')); - await until('the key column name', () => fill('table-change-column-0', 'id')); - await until('the second column name', () => fill('table-change-column-1', 'name')); - await until('Create table to accept a click', () => click('table-change-submit')); - await until('the pets table in the tree', async () => (await clickLabel('Actions for pets')) && clickLabel('Add column')); - await until('the added column name', () => fill('table-change-column-0', 'born')); - await until('the date type', () => js("const el = document.querySelector('[data-testid=table-change-type-0]'); if (!el) return false; const set = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value').set; set.call(el, 'date'); el.dispatchEvent(new Event('change', { bubbles: true })); return el.value === 'date';")); - await until('Add column to accept a click', () => click('table-change-submit')); - await until('the column to be added', () => shows('Column added')); - await shot('8-ddl'); - console.log(`e2e: SQLite connection -> Query this table -> Run -> edit name -> Apply -> Run -> "${NEW_NAME}" -> delete row 2 -> Apply -> Run -> 1 row -> add "Cy" -> Apply -> Run -> 2 rows -> key-less notes: read-only, reason shown -> New table pets -> Add column born`); -} catch (error) { - failed = true; - console.error(`e2e failed: ${error.message}`); - if (session) { - await shot('failure').catch(() => {}); - const source = await wd('GET', `/session/${session}/source`).catch((e) => `source unavailable: ${e.message}`); - writeFileSync(join(SHOTS, 'failure.html'), String(source)); - } -} finally { - if (session) await wd('DELETE', `/session/${session}`).catch(() => {}); -} -process.exit(failed ? 1 : 0); - -async function text(id) { - return js("const el = document.querySelector('[data-testid=\"' + arguments[0] + '\"]'); return el ? el.textContent : null;", id); -} diff --git a/e2e/seed.sql b/e2e/seed.sql deleted file mode 100644 index c2a394a..0000000 --- a/e2e/seed.sql +++ /dev/null @@ -1,6 +0,0 @@ --- The E2E database: a table with a single-column primary key, so its result is editable. -CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT NOT NULL, plan TEXT NOT NULL); -INSERT INTO people (id, name, plan) VALUES (1, 'Ann', 'Free'), (2, 'Bo', 'Scale'); --- A table without a primary key: its results must stay read-only and say why. -CREATE TABLE notes (body TEXT NOT NULL); -INSERT INTO notes (body) VALUES ('first'), ('second'); diff --git a/index.html b/index.html deleted file mode 100644 index faad59a..0000000 --- a/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - Redrob Query - - -
- - - diff --git a/installer/macos/dmg_settings.py b/installer/macos/dmg_settings.py new file mode 100644 index 0000000..a850660 --- /dev/null +++ b/installer/macos/dmg_settings.py @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# dmgbuild settings for the Redrob Query disk image (installer/macos/make-dmg.sh passes the -D values). +# +# dmgbuild writes the Finder layout (.DS_Store) itself instead of scripting Finder, so the image comes +# out the same on a build machine reached over ssh and on a CI runner, neither of which can be relied +# on to let a script drive Finder. Icon centres and the window size match res/background.html. +import os.path + +app = defines["app"] # noqa: F821 -- dmgbuild injects `defines` +uninstaller = defines["uninstaller"] # noqa: F821 +here = defines["here"] # noqa: F821 + +format = "UDZO" +compression_level = 9 +filesystem = "HFS+" + +files = [app, uninstaller] +# The licence texts and notices the zip carries beside the app, so the image ships them too. +licenses = defines.get("licenses") # noqa: F821 +if licenses: + files.append((licenses, "Licenses")) +symlinks = {"Applications": "/Applications"} +icon = os.path.join(app, "Contents", "Resources", "AppIcon.icns") # the volume's own icon + +# dmgbuild picks up background@2x.png beside background.png and makes a Retina TIFF from the pair. +background = os.path.join(here, "res", "background.png") +window_rect = ((200, 120), (660, 400)) +default_view = "icon-view" +show_status_bar = False +show_tab_view = False +show_toolbar = False +show_pathbar = False +show_sidebar = False +arrange_by = None +icon_size = 96 +text_size = 13 +icon_locations = { + os.path.basename(app): (170, 180), + "Applications": (490, 180), + os.path.basename(uninstaller): (250, 318), + "Licenses": (410, 318), +} diff --git a/installer/macos/make-dmg.sh b/installer/macos/make-dmg.sh new file mode 100644 index 0000000..1cff038 --- /dev/null +++ b/installer/macos/make-dmg.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Build the Redrob Query disk image: the app, an Applications link, and the uninstaller, on a branded +# background. Runs on macOS. +# +# make-dmg.sh --app "/Redrob Query.app" --out --version X.Y.Z +# [--licenses ] [--sign-identity ] +# +# --licenses is the staged share/redrob-query/licenses folder (tools/release/stage.py: LICENSE, NOTICE, +# UPSTREAM_NOTICES.md); it is copied in as "Licenses", as the zip carries it beside the app. +# +# Needs dmgbuild (pinned 1.6.5, importable by `python3`; a venv where pip refuses --user), which writes the Finder +# layout itself, so no GUI session or automation permission is needed (dmg_settings.py). +# +# With --sign-identity the uninstaller and the image are signed (hardened runtime, timestamp). The app +# must already be signed, notarized and stapled: the image is built around it, not into it. The release +# workflow then notarizes and staples the image itself. +set -euo pipefail + +here="$(cd "$(dirname "$0")" && pwd)" +app="" out="" version="" identity="" licenses="" +while [ $# -gt 0 ]; do + case "$1" in + --app) app="$2"; shift 2 ;; + --out) out="$2"; shift 2 ;; + --version) version="$2"; shift 2 ;; + --licenses) licenses="$2"; shift 2 ;; + --sign-identity) identity="$2"; shift 2 ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done +[ -d "$app" ] || { echo "--app must be the Redrob Query.app bundle" >&2; exit 2; } +[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]] || { echo "--version must be X.Y.Z" >&2; exit 2; } +[ -n "$out" ] || { echo "--out is required" >&2; exit 2; } +icns="$app/Contents/Resources/AppIcon.icns" +[ -f "$icns" ] || { echo "the app carries no Contents/Resources/AppIcon.icns" >&2; exit 2; } +python3 -c 'import dmgbuild' 2>/dev/null || { echo "dmgbuild is missing: python3 -m pip install --user dmgbuild==1.6.5" >&2; exit 2; } + +work="$(mktemp -d "${TMPDIR:-/tmp}/redrob-dmg.XXXXXX")" +trap 'rm -rf "$work"' EXIT + +# The uninstaller: a script in a bundle, so it opens from Finder with the product's icon. +un="$work/Uninstall Redrob Query.app" +mkdir -p "$un/Contents/MacOS" "$un/Contents/Resources" +cp "$here/uninstaller/uninstall.sh" "$un/Contents/MacOS/Uninstall Redrob Query" +chmod 755 "$un/Contents/MacOS/Uninstall Redrob Query" +cp "$icns" "$un/Contents/Resources/AppIcon.icns" +cat > "$un/Contents/Info.plist" < + + + + CFBundleExecutableUninstall Redrob Query + CFBundleIdentifierai.redrob.query.uninstaller + CFBundleNameUninstall Redrob Query + CFBundleDisplayNameUninstall Redrob Query + CFBundleIconFileAppIcon + CFBundlePackageTypeAPPL + CFBundleShortVersionString${version} + CFBundleVersion${version} + LSMinimumSystemVersion12.0 + NSHumanReadableCopyrightCopyright 2026 Janghoon Lee (Redrob) and contributors. GPL-3.0-or-later. + + +PLIST +if [ -n "$identity" ]; then + codesign --force --options runtime --timestamp --sign "$identity" "$un" + codesign --verify --strict --verbose=2 "$un" +fi + +mkdir -p "$(dirname "$out")" +rm -f "$out" +extra=() +if [ -n "$licenses" ]; then + [ -f "$licenses/LICENSE" ] && [ -f "$licenses/NOTICE" ] || { echo "--licenses has no LICENSE/NOTICE: $licenses" >&2; exit 2; } + extra=(-D licenses="$licenses") +fi +python3 -m dmgbuild -s "$here/dmg_settings.py" \ + -D app="$app" -D uninstaller="$un" -D here="$here" ${extra[@]+"${extra[@]}"} \ + "Redrob Query" "$out" +if [ -n "$identity" ]; then + codesign --force --timestamp --sign "$identity" "$out" + codesign --verify --verbose=2 "$out" +fi +hdiutil verify -quiet "$out" +echo "built $out ($(du -h "$out" | cut -f1))" diff --git a/installer/macos/res/background.html b/installer/macos/res/background.html new file mode 100644 index 0000000..c23a7e0 --- /dev/null +++ b/installer/macos/res/background.html @@ -0,0 +1,55 @@ + + + + + + + + +
+
+ Redrob Query +
Drag the app onto Applications to install it.
+ + + +
+ + diff --git a/installer/macos/res/background.png b/installer/macos/res/background.png new file mode 100644 index 0000000000000000000000000000000000000000..fc24ed1dfca782100e96e6dc35bd9fa20193f91b GIT binary patch literal 18186 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 u&`)}/dev/null +on run argv + try + set r to display dialog (item 1 of argv) with title "$TITLE" buttons {$buttons} default button "$default" $icon_clause + return button returned of r + on error number -128 + return "" + end try +end run +OSA +} + +say_done() { # message + [ "$silent" -eq 1 ] && return 0 + ask "$1" "OK" "OK" >/dev/null +} + +apps=() +for candidate in "/Applications/Redrob Query.app" "$HOME/Applications/Redrob Query.app"; do + [ -d "$candidate" ] && apps+=("$candidate") +done + +if [ "${#apps[@]}" -eq 0 ]; then + say_done "Redrob Query is not in Applications, so there is nothing to remove." + exit 3 +fi + +# Only a Redrob Query started from a copy being removed blocks it; one opened from a download folder does not. +for app in "${apps[@]}"; do + if pgrep -f "^$app/Contents/MacOS/" >/dev/null 2>&1; then + say_done "Redrob Query is open. Save your work, quit it, then open the uninstaller again." + exit 4 + fi +done + +if [ "$silent" -eq 0 ]; then + answer="$(ask "Remove Redrob Query from this Mac? It goes to the Trash. Your databases are not touched." \ + "Uninstall" "Cancel" "Uninstall")" + [ "$answer" = "Uninstall" ] || exit 1 + answer="$(ask "Also delete its settings and connection profiles? Keep them if you may install Redrob Query again. Passwords stay in Keychain." \ + "Keep" "Delete" "Keep")" + [ -n "$answer" ] || exit 1 + [ "$answer" = "Delete" ] && delete_data=1 +fi + +# To the Trash, not deleted: a name already in the Trash gets a timestamp rather than being replaced. +trash="$HOME/.Trash" +mkdir -p "$trash" +for app in "${apps[@]}"; do + target="$trash/Redrob Query.app" + [ -e "$target" ] && target="$trash/Redrob Query $(date +%Y-%m-%d\ %H.%M.%S).app" + if ! mv "$app" "$target" 2>/dev/null; then + say_done "Redrob Query could not be moved to the Trash. Drag it from Applications to the Trash instead." + exit 5 + fi +done + +if [ "$delete_data" -eq 1 ]; then + defaults delete "com.redrob.Redrob Query" >/dev/null 2>&1 # also flushes cfprefsd's cached copy + for path in \ + "$HOME/Library/Application Support/Redrob Query" \ + "$HOME/Library/Preferences/com.redrob.Redrob Query.plist" \ + "$HOME/Library/Application Support/Redrob/Redrob Query" \ + "$HOME/Library/Preferences/Redrob/Redrob Query" \ + "$HOME/Library/Caches/Redrob/Redrob Query" \ + "$HOME/Library/Saved Application State/ai.redrob.query.savedState"; do + rm -rf -- "$path" + done + say_done "Redrob Query is in the Trash, and its settings and AI engine were deleted." +else + say_done "Redrob Query is in the Trash. Its settings stay on this Mac for a later install." +fi +exit 0 diff --git a/installer/windows/README.md b/installer/windows/README.md new file mode 100644 index 0000000..4177a10 --- /dev/null +++ b/installer/windows/README.md @@ -0,0 +1,72 @@ +# Redrob Query: Windows setup + +`redrob_query_setup.cpp` is the branded Windows setup and its uninstaller, in one small native Win32 app +(Direct2D, DirectWrite, WIC; nothing beyond the Windows SDK). It is the Redrob Launcher setup +(`redrob-labs/redrob-launcher`, `installer/windows/`, our own code, itself from the Redrob Canvas and Redrob +Browser setups) with Redrob Query's payload layout and wording: one wizard look across the Redrob apps, a +language page, an `.exe` that carries the Redrob icon and builds one SmartScreen reputation. + +For Redrob Launcher the installed product is **ProductName `Redrob Query`**, the executable is +**`bin\redrob-query.exe`** under `%LOCALAPPDATA%\Programs\Redrob Query`, and the Settings > Apps entry is +`HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\RedrobQuery` with publisher `Redrob`. + +## What it does + +- **Language first.** The first screen is bilingual and offers English / 한국어; every screen after it, the + window title included, follows the choice. It is preselected from a previous install's answer, else + Korean on a Korean Windows, else English (`--lang en|ko` overrides). The answer goes to + `HKCU\Software\Redrob\Redrob Query\Language` so the next setup preselects it. +- **Install**, per user, no administrator rights, into `%LOCALAPPDATA%\Programs\Redrob Query`. The payload is + the release's own signed `-portable.zip` (`bin\redrob-query.exe` and the licence set, from + `tools/release/stage.py`), unpacked with System32's `tar.exe` beside the old install and swapped in, so a + failed update never leaves a half-updated folder. A running Redrob Query is never closed for the person: + the setup asks them to close it, then Retry. +- **Shortcuts**: Start menu always, desktop when the welcome screen's box is ticked (the choice is + remembered for the next update). Both carry the AppUserModelID `ai.redrob.query`, set through + `IPropertyStore`, which Windows needs for toast notifications from an unpackaged app. +- **Registry**: `InstallDir`, `Language`, `DesktopShortcutChoice` under `HKCU\Software\Redrob\Redrob Query`; + the App Paths entry for Win+R `redrob-query`; and the **Settings > Apps** entry + (`...\Uninstall\RedrobQuery`: name, version, publisher, icon, size, `UninstallString`). +- **Uninstall**: the same app built without a payload, written into the install folder as + `Uninstall Redrob Query.exe`. It asks first, with "Also delete its settings and connection profiles" off by + default, then removes the shortcuts, the entries and the folder. With the box: `%APPDATA%\Redrob Query` + (connection profiles, workspace, `ui.ron`) and the registry key. Passwords live in the Windows Credential + Manager and are not touched. `%APPDATA%\Redrob\Shared` (the launcher's sign-in every Redrob app reads) is + never touched. + +## Flags + +| Flag | Does | +| --- | --- | +| (none) | the setup window; the uninstaller build opens its confirm screen | +| `--lang en` / `--lang ko` | preselect the language | +| `--uninstall` | the uninstall window (what Settings > Apps runs) | +| `--silent` | the same work with no window; the exit code is the result (0 = success, `0xE0000001` = Redrob Query is running). Never opens the app afterwards | +| `--delete-data` | with `--uninstall --silent`: also delete settings and connection profiles | +| `--demo`, `--demo-error`, `--demo-uninstall` | the window's states on a timer, touching nothing | +| `--render-stages ` | draw every screen (both languages) to PNGs in ``, no window | + +## Build + +Visual Studio with the C++ x64 toolset (found with `vswhere`) and Python 3: + +``` +py -3 installer\windows\build.py --out --arch x64 --payload redrob-query-X.Y.Z-windows-x64-portable.zip --version X.Y.Z +``` + +The release workflow builds it in two stages so every binary is signed before it is embedded: +`--uninstaller-only`, sign that, then `--payload … --uninstaller --name …-setup.exe`, sign that +(`.github/workflows/release.yml`, Windows leg; signing is the shared `redrob-labs/.github/actions/sign-windows` +action through Azure Artifact Signing). + +The art is committed: `res/background.png` is `res/background.html` rasterised at 1320x800 (shared with the +other Redrob setups); `res/lockup.png` (the Redrob Query lockup, white, scaled) and +`resources/icons/redrob-query.ico` come from `scripts/build-brand-assets.py` and are pinned in +`brand/SOURCES.json`. + +## Verify + +`verify-install.ps1` runs on a Windows machine against a built setup: install, shortcuts, the Settings entry, +the signed exe, an upgrade over the install, an update refused while Redrob Query runs, and the uninstall. It +does not exercise `--delete-data`, which would delete that machine's real connection profiles. Both languages: +`--lang ko` and `--lang en`. diff --git a/installer/windows/build.py b/installer/windows/build.py new file mode 100644 index 0000000..50d45d3 --- /dev/null +++ b/installer/windows/build.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later +"""Build the Redrob Query Windows setup (and its uninstaller). + +Standard library only. It: + 1. locates Visual Studio (vswhere) and runs rc.exe / cl.exe under its developer environment for the + requested --arch (x64, x86 or arm64; the x86 and arm64 compilers are cross compilers hosted on x64); + 2. compiles redrob_query_setup.rc + redrob_query_setup.cpp twice: + uninstall/Uninstall Redrob Query.exe no payload; this IS the uninstaller (--uninstall) + embeds the release zip and the uninstaller above + +Usage: + build.py --out --arch x64 --payload --version 0.4.0 [--name ] + build.py --out --version 0.4.0 # demo-only build (REDROB_NO_PAYLOAD): --demo flags only + +The payload is the release's own -portable.zip (tools/release/stage.py: bin/redrob-query.exe and the +licence set), already Authenticode-signed inside, so the setup must be built after signing and then signed +itself (release workflow, Windows leg). + +The art is committed, not rendered here: res/background.png is res/background.html rasterised at 1320x800 +(shared with the other Redrob setups); res/lockup.png and resources/icons/redrob-query.ico are built by +scripts/build-brand-assets.py from the design system delivery and pinned in brand/SOURCES.json. Pretendard +is embedded when the design system delivery is present (--design-system, default ../redrob-design-system) +and fontTools is installed; otherwise the window uses Segoe UI Variable (DirectWrite falls back to a system +font for Hangul). + +Adapted from redrob-labs/redrob-launcher installer/windows/build.py (our own code). +""" +from __future__ import annotations + +import argparse +import os +import re +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path + +HERE = Path(__file__).resolve().parent +RES = HERE / "res" +REPO = HERE.parent.parent +DEFAULT_DS = REPO.parent / "redrob-design-system" +ICON = REPO / "resources" / "icons" / "redrob-query.ico" +BACKGROUND = RES / "background.png" +LOCKUP_PNG = RES / "lockup.png" + + +def find_vswhere() -> Path: + pf = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + vswhere = Path(pf) / "Microsoft Visual Studio" / "Installer" / "vswhere.exe" + if vswhere.exists(): + return vswhere + found = shutil.which("vswhere") + if found: + return Path(found) + sys.exit("vswhere.exe not found; install Visual Studio with the C++ x64 toolset.") + + +# vcvarsall.bat's name for each target, hosted on an x64 machine. +VCVARS_ARCH = {"x64": "x64", "x86": "x64_x86", "arm64": "x64_arm64"} + + +def vs_dev_env(arch: str) -> dict: + """The environment of a developer command prompt for `arch` (x64, x86 or arm64; x64 host).""" + vsinstall = subprocess.run( + [str(find_vswhere()), "-latest", "-products", "*", + "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-property", "installationPath"], + capture_output=True, text=True, check=True).stdout.strip() + if not vsinstall: + sys.exit("No Visual Studio install with the C++ x64 toolset found.") + vcvars = Path(vsinstall) / "VC" / "Auxiliary" / "Build" / "vcvarsall.bat" + if not vcvars.exists(): + sys.exit(f"vcvarsall.bat missing under {vsinstall}") + # vcvars can leave a non-zero errorlevel on success, so key off a marker and a sane env instead. + # A real batch file carries vcvars' variables into `set`; an inline `cmd /c "call ... & set"` does not. + marker = "__REDROB_ENV__" + bat = Path(tempfile.gettempdir()) / "_redrob_query_vcvars.bat" + bat.write_text(f'@echo off\r\ncall "{vcvars}" {VCVARS_ARCH[arch]}\r\necho {marker}\r\nset\r\n', encoding="ascii") + try: + out = subprocess.run(["cmd", "/c", str(bat)], capture_output=True, text=True, errors="replace").stdout + finally: + bat.unlink(missing_ok=True) + env, seen = {}, False + for line in out.splitlines(): + if line.strip() == marker: + seen = True + elif seen and "=" in line: + k, v = line.split("=", 1) + env[k] = v + keys = {k.upper() for k in env} + if "PATH" not in keys or "INCLUDE" not in keys: + sys.exit("vcvarsall did not produce a developer environment (no INCLUDE/PATH).") + env.setdefault("SystemRoot", os.environ.get("SystemRoot", r"C:\Windows")) + return env + + +def prepare_font(design_system: Path, out_ttf: Path) -> bool: + """Pretendard SemiBold. AddFontMemResourceEx needs TTF/OTF; the design system ships woff2.""" + woff2 = design_system / "fonts" / "Pretendard-SemiBold.woff2" + if not woff2.exists(): + return False + try: + from fontTools.ttLib import TTFont # type: ignore + font = TTFont(str(woff2)) + font.flavor = None + font.save(str(out_ttf)) + return out_ttf.exists() + except Exception: + print("note: fontTools not available; the window uses Segoe UI Variable.") + return False + + +def run_in_env(cmd: list, env: dict, cwd: str) -> None: + """CreateProcess resolves the program on the PARENT's PATH, so resolve it on the dev env's first.""" + path = next((v for k, v in env.items() if k.upper() == "PATH"), None) + resolved = shutil.which(cmd[0], path=path) + if resolved: + cmd = [resolved, *cmd[1:]] + print("$", " ".join(cmd), flush=True) + subprocess.run(cmd, env=env, check=True, cwd=cwd) + + +def compile_app(env, out: Path, exe_name: str, font, ver: str, payload, uninstaller, machine: str = "X64") -> Path: + """rc + cl + link into out/exe_name. Paths reach RC through a generated header with forward slashes: + RC's preprocessor reads backslashes in string macros as escapes.""" + def fwd(p) -> str: + return str(p).replace("\\", "/") + + out.mkdir(parents=True, exist_ok=True) + parts = (ver.split(".") + ["0", "0", "0", "0"])[:4] + lines = [ + "// generated by build.py -- do not edit", + f'#define REDROB_BACKGROUND_PATH "{fwd(BACKGROUND)}"', + f'#define REDROB_LOCKUP_PATH "{fwd(LOCKUP_PNG)}"', + f'#define REDROB_ICON_PATH "{fwd(ICON)}"', + f"#define REDROB_VER_FILE {','.join(parts)}", + f'#define REDROB_VER_STR "{ver}"', + f'#define REDROB_VER_WSTR L"{ver}"', + f'#define REDROB_SETUP_KIND "{"Uninstaller" if payload is None else "Setup"}"', + ] + if font: + lines.append(f'#define REDROB_FONT_PATH "{fwd(font)}"') + if payload: + lines.append(f'#define REDROB_PAYLOAD_PATH "{fwd(payload)}"') + else: + lines.append("#define REDROB_NO_PAYLOAD 1") + if uninstaller: + lines.append(f'#define REDROB_UNINSTALLER_PATH "{fwd(uninstaller)}"') + (out / "build_defs.h").write_text("\n".join(lines) + "\n", encoding="ascii") + + res = out / "redrob_query_setup.res" + run_in_env(["rc.exe", "/nologo", f"/I{out}", f"/I{HERE}", f"/fo{res}", str(HERE / "redrob_query_setup.rc")], + env, str(HERE)) + exe = out / exe_name + obj = out / "redrob_query_setup.obj" + run_in_env(["cl.exe", "/nologo", "/EHsc", "/O2", "/W3", "/WX", "/MT", "/std:c++17", "/utf-8", + "/DUNICODE", "/D_UNICODE", f"/I{out}", f"/Fo{obj}", str(HERE / "redrob_query_setup.cpp"), + "/link", "/SUBSYSTEM:WINDOWS", "/MANIFEST:NO", f"/MACHINE:{machine}", str(res), f"/OUT:{exe}"], + env, str(out)) + if not exe.exists(): + sys.exit(f"link produced no {exe_name}") + return exe + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("--out", required=True) + ap.add_argument("--payload", help="the release's windows zip (omit for a demo-only build)") + ap.add_argument("--version", required=True, help="e.g. 0.4.0") + ap.add_argument("--arch", default="x64", choices=sorted(VCVARS_ARCH), help="target architecture (default x64)") + ap.add_argument("--name", default="Redrob Query Setup.exe", help="output file name of the setup") + ap.add_argument("--design-system", default=str(DEFAULT_DS)) + ap.add_argument("--uninstaller-only", action="store_true", + help="build only /uninstall/Uninstall Redrob Query.exe, so it can be signed first") + ap.add_argument("--uninstaller", help="an already built (and signed) uninstaller to embed, instead of " + "building one") + args = ap.parse_args() + + if not re.fullmatch(r"\d+\.\d+\.\d+", args.version): + sys.exit(f"--version must be X.Y.Z, got {args.version!r}") + for f in (BACKGROUND, LOCKUP_PNG, ICON): + if not f.exists(): + sys.exit(f"missing committed asset {f}; run installer/windows/build-art.py / packaging/brand-assets.sh") + + out = Path(args.out).resolve() + out.mkdir(parents=True, exist_ok=True) + env = vs_dev_env(args.arch) + machine = {"x64": "X64", "x86": "X86", "arm64": "ARM64"}[args.arch] + font = out / "Pretendard-SemiBold.ttf" + font = font if prepare_font(Path(args.design_system), font) else None + + if args.uninstaller_only: + exe = compile_app(env, out / "uninstall", "Uninstall Redrob Query.exe", font, args.version, + payload=None, uninstaller=None, machine=machine) + elif args.payload: + payload = Path(args.payload).resolve() + if not payload.is_file(): + sys.exit(f"payload not found: {payload}") + if args.uninstaller: + uninstaller = Path(args.uninstaller).resolve() + if not uninstaller.is_file(): + sys.exit(f"uninstaller not found: {uninstaller}") + else: + uninstaller = compile_app(env, out / "uninstall", "Uninstall Redrob Query.exe", font, args.version, + payload=None, uninstaller=None, machine=machine) + exe = compile_app(env, out, args.name, font, args.version, payload=payload, uninstaller=uninstaller, machine=machine) + else: + exe = compile_app(env, out, args.name, font, args.version, payload=None, uninstaller=None, machine=machine) + print(f"\nbuilt {exe} {exe.stat().st_size // 1024} KB (payload: {'yes' if args.payload else 'none'})", flush=True) + + +if __name__ == "__main__": + main() diff --git a/installer/windows/redrob_query_setup.cpp b/installer/windows/redrob_query_setup.cpp new file mode 100644 index 0000000..3552e9a --- /dev/null +++ b/installer/windows/redrob_query_setup.cpp @@ -0,0 +1,1494 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// redrob_query_setup -- the branded Windows setup and uninstaller for Redrob Query. +// +// Adapted from the Redrob Launcher setup (redrob-labs/redrob-launcher, installer/windows/), itself adapted +// from the Redrob Canvas and Redrob Browser setup windows; all our own code. The window and the design-system +// rules are the same. The first screen asks the language (English or Korean) and every later screen follows +// it; a "desktop shortcut" box is the one option; there is no Redrob Code opt-in. +// +// What it is: a native Win32 app, no dependencies beyond the Windows SDK. It renders a small borderless, +// rounded, DWM-shadowed window with the Redrob Query lockup, a status line and a progress bar. +// +// Design system (redrob-design-system): +// * Ground "deep" (20-color.md): field is Redrob Black #0A0B0C. type is gray-1 / gray-5; the call to +// action is a white pill with black type. +// * "Blue acts" (20-color.md): the progress fill is action-primary, Redrob Blue #2B52FF. +// * Foot light is `wash-edge` from the brand gradient, pre-rendered into res/background.png from +// res/background.html. +// * Type is Pretendard (30-typography.md, 35-korean.md: it carries Hangul), embedded in-memory when +// available, else Segoe UI Variable with DirectWrite's font fallback for Hangul. +// * Motion: every transition under a third of a second (40-surfaces-and-motion.md). +// +// Language: the first screen is bilingual and offers English / 한국어. The choice is stored under +// HKCU\Software\Redrob\Redrob Query\Language (en, ko) so the next setup preselects it. The screen defaults +// to the value a previous install recorded, else Korean on a Korean Windows, else English; --lang en|ko +// overrides. +// +// Install (per user, no administrator rights): +// 1. The payload is the release's own signed portable zip (bin\redrob-query.exe and the licence set, +// tools/release/stage.py), embedded as RCDATA. It is written to %TEMP% and unpacked with Windows' own +// tar.exe (bsdtar, in System32 since Windows 10 1803, reads zip) into %LOCALAPPDATA%\Programs\Redrob +// Query.new. +// 2. The old install, if any, is renamed aside and the new one renamed into place, so a failed update +// never leaves half of two versions. A running Redrob Query keeps its files locked; the setup asks the +// person to close it, then Retry. +// 3. The uninstaller (this same app built without a payload) is written into the install folder; the +// Start-menu shortcut (with the AppUserModelID ai.redrob.query, which Windows needs to show toast +// notifications from an unpackaged app) and, if chosen, the desktop shortcut are made; the +// Settings > Apps entry, the App Paths entry (Win+R "redrob-query") and the app's own key +// (InstallDir, Language, DesktopShortcutChoice) are written under HKCU. +// 4. Redrob Query is opened (not with --silent). +// The steps report no progress of their own, so the bar is a time-based eased ramp capped at ~90% until +// they finish. +// +// Uninstall (Settings > Apps, or the uninstaller directly): confirm, with "Also delete settings" off by +// default; remove the shortcuts, the registry entries and the install folder. The running uninstaller +// cannot delete itself, so a hidden PowerShell removes it and the folder once it exits. The shared account +// and language files (%APPDATA%\Redrob\Shared) belong to every Redrob app and are never touched. +// +// Flags: +// --lang en|ko start in that language (skips nothing: the language screen still shows, preselected). +// --silent install (or, with --uninstall, remove) with no window; the exit code is the result. +// --delete-data with --uninstall --silent: also delete the app's settings (connection profiles). +// --demo run the whole install sequence on a timer with NO payload. +// --demo-error show the error state immediately. +// --uninstall remove Redrob Query (what Settings > Apps runs). +// --demo-uninstall the uninstall sequence on a timer, removing nothing. +// --render-stages DIR draw each screen to a PNG in DIR, no window. +// (no flag) install the embedded payload for real. + +#ifndef UNICODE +#define UNICODE +#endif +#ifndef NOMINMAX +#define NOMINMAX // keep windows.h's min/max macros from breaking std::min/std::max +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // so propkey.h DEFINES the PKEY_* keys in this one translation unit +#include +#include +#include +#include +#include +#include +#include + +#include "resource.h" +#include "build_defs.h" // generated by build.py: version, resource paths + +#pragma comment(lib, "dwmapi.lib") +#pragma comment(lib, "d2d1.lib") +#pragma comment(lib, "dwrite.lib") +#pragma comment(lib, "windowscodecs.lib") +#pragma comment(lib, "shell32.lib") +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "advapi32.lib") +#pragma comment(lib, "propsys.lib") + +namespace { + +// ---- design-system colors (deep ground) ----------------------------------- +constexpr D2D1_COLOR_F kRedrobBlack = {0x0A / 255.0f, 0x0B / 255.0f, 0x0C / 255.0f, 1.0f}; +constexpr D2D1_COLOR_F kInkPrimary = {0xF8 / 255.0f, 0xF9 / 255.0f, 0xFB / 255.0f, 1.0f}; // gray-1 +constexpr D2D1_COLOR_F kInkMuted = {0x83 / 255.0f, 0x8A / 255.0f, 0x97 / 255.0f, 1.0f}; // ink-muted dark +constexpr D2D1_COLOR_F kActionBlue = {0x2B / 255.0f, 0x52 / 255.0f, 0xFF / 255.0f, 1.0f}; // action-primary +constexpr D2D1_COLOR_F kTrack = {0xFF / 255.0f, 0xFF / 255.0f, 0xFF / 255.0f, 0.14f}; // border-subtle on dark +constexpr D2D1_COLOR_F kDanger = {0xFF / 255.0f, 0x4B / 255.0f, 0x4B / 255.0f, 1.0f}; // accent-red-3 / status-danger +constexpr D2D1_COLOR_F kWhite = {1.0f, 1.0f, 1.0f, 1.0f}; + +// ---- geometry (logical px at 96 DPI) --------------------------------------- +constexpr int kBaseW = 660; +constexpr int kBaseH = 400; +constexpr float kRadius = 14.0f; // rounded corners +constexpr float kCaption = 44.0f; // top drag strip height + +// window messages / timers +constexpr UINT_PTR kTickTimer = 1; +constexpr UINT kTickMs = 16; // ~60fps for the eased bar / fades +constexpr UINT_PTR kPayloadMsgDone = (WM_APP + 1); +constexpr UINT_PTR kUninstallMsgDone = (WM_APP + 2); +constexpr UINT_PTR kStageMsg = (WM_APP + 3); // the worker moved on: wParam is the Stage + +// Results the install and uninstall workers post. 0 is success; anything else is a Win32 error code, except +// these, which have their own sentences. +constexpr DWORD kResultAppRunning = 0xE0000001; // Redrob Query is open from the install folder +constexpr DWORD kResultUnpackFailed = 0xE0000002; // tar.exe could not unpack the payload +constexpr DWORD kResultNoExe = 0xE0000003; // the unpacked payload has no redrob-query.exe + +// Where everything goes. Per user, like every other app that installs without administrator rights. +constexpr wchar_t kInstallSubdir[] = L"\\Programs\\Redrob Query"; // under %LOCALAPPDATA% +constexpr wchar_t kAppExeRel[] = L"\\bin\\redrob-query.exe"; // the portable zip's own layout (stage.py) +constexpr wchar_t kPortableMarker[] = L"\\portable.txt"; // not in this zip; harmless if absent +constexpr wchar_t kUninstallerName[] = L"\\Uninstall Redrob Query.exe"; +constexpr wchar_t kShortcutName[] = L"\\Redrob Query.lnk"; +constexpr wchar_t kAppUserModelId[] = L"ai.redrob.query"; +// Settings > Apps. Its own key. +constexpr wchar_t kArpKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\RedrobQuery"; +// The app's own key: InstallDir, Language, DesktopShortcutChoice. +constexpr wchar_t kAppKey[] = L"Software\\Redrob\\Redrob Query"; +constexpr wchar_t kAppPathsKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\redrob-query.exe"; + +enum class Stage { Preparing, Installing, Opening, Error, Confirm, Removing, Removed, Welcome, Language }; +enum class Mode { Install, Uninstall }; +enum class Lang { En, Ko }; + +// The language every screen after the first draws in. Set by the language screen (or --lang, or the +// value a previous install recorded, or the Windows UI language), read by Tr() at every string. +Lang g_lang = Lang::En; +inline const wchar_t* Tr(const wchar_t* en, const wchar_t* ko) { return g_lang == Lang::Ko ? ko : en; } + +// A tiny COM pointer so cleanup is not a maze of gotos. Move-only: a plain +// memberwise copy would double-Release (and a function returning one by value +// would hand back an already-freed interface). +template struct ComPtr { + T* p = nullptr; + ComPtr() = default; + ComPtr(const ComPtr&) = delete; + ComPtr& operator=(const ComPtr&) = delete; + ComPtr(ComPtr&& o) noexcept : p(o.p) { o.p = nullptr; } + ComPtr& operator=(ComPtr&& o) noexcept { + if (std::addressof(o) != this) { if (p) p->Release(); p = o.p; o.p = nullptr; } + return *this; + } + ~ComPtr() { if (p) p->Release(); } + T** operator&() { return &p; } + T* operator->() const { return p; } + explicit operator bool() const { return p != nullptr; } + void reset() { if (p) { p->Release(); p = nullptr; } } +}; + +std::wstring g_statusOverride; + +// --------------------------------------------------------------------------- +// The app object. One window, Direct2D render target, DirectWrite text, WIC for +// the PNG resources, and a worker thread that runs the payload. +// --------------------------------------------------------------------------- +class SetupApp { + public: + SetupApp(bool demo, bool demoError, Mode mode) : demo_(demo), demoError_(demoError), mode_(mode) {} + + int Run(HINSTANCE hinst); + // --silent: the same work with no window, for scripted installs. + // The process exit code is the worker's result (0 = success). Never opens the app afterwards. + int RunSilent(HINSTANCE hinst, bool deleteData); + + private: + static LRESULT CALLBACK WndProcThunk(HWND, UINT, WPARAM, LPARAM); + LRESULT WndProc(UINT, WPARAM, LPARAM); + + void CreateDeviceResources(); + void DiscardDeviceResources(); + void OnPaint(); + void OnTick(); + void LayoutText(); + RECT ToDevice(float x, float y, float w, float h); + void computeButtonRects(float minX, float closeX, float cy); + + ComPtr LoadPngResource(int resId, ID2D1RenderTarget* rt); + HRESULT DrawScene(); // BeginDraw..EndDraw on rt_, whatever kind of target it is + public: + // --render-stages : draw each screen into a PNG with no window, so the layout can be checked + // on a machine reached over ssh, where no window can be shown. Returns 0 when every file was written. + int RenderStages(HINSTANCE hinst, const std::wstring& dir); + private: + + // payload + static DWORD WINAPI PayloadThunk(LPVOID self); + DWORD RunPayload(); + DWORD InstallApp(); + bool ExtractPayload(std::wstring& outPath); + bool InstallUninstaller(const std::wstring& dir); + void RecordChoices(const std::wstring& dir); + + void StartInstall(); + + // uninstall + static DWORD WINAPI UninstallThunk(LPVOID self); + DWORD RunUninstall(); + void StartUninstall(); + void ScheduleSelfCleanup(); + + void SetStage(Stage s); + void RequestClose(bool force); + void ChooseLanguage(Lang l); + + HINSTANCE hinst_ = nullptr; + HWND hwnd_ = nullptr; + UINT dpi_ = 96; + bool demo_ = false; + bool demoError_ = false; + Mode mode_ = Mode::Install; + bool deleteData_ = false; // the confirm screen's "also delete settings" + bool silent_ = false; + bool desktopShortcut_ = true; // the welcome screen's "create a desktop shortcut"; a previous choice is kept + + // Direct2D / DirectWrite / WIC + ComPtr d2dFactory_; + ComPtr dwFactory_; + ComPtr wicFactory_; + ComPtr rt_; + ComPtr bgBitmap_; + ComPtr lockupBitmap_; + ComPtr fmtStatus_; + ComPtr fmtButton_; + ComPtr fontColl_; + std::wstring fontFamily_ = L"Segoe UI Variable Display"; + + // state + Stage stage_ = Stage::Preparing; + double progress_ = 0.0; // 0..1 displayed + double progressTarget_ = 0.0; + ULONGLONG startTick_ = 0; + ULONGLONG openedAt_ = 0; + ULONGLONG stageTick_ = 0; // when the current stage began; the install ramps from here + bool payloadDone_ = false; + DWORD payloadExit_ = 0; + HANDLE payloadThread_ = nullptr; + std::wstring errorMessage_; + std::wstring extractedPayload_; + bool closing_ = false; + + // hit-tested button rects (device px), recomputed each paint + RECT rcMin_{}, rcClose_{}, rcRetry_{}, rcErrClose_{}, rcCheck_{}; + bool confirmingQuit_ = false; +}; + +SetupApp* g_app = nullptr; + +std::wstring StatusText(Stage s) { + if (!g_statusOverride.empty()) return g_statusOverride; + switch (s) { + case Stage::Language: return L"Choose your language \u00B7 언어를 선택하세요"; + case Stage::Welcome: return Tr(L"Install Redrob Query", L"Redrob Query 설치"); + case Stage::Preparing: return Tr(L"Preparing\u2026", L"준비 중\u2026"); + case Stage::Installing: return Tr(L"Installing\u2026", L"설치 중\u2026"); + case Stage::Opening: return Tr(L"Opening Redrob Query\u2026", L"Redrob Query 여는 중\u2026"); + case Stage::Error: return Tr(L"Something went wrong", L"문제가 생겼어요"); + case Stage::Confirm: return Tr(L"Uninstall Redrob Query?", L"Redrob Query를 제거할까요?"); + case Stage::Removing: return Tr(L"Removing Redrob Query\u2026", L"Redrob Query 제거 중\u2026"); + case Stage::Removed: return Tr(L"Redrob Query has been removed", L"Redrob Query를 제거했어요"); + } + return L""; +} + +// What the error screen says for a worker's result. Retry is on the screen, so each one says what to do. +std::wstring ResultSentence(DWORD code, bool uninstall) { + if (code == kResultAppRunning) + return uninstall ? Tr(L"Redrob Query is open. Close it, then press Retry.", + L"Redrob Query가 실행 중이에요. 창을 닫은 뒤 다시 시도를 누르세요.") + : Tr(L"Redrob Query is open. Close it, then press Retry to update it.", + L"Redrob Query가 실행 중이에요. 창을 닫은 뒤 다시 시도를 눌러 업데이트하세요."); + if (code == kResultUnpackFailed) + return Tr(L"The app files could not be unpacked. Please try again.", L"앱 파일을 풀 수 없었어요. 다시 시도해 주세요."); + if (code == kResultNoExe) + return Tr(L"This setup file is damaged. Please download it again.", L"이 설치 파일이 손상됐어요. 다시 내려받아 주세요."); + if (code == ERROR_DISK_FULL || code == ERROR_HANDLE_DISK_FULL) + return Tr(L"There is not enough free disk space. Free some space, then press Retry.", + L"디스크 공간이 부족해요. 공간을 비운 뒤 다시 시도를 누르세요."); + if (code == ERROR_ACCESS_DENIED || code == ERROR_SHARING_VIOLATION) + return Tr(L"A file is in use. Close Redrob Query and anything opened from its folder, then press Retry.", + L"파일이 사용 중이에요. Redrob Query와 그 폴더에서 연 것을 모두 닫은 뒤 다시 시도를 누르세요."); + const std::wstring n = std::to_wstring(code); + if (uninstall) + return g_lang == Lang::Ko ? L"Redrob Query를 제거하지 못했어요 (코드 " + n + L"). 다시 시도해 주세요." + : L"Redrob Query could not be removed (code " + n + L"). Please try again."; + return g_lang == Lang::Ko ? L"설치를 마치지 못했어요 (코드 " + n + L"). 다시 시도해 주세요." + : L"Setup did not complete (code " + n + L"). Please try again."; +} + +// --------------------------------------------------------------------------- + +// The language a previous install recorded (en, ko), or nothing. +static std::wstring RecordedLanguage() { + HKEY key = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, kAppKey, 0, KEY_READ, &key) != ERROR_SUCCESS) return L""; + wchar_t buf[16] = {}; + DWORD size = sizeof(buf), type = 0; + std::wstring out; + if (RegQueryValueExW(key, L"Language", nullptr, &type, reinterpret_cast(buf), &size) == ERROR_SUCCESS && type == REG_SZ) + out = buf; + RegCloseKey(key); + return out; +} + +// The desktop-shortcut choice a previous install recorded: 1, 0, or -1 for none. +static int RecordedDesktopShortcut() { + HKEY key = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, kAppKey, 0, KEY_READ, &key) != ERROR_SUCCESS) return -1; + wchar_t buf[8] = {}; + DWORD size = sizeof(buf), type = 0; + int out = -1; + if (RegQueryValueExW(key, L"DesktopShortcutChoice", nullptr, &type, reinterpret_cast(buf), &size) == ERROR_SUCCESS && type == REG_SZ) + out = (buf[0] == L'1') ? 1 : 0; + RegCloseKey(key); + return out; +} + +// The language to preselect: --lang, else the recorded one, else Korean on a Korean Windows, else English. +static Lang DefaultLanguage(const std::wstring& flag) { + if (lstrcmpiW(flag.c_str(), L"ko") == 0) return Lang::Ko; + if (lstrcmpiW(flag.c_str(), L"en") == 0) return Lang::En; + const std::wstring recorded = RecordedLanguage(); + if (lstrcmpiW(recorded.c_str(), L"ko") == 0) return Lang::Ko; + if (lstrcmpiW(recorded.c_str(), L"en") == 0) return Lang::En; + return PRIMARYLANGID(GetUserDefaultUILanguage()) == LANG_KOREAN ? Lang::Ko : Lang::En; +} + +int SetupApp::Run(HINSTANCE hinst) { + hinst_ = hinst; + g_app = this; + + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + (void)hr; + + D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory_); + DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast(&dwFactory_)); + CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&wicFactory_)); + + WNDCLASSEX wc = {sizeof(wc)}; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = &SetupApp::WndProcThunk; + wc.hInstance = hinst; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.lpszClassName = L"RedrobQuerySetupWindow"; + wc.hIcon = LoadIconW(hinst, MAKEINTRESOURCEW(IDI_APPICON)); + wc.hIconSm = wc.hIcon; + RegisterClassEx(&wc); + + // Center on the primary monitor's work area. + dpi_ = GetDpiForSystem(); + int w = MulDiv(kBaseW, dpi_, 96); + int h = MulDiv(kBaseH, dpi_, 96); + RECT wa{}; + SystemParametersInfo(SPI_GETWORKAREA, 0, &wa, 0); + int x = wa.left + ((wa.right - wa.left) - w) / 2; + int y = wa.top + ((wa.bottom - wa.top) - h) / 2; + + // Borderless (WS_POPUP). WS_THICKFRAME is kept out; a DWM shadow is attached + // separately so a popup still casts one. + hwnd_ = CreateWindowExW(WS_EX_APPWINDOW, wc.lpszClassName, + mode_ == Mode::Uninstall ? Tr(L"Uninstall Redrob Query", L"Redrob Query 제거") + : Tr(L"Redrob Query Setup", L"Redrob Query 설치"), + WS_POPUP, x, y, w, h, nullptr, nullptr, hinst, this); + if (!hwnd_) return 2; + + // DWM: a real shadow on a borderless window + rounded corners (Win11). + MARGINS m = {1, 1, 1, 1}; + DwmExtendFrameIntoClientArea(hwnd_, &m); + DWM_WINDOW_CORNER_PREFERENCE corner = DWMWCP_ROUND; + DwmSetWindowAttribute(hwnd_, DWMWA_WINDOW_CORNER_PREFERENCE, &corner, sizeof(corner)); + BOOL dark = TRUE; + DwmSetWindowAttribute(hwnd_, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark, sizeof(dark)); + + LayoutText(); + + startTick_ = GetTickCount64(); + if (mode_ == Mode::Uninstall) { + SetStage(Stage::Confirm); + } else if (demoError_) { + errorMessage_ = L"The installer could not be started. Please try again."; + SetStage(Stage::Error); + } else { + // Nothing is installed until the person presses Install. The language comes first: every screen + // after it, every later screen follows the choice. + const int previous = RecordedDesktopShortcut(); + if (previous >= 0) desktopShortcut_ = previous == 1; + SetStage(Stage::Language); + } + + ShowWindow(hwnd_, SW_SHOW); + UpdateWindow(hwnd_); + + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0) > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if (payloadThread_) { + WaitForSingleObject(payloadThread_, 5000); + CloseHandle(payloadThread_); + } + // Every COM object goes BEFORE CoUninitialize. Left to the destructor, the WIC factory was released after + // COM was torn down, and the process died with an access violation on its way out of a successful install. + DiscardDeviceResources(); + fmtStatus_.reset(); + fmtButton_.reset(); + fontColl_.reset(); + wicFactory_.reset(); + dwFactory_.reset(); + d2dFactory_.reset(); + CoUninitialize(); + return (int)msg.wParam; +} + +void SetupApp::LayoutText() { + // Load Pretendard SemiBold from the embedded TTF into a private font + // collection; fall back to Segoe UI Variable if the resource is absent. + HRSRC res = FindResourceW(hinst_, MAKEINTRESOURCEW(IDR_FONT_SANS), RT_RCDATA); + if (res) { + HGLOBAL h = LoadResource(hinst_, res); + void* data = LockResource(h); + DWORD sz = SizeofResource(hinst_, res); + if (data && sz) { + DWORD nFonts = 0; + HANDLE added = AddFontMemResourceEx(data, sz, nullptr, &nFonts); + if (added) fontFamily_ = L"Pretendard SemiBold"; + } + } + dwFactory_->CreateTextFormat(fontFamily_.c_str(), nullptr, DWRITE_FONT_WEIGHT_SEMI_BOLD, + DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, + 17.0f, L"en-us", &fmtStatus_); + if (fmtStatus_) { + fmtStatus_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); + fmtStatus_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER); + } + dwFactory_->CreateTextFormat(fontFamily_.c_str(), nullptr, DWRITE_FONT_WEIGHT_SEMI_BOLD, + DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, + 13.0f, L"en-us", &fmtButton_); + if (fmtButton_) { + fmtButton_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); + fmtButton_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER); + } +} + +LRESULT CALLBACK SetupApp::WndProcThunk(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + if (msg == WM_NCCREATE) { + auto* cs = reinterpret_cast(lp); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + } + auto* self = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (self) { self->hwnd_ = hwnd; return self->WndProc(msg, wp, lp); } + return DefWindowProc(hwnd, msg, wp, lp); +} + +LRESULT SetupApp::WndProc(UINT msg, WPARAM wp, LPARAM lp) { + switch (msg) { + case WM_PAINT: OnPaint(); return 0; + case WM_ERASEBKGND: return 1; + + case WM_TIMER: + if (wp == kTickTimer) OnTick(); + return 0; + + case kStageMsg: + if (stage_ != Stage::Error) SetStage((Stage)wp); + return 0; + + case kPayloadMsgDone: { + payloadDone_ = true; + payloadExit_ = (DWORD)wp; + if (payloadExit_ == 0) { + progressTarget_ = 1.0; + SetStage(Stage::Opening); + } else { + errorMessage_ = ResultSentence(payloadExit_, false); + SetStage(Stage::Error); + } + return 0; + } + + case kUninstallMsgDone: { + const DWORD code = (DWORD)wp; + if (code == 0) { + progressTarget_ = 1.0; + progress_ = 1.0; + SetStage(Stage::Removed); + if (!demo_) ScheduleSelfCleanup(); + } else { + errorMessage_ = ResultSentence(code, true); + SetStage(Stage::Error); + } + return 0; + } + + case WM_GETDPISCALEDSIZE: return TRUE; + case WM_DPICHANGED: { + dpi_ = HIWORD(wp); + RECT* r = reinterpret_cast(lp); + SetWindowPos(hwnd_, nullptr, r->left, r->top, r->right - r->left, r->bottom - r->top, + SWP_NOZORDER | SWP_NOACTIVATE); + DiscardDeviceResources(); + InvalidateRect(hwnd_, nullptr, FALSE); + return 0; + } + + case WM_SIZE: DiscardDeviceResources(); return 0; + + case WM_LBUTTONDOWN: { + POINT pt{GET_X_LPARAM(lp), GET_Y_LPARAM(lp)}; + if (PtInRect(&rcClose_, pt)) { RequestClose(false); return 0; } + if (PtInRect(&rcMin_, pt)) { ShowWindow(hwnd_, SW_MINIMIZE); return 0; } + if (stage_ == Stage::Language) { + // The two pills ARE the choice: picking one moves on, in that language. + if (PtInRect(&rcRetry_, pt)) { ChooseLanguage(Lang::En); return 0; } + if (PtInRect(&rcErrClose_, pt)) { ChooseLanguage(Lang::Ko); return 0; } + } + if (stage_ == Stage::Confirm) { + if (PtInRect(&rcCheck_, pt)) { deleteData_ = !deleteData_; InvalidateRect(hwnd_, nullptr, FALSE); return 0; } + if (PtInRect(&rcRetry_, pt)) { StartUninstall(); return 0; } // "Uninstall" + if (PtInRect(&rcErrClose_, pt)) { RequestClose(true); return 0; } // "Cancel" + } + if (stage_ == Stage::Welcome) { + if (PtInRect(&rcCheck_, pt)) { + desktopShortcut_ = !desktopShortcut_; + InvalidateRect(hwnd_, nullptr, FALSE); + return 0; + } + if (PtInRect(&rcRetry_, pt)) { StartInstall(); return 0; } // "Install" + if (PtInRect(&rcErrClose_, pt)) { SetStage(Stage::Language); return 0; } // "Back" + } + if (stage_ == Stage::Removed && PtInRect(&rcRetry_, pt)) { RequestClose(true); return 0; } // "Close" + if (stage_ == Stage::Error && mode_ == Mode::Uninstall && PtInRect(&rcRetry_, pt)) { + errorMessage_.clear(); + SetStage(Stage::Confirm); // back to the question, so a retry is still a choice + return 0; + } + if (stage_ == Stage::Error && PtInRect(&rcRetry_, pt)) { + errorMessage_.clear(); + StartInstall(); + return 0; + } + if (stage_ == Stage::Error && PtInRect(&rcErrClose_, pt)) { RequestClose(true); return 0; } + // Anywhere else on the caption strip drags the window. + if (pt.y < MulDiv((int)kCaption, dpi_, 96)) { + ReleaseCapture(); + SendMessage(hwnd_, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + return 0; + } + + case WM_KEYDOWN: + if (wp == VK_ESCAPE) { RequestClose(false); return 0; } + if (stage_ == Stage::Language) { + // Left/Right or E/K pick; Enter keeps the preselected language. + if (wp == VK_LEFT || wp == 'E') { ChooseLanguage(Lang::En); return 0; } + if (wp == VK_RIGHT || wp == 'K') { ChooseLanguage(Lang::Ko); return 0; } + if (wp == VK_RETURN) { ChooseLanguage(g_lang); return 0; } + return 0; + } + if (stage_ == Stage::Confirm && wp == VK_SPACE) { + deleteData_ = !deleteData_; + InvalidateRect(hwnd_, nullptr, FALSE); + return 0; + } + if (stage_ == Stage::Confirm && wp == VK_RETURN) { StartUninstall(); return 0; } + if (stage_ == Stage::Welcome && wp == VK_SPACE) { + desktopShortcut_ = !desktopShortcut_; + InvalidateRect(hwnd_, nullptr, FALSE); + return 0; + } + if (stage_ == Stage::Welcome && wp == VK_RETURN) { StartInstall(); return 0; } + if (stage_ == Stage::Welcome && wp == VK_BACK) { SetStage(Stage::Language); return 0; } + if (stage_ == Stage::Removed && wp == VK_RETURN) { RequestClose(true); return 0; } + return 0; + + case WM_SYSKEYDOWN: + if (wp == VK_F4) { RequestClose(false); return 0; } // Alt+F4 + return DefWindowProc(hwnd_, msg, wp, lp); + + case WM_CLOSE: + RequestClose(false); + return 0; + + case WM_DESTROY: + KillTimer(hwnd_, kTickTimer); + DiscardDeviceResources(); + PostQuitMessage(0); + return 0; + } + return DefWindowProc(hwnd_, msg, wp, lp); +} + +void SetupApp::RequestClose(bool force) { + // While installing, Esc / close ask for confirmation first (do not abandon an + // install silently). In demo and error states, close immediately. + bool installing = (stage_ == Stage::Installing || stage_ == Stage::Preparing) && !demo_ && !force && + mode_ == Mode::Install; + // A removal cannot be safely abandoned halfway; closing waits for it to finish. + if (stage_ == Stage::Removing && !force) return; + if (installing) { + int r = MessageBoxW(hwnd_, + Tr(L"Redrob Query is still installing. Stop the installation and quit?", + L"Redrob Query를 아직 설치하고 있어요. 설치를 멈추고 끝낼까요?"), + Tr(L"Redrob Query Setup", L"Redrob Query 설치"), MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); + if (r != IDYES) return; + } + DestroyWindow(hwnd_); +} + +void SetupApp::SetStage(Stage s) { + if (s != stage_) stageTick_ = GetTickCount64(); + stage_ = s; + g_statusOverride.clear(); + if (s == Stage::Opening) openedAt_ = GetTickCount64(); + if (s == Stage::Error || s == Stage::Confirm || s == Stage::Removed || s == Stage::Welcome || s == Stage::Language) + KillTimer(hwnd_, kTickTimer); + if (hwnd_) InvalidateRect(hwnd_, nullptr, FALSE); +} + +// The language screen's answer: from here on every string, including the window title, is in `l`. +void SetupApp::ChooseLanguage(Lang l) { + g_lang = l; + if (hwnd_) SetWindowTextW(hwnd_, mode_ == Mode::Uninstall ? Tr(L"Uninstall Redrob Query", L"Redrob Query 제거") + : Tr(L"Redrob Query Setup", L"Redrob Query 설치")); + SetStage(Stage::Welcome); +} + +// The Install button (and Retry). +void SetupApp::StartInstall() { + progress_ = progressTarget_ = 0.0; + startTick_ = GetTickCount64(); + payloadDone_ = false; + SetStage(Stage::Preparing); + SetTimer(hwnd_, kTickTimer, kTickMs, nullptr); + if (!demo_) { + if (payloadThread_) { WaitForSingleObject(payloadThread_, 5000); CloseHandle(payloadThread_); } + payloadThread_ = CreateThread(nullptr, 0, &SetupApp::PayloadThunk, this, 0, nullptr); + } +} + +static double EaseOut(double t) { return 1 - (1 - t) * (1 - t); } + +void SetupApp::OnTick() { + ULONGLONG elapsed = GetTickCount64() - startTick_; + + if (mode_ == Mode::Uninstall) { + // Removal reports no progress either: the same eased ramp, over ~12s, capped until it finishes. + if (stage_ == Stage::Removing) { + double t = std::min(1.0, (GetTickCount64() - startTick_) / 12000.0); + progressTarget_ = 0.90 * (1 - (1 - t) * (1 - t)); + } + } else if (demo_ && stage_ != Stage::Error) { + // Scripted demo, same shape as a real run: Preparing (0-0.8s) -> Installing (0.8-5s, bar to ~0.9) + // -> Opening (5-6.5s) -> close. + if (elapsed < 800) { if (stage_ != Stage::Preparing) SetStage(Stage::Preparing); progressTarget_ = 0.04; } + else if (elapsed < 5000) { if (stage_ != Stage::Installing) SetStage(Stage::Installing); + progressTarget_ = 0.90 * EaseOut((elapsed - 800) / 4200.0); } + else if (elapsed < 6500) { if (stage_ != Stage::Opening) SetStage(Stage::Opening); progressTarget_ = 1.0; } + else { DestroyWindow(hwnd_); return; } + } else if (!demo_) { + // Real run. Preparing turns into Installing after 0.6s; the bar is a time-based eased ramp, capped + // until the worker finishes. + if (stage_ == Stage::Preparing && elapsed > 600) SetStage(Stage::Installing); + const double since = (double)(GetTickCount64() - stageTick_); + if (stage_ == Stage::Installing && !payloadDone_) { + progressTarget_ = 0.90 * EaseOut(std::min(1.0, since / 30000.0)); // approach over ~30s + } + if (stage_ == Stage::Opening) { + progressTarget_ = 1.0; + // Close on a fixed delay after Opening began: the app has been started (InstallApp), so + // there is nothing left for this window to do but get out of the way. + if (GetTickCount64() - openedAt_ > 1400) { DestroyWindow(hwnd_); return; } + } + } + + // Ease displayed progress toward the target (motion < 1/3 s per step). + progress_ += (progressTarget_ - progress_) * 0.12; + if (progress_ > 0.9999) progress_ = 1.0; + InvalidateRect(hwnd_, nullptr, FALSE); +} + +// ---- rendering ------------------------------------------------------------- + +void SetupApp::CreateDeviceResources() { + if (rt_) return; + RECT rc; GetClientRect(hwnd_, &rc); + D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)); + ComPtr hwndRt; + HRESULT hr = d2dFactory_->CreateHwndRenderTarget( + props, D2D1::HwndRenderTargetProperties(hwnd_, size, D2D1_PRESENT_OPTIONS_NONE), &hwndRt); + (void)hr; + if (!hwndRt) return; + rt_.p = hwndRt.p; // ID2D1HwndRenderTarget is an ID2D1RenderTarget; rt_ takes the reference + hwndRt.p = nullptr; + rt_->SetDpi((float)dpi_, (float)dpi_); + bgBitmap_ = LoadPngResource(IDR_BACKGROUND_PNG, rt_.p); + lockupBitmap_ = LoadPngResource(IDR_LOCKUP_PNG, rt_.p); +} + +void SetupApp::DiscardDeviceResources() { + bgBitmap_.reset(); + lockupBitmap_.reset(); + rt_.reset(); +} + +ComPtr SetupApp::LoadPngResource(int resId, ID2D1RenderTarget* rt) { + ComPtr out; + HRSRC res = FindResourceW(hinst_, MAKEINTRESOURCEW(resId), RT_RCDATA); + if (!res) return out; + HGLOBAL h = LoadResource(hinst_, res); + void* data = LockResource(h); + DWORD sz = SizeofResource(hinst_, res); + if (!data || !sz) return out; + + ComPtr stream; + wicFactory_->CreateStream(&stream); + stream->InitializeFromMemory((BYTE*)data, sz); + ComPtr dec; + if (FAILED(wicFactory_->CreateDecoderFromStream(stream.p, nullptr, + WICDecodeMetadataCacheOnLoad, &dec))) return out; + ComPtr frame; + dec->GetFrame(0, &frame); + ComPtr conv; + wicFactory_->CreateFormatConverter(&conv); + conv->Initialize(frame.p, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, + nullptr, 0.0, WICBitmapPaletteTypeMedianCut); + rt->CreateBitmapFromWicBitmap(conv.p, nullptr, &out); + return out; +} + +void SetupApp::OnPaint() { + CreateDeviceResources(); + PAINTSTRUCT ps; BeginPaint(hwnd_, &ps); + if (!rt_) { EndPaint(hwnd_, &ps); return; } + if (DrawScene() == D2DERR_RECREATE_TARGET) DiscardDeviceResources(); + EndPaint(hwnd_, &ps); +} + +HRESULT SetupApp::DrawScene() { + rt_->BeginDraw(); + rt_->Clear(kRedrobBlack); + + const float W = (float)kBaseW, H = (float)kBaseH; + + // Background art (deep ground + wash-edge), drawn to fill the window. + if (bgBitmap_) { + rt_->DrawBitmap(bgBitmap_.p, D2D1::RectF(0, 0, W, H), 1.0f, + D2D1_BITMAP_INTERPOLATION_MODE_LINEAR); + } + + ComPtr ink, muted, blue, track, danger, white; + rt_->CreateSolidColorBrush(kInkPrimary, &ink); + rt_->CreateSolidColorBrush(kInkMuted, &muted); + rt_->CreateSolidColorBrush(kActionBlue, &blue); + rt_->CreateSolidColorBrush(kTrack, &track); + rt_->CreateSolidColorBrush(kDanger, &danger); + rt_->CreateSolidColorBrush(kWhite, &white); + + // ---- caption glyphs (min / close), top-right ----------------------------- + const float gl = 14.0f; // glyph half-size + const float cy = kCaption / 2.0f; + const float closeX = W - 28.0f; + const float minX = W - 64.0f; + computeButtonRects(minX, closeX, cy); + // close (x) + rt_->DrawLine(D2D1::Point2F(closeX - gl / 2, cy - gl / 2), + D2D1::Point2F(closeX + gl / 2, cy + gl / 2), ink.p, 1.6f); + rt_->DrawLine(D2D1::Point2F(closeX + gl / 2, cy - gl / 2), + D2D1::Point2F(closeX - gl / 2, cy + gl / 2), ink.p, 1.6f); + // minimize (-) + rt_->DrawLine(D2D1::Point2F(minX - gl / 2, cy), + D2D1::Point2F(minX + gl / 2, cy), ink.p, 1.6f); + + // ---- lockup (product mark + name), centred in the upper third --------------- + // res/lockup.png, built by installer/windows/build-art.py as the design system builds its product + // lockups. Same size and place as the Redrob Query and Redrob Browser setups'. + if (lockupBitmap_) { + D2D1_SIZE_F ls = lockupBitmap_->GetSize(); + float lh = 34.0f; // >= 18px min screen (10-logo.md) + float lw = ls.width * (lh / ls.height); + float lx = (W - lw) / 2.0f; + float ly = H * 0.30f; + rt_->DrawBitmap(lockupBitmap_.p, D2D1::RectF(lx, ly, lx + lw, ly + lh), 1.0f, + D2D1_BITMAP_INTERPOLATION_MODE_LINEAR); + } + + // ---- status line, centered ------------------------------------------------ + std::wstring status = StatusText(stage_); + D2D1_RECT_F statusRc = D2D1::RectF(40, H * 0.46f, W - 40, H * 0.46f + 30); + if (fmtStatus_) rt_->DrawTextW(status.c_str(), (UINT32)status.size(), fmtStatus_.p, statusRc, + (stage_ == Stage::Error ? danger.p : ink.p)); + + ComPtr black; + rt_->CreateSolidColorBrush(kRedrobBlack, &black); + // A white pill (the one prominent action, black type) and an outline pill, as the error state draws them. + auto pill = [&](float x, float y, float w, float h, const wchar_t* label, bool primary) { + D2D1_ROUNDED_RECT rr = {D2D1::RectF(x, y, x + w, y + h), h / 2, h / 2}; + if (primary) rt_->FillRoundedRectangle(rr, white.p); + else rt_->DrawRoundedRectangle(rr, track.p, 1.4f); + if (fmtButton_) rt_->DrawTextW(label, (UINT32)wcslen(label), fmtButton_.p, + D2D1::RectF(x, y, x + w, y + h), primary ? black.p : ink.p); + }; + const float pw = 120.0f, ph = 38.0f, py = H * 0.72f; + + // A 16px box and its label, centred together as one row. The whole row is the hit target, not just the box. + auto checkRow = [&](const wchar_t* label, bool checked) { + float labelW = 200.0f; + ComPtr layout; + if (fmtButton_ && SUCCEEDED(dwFactory_->CreateTextLayout(label, (UINT32)wcslen(label), fmtButton_.p, + W, 24.0f, &layout))) { + layout->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); + DWRITE_TEXT_METRICS tm{}; + if (SUCCEEDED(layout->GetMetrics(&tm))) labelW = tm.widthIncludingTrailingWhitespace; + } + const float box = 16.0f, gap = 10.0f, rowY = H * 0.615f; + const float rowX = (W - (box + gap + labelW)) / 2.0f; + D2D1_ROUNDED_RECT boxRR = {D2D1::RectF(rowX, rowY, rowX + box, rowY + box), 4.0f, 4.0f}; + if (checked) { + rt_->FillRoundedRectangle(boxRR, blue.p); + rt_->DrawLine(D2D1::Point2F(rowX + 4.0f, rowY + 8.5f), D2D1::Point2F(rowX + 7.0f, rowY + 11.5f), white.p, 1.8f); + rt_->DrawLine(D2D1::Point2F(rowX + 7.0f, rowY + 11.5f), D2D1::Point2F(rowX + 12.5f, rowY + 5.0f), white.p, 1.8f); + } else { + rt_->DrawRoundedRectangle(boxRR, muted.p, 1.4f); + } + if (layout) rt_->DrawTextLayout(D2D1::Point2F(rowX + box + gap, rowY + box / 2 - 12.0f), layout.p, ink.p); + rcCheck_ = ToDevice(rowX - 6.0f, rowY - 6.0f, box + gap + labelW + 12.0f, box + 12.0f); + }; + auto bodyLine = [&](const wchar_t* body) { + // Tall enough for two lines (the language screen says it in both languages). + if (fmtButton_) rt_->DrawTextW(body, (UINT32)wcslen(body), fmtButton_.p, + D2D1::RectF(60, H * 0.52f, W - 60, H * 0.62f), muted.p); + }; + auto twoPills = [&](const wchar_t* primary, const wchar_t* secondary) { + const float ux = W / 2.0f - pw - 8.0f, cx = W / 2.0f + 8.0f; + rcRetry_ = ToDevice(ux, py, pw, ph); + rcErrClose_ = ToDevice(cx, py, pw, ph); + pill(ux, py, pw, ph, primary, true); + pill(cx, py, pw, ph, secondary, false); + }; + + if (stage_ == Stage::Language) { + // Bilingual by nature: it comes before the choice. The two pills are the choice; the preselected + // one (a previous install's answer, or the Windows UI language) is drawn as the primary. + bodyLine(L"Setup will continue in this language.\n설치 과정이 이 언어로 진행됩니다."); + rcCheck_ = RECT{}; + const float ux = W / 2.0f - pw - 8.0f, cx = W / 2.0f + 8.0f; + rcRetry_ = ToDevice(ux, py, pw, ph); // English + rcErrClose_ = ToDevice(cx, py, pw, ph); // 한국어 + pill(ux, py, pw, ph, L"English", g_lang == Lang::En); + pill(cx, py, pw, ph, L"한국어", g_lang == Lang::Ko); + } else if (stage_ == Stage::Welcome) { + bodyLine(Tr(L"Installs for this Windows account. No administrator rights needed.", + L"이 Windows 계정에만 설치됩니다. 관리자 권한이 필요하지 않아요.")); + checkRow(Tr(L"Create a desktop shortcut", L"바탕 화면에 바로 가기 만들기"), desktopShortcut_); + twoPills(Tr(L"Install", L"설치"), Tr(L"Back", L"뒤로")); + } else if (stage_ == Stage::Confirm) { + bodyLine(Tr(L"Redrob Query will be removed from this PC. Your databases are not touched.", + L"이 PC에서 Redrob Query를 제거합니다. 데이터베이스는 건드리지 않아요.")); + checkRow(Tr(L"Also delete its settings and connection profiles", L"설정과 연결 프로필도 삭제"), deleteData_); + twoPills(Tr(L"Uninstall", L"제거"), Tr(L"Cancel", L"취소")); + } else if (stage_ == Stage::Removed) { + const wchar_t* body = deleteData_ ? Tr(L"Its settings and connection profiles were deleted too.", L"설정과 연결 프로필도 삭제했어요.") + : Tr(L"Its settings stay on this PC for a later install.", L"설정은 다음 설치를 위해 이 PC에 남아 있어요."); + if (fmtButton_) rt_->DrawTextW(body, (UINT32)wcslen(body), fmtButton_.p, + D2D1::RectF(60, H * 0.53f, W - 60, H * 0.59f), muted.p); + const float x = (W - pw) / 2.0f; + rcRetry_ = ToDevice(x, py, pw, ph); // Close + rcErrClose_ = RECT{}; + pill(x, py, pw, ph, Tr(L"Close", L"닫기"), true); + + } else if (stage_ != Stage::Error) { + // ---- progress bar: thin, rounded, blue fill on a faint track ----------- + const float barW = W * 0.76f, barH = 6.0f; + const float barX = (W - barW) / 2.0f, barY = H * 0.60f; + D2D1_ROUNDED_RECT trackRR = {D2D1::RectF(barX, barY, barX + barW, barY + barH), barH / 2, barH / 2}; + rt_->FillRoundedRectangle(trackRR, track.p); + float fillW = (float)(barW * progress_); + if (fillW > barH) { + D2D1_ROUNDED_RECT fillRR = {D2D1::RectF(barX, barY, barX + fillW, barY + barH), barH / 2, barH / 2}; + rt_->FillRoundedRectangle(fillRR, blue.p); + } + } else { + // ---- error state: message + Retry (white pill, black type) / Close ----- + D2D1_RECT_F msgRc = D2D1::RectF(60, H * 0.54f, W - 60, H * 0.66f); + if (fmtButton_) rt_->DrawTextW(errorMessage_.c_str(), (UINT32)errorMessage_.size(), + fmtButton_.p, msgRc, muted.p); + // Retry pill (white fill, black type) -- the single prominent CTA. + const float pw = 120.0f, ph = 38.0f, py = H * 0.72f; + const float rx = W / 2.0f - pw - 8.0f, cx = W / 2.0f + 8.0f; + rcRetry_ = ToDevice(rx, py, pw, ph); + rcErrClose_ = ToDevice(cx, py, pw, ph); + D2D1_ROUNDED_RECT pill = {D2D1::RectF(rx, py, rx + pw, py + ph), ph / 2, ph / 2}; + rt_->FillRoundedRectangle(pill, white.p); + D2D1_RECT_F rtxt = D2D1::RectF(rx, py, rx + pw, py + ph); + const wchar_t* retry = Tr(L"Retry", L"다시 시도"); + if (fmtButton_) rt_->DrawTextW(retry, (UINT32)wcslen(retry), fmtButton_.p, rtxt, black.p); + // Close (outline) + D2D1_ROUNDED_RECT closeRR = {D2D1::RectF(cx, py, cx + pw, py + ph), ph / 2, ph / 2}; + rt_->DrawRoundedRectangle(closeRR, track.p, 1.4f); + D2D1_RECT_F ctxt = D2D1::RectF(cx, py, cx + pw, py + ph); + const wchar_t* close = Tr(L"Close", L"닫기"); + if (fmtButton_) rt_->DrawTextW(close, (UINT32)wcslen(close), fmtButton_.p, ctxt, ink.p); + } + + return rt_->EndDraw(); +} + +int SetupApp::RenderStages(HINSTANCE hinst, const std::wstring& dir) { + hinst_ = hinst; + g_app = this; + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory_); + DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast(&dwFactory_)); + CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wicFactory_)); + if (!d2dFactory_ || !dwFactory_ || !wicFactory_) return 2; + LayoutText(); + dpi_ = 192; // 200%: the sharpest the art is drawn for + const UINT pw = MulDiv(kBaseW, dpi_, 96), ph = MulDiv(kBaseH, dpi_, 96); + int failures = 0; + struct Shot { Stage stage; double progress; const wchar_t* name; Lang lang; }; + const Shot shots[] = { + {Stage::Language, 0.0, L"0-language.png", Lang::En}, + {Stage::Welcome, 0.0, L"1-welcome.png", Lang::En}, + {Stage::Welcome, 0.0, L"1-welcome-ko.png", Lang::Ko}, + {Stage::Installing, 0.55, L"2-installing.png", Lang::En}, + {Stage::Installing, 0.55, L"2-installing-ko.png", Lang::Ko}, + {Stage::Opening, 1.0, L"3-opening.png", Lang::En}, + {Stage::Error, 0.0, L"4-error.png", Lang::En}, + {Stage::Error, 0.0, L"4-error-ko.png", Lang::Ko}, + {Stage::Confirm, 0.0, L"5-uninstall-confirm.png", Lang::En}, + {Stage::Confirm, 0.0, L"5-uninstall-confirm-ko.png", Lang::Ko}, + {Stage::Removing, 0.4, L"6-removing.png", Lang::En}, + {Stage::Removed, 1.0, L"7-removed.png", Lang::En}, + }; + for (const Shot& s : shots) { + ComPtr bitmap; + if (FAILED(wicFactory_->CreateBitmap(pw, ph, GUID_WICPixelFormat32bppPBGRA, WICBitmapCacheOnLoad, &bitmap))) { + ++failures; continue; + } + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), (float)dpi_, (float)dpi_); + DiscardDeviceResources(); + if (FAILED(d2dFactory_->CreateWicBitmapRenderTarget(bitmap.p, props, &rt_))) { ++failures; continue; } + bgBitmap_ = LoadPngResource(IDR_BACKGROUND_PNG, rt_.p); + lockupBitmap_ = LoadPngResource(IDR_LOCKUP_PNG, rt_.p); + mode_ = (s.stage == Stage::Confirm || s.stage == Stage::Removing || s.stage == Stage::Removed) + ? Mode::Uninstall : Mode::Install; + stage_ = s.stage; + progress_ = s.progress; + g_lang = s.lang; + if (s.stage == Stage::Error) errorMessage_ = ResultSentence(kResultAppRunning, false); + if (FAILED(DrawScene())) { ++failures; continue; } + + const std::wstring path = dir + L"\\" + s.name; + ComPtr stream; + ComPtr encoder; + ComPtr frame; + bool ok = SUCCEEDED(wicFactory_->CreateStream(&stream)) && + SUCCEEDED(stream->InitializeFromFilename(path.c_str(), GENERIC_WRITE)) && + SUCCEEDED(wicFactory_->CreateEncoder(GUID_ContainerFormatPng, nullptr, &encoder)) && + SUCCEEDED(encoder->Initialize(stream.p, WICBitmapEncoderNoCache)) && + SUCCEEDED(encoder->CreateNewFrame(&frame, nullptr)) && + SUCCEEDED(frame->Initialize(nullptr)) && + SUCCEEDED(frame->WriteSource(bitmap.p, nullptr)) && + SUCCEEDED(frame->Commit()) && SUCCEEDED(encoder->Commit()); + if (!ok) ++failures; + } + DiscardDeviceResources(); + fmtStatus_.reset(); + fmtButton_.reset(); + wicFactory_.reset(); + dwFactory_.reset(); + d2dFactory_.reset(); + CoUninitialize(); + return failures; +} + +// device-pixel rect from logical (dip) coordinates +RECT SetupApp::ToDevice(float x, float y, float w, float h) { + float s = dpi_ / 96.0f; + RECT r; + r.left = (LONG)(x * s); r.top = (LONG)(y * s); + r.right = (LONG)((x + w) * s); r.bottom = (LONG)((y + h) * s); + return r; +} + +void SetupApp::computeButtonRects(float minX, float closeX, float cy) { + float s = dpi_ / 96.0f; + auto box = [&](float cx) { + RECT r; r.left = (LONG)((cx - 16) * s); r.top = (LONG)((cy - 16) * s); + r.right = (LONG)((cx + 16) * s); r.bottom = (LONG)((cy + 16) * s); return r; + }; + rcMin_ = box(minX); + rcClose_ = box(closeX); +} + +// ---- payload --------------------------------------------------------------- + +DWORD WINAPI SetupApp::PayloadThunk(LPVOID self) { + return reinterpret_cast(self)->RunPayload(); +} + +// %LOCALAPPDATA%, or empty. +static std::wstring LocalAppData() { + wchar_t local[MAX_PATH]; + if (FAILED(SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, local))) return L""; + return local; +} + +static std::wstring KnownFolder(REFKNOWNFOLDERID id) { + PWSTR p = nullptr; + std::wstring out; + if (SUCCEEDED(SHGetKnownFolderPath(id, 0, nullptr, &p))) out = p; + CoTaskMemFree(p); + return out; +} + +static std::wstring InstallDir() { + const std::wstring local = LocalAppData(); + return local.empty() ? L"" : local + kInstallSubdir; +} + +static bool Exists(const std::wstring& p) { return GetFileAttributesW(p.c_str()) != INVALID_FILE_ATTRIBUTES; } + +// The one subfolder of `dir` that holds `rel` (a path starting with a backslash), or empty. +static std::wstring SingleSubfolderWith(const std::wstring& dir, const wchar_t* rel) { + std::wstring found; + WIN32_FIND_DATAW fd; + HANDLE find = FindFirstFileW((dir + L"\\*").c_str(), &fd); + if (find == INVALID_HANDLE_VALUE) return found; + do { + const std::wstring name = fd.cFileName; + if (name == L"." || name == L".." || !(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue; + const std::wstring sub = dir + L"\\" + name; + if (Exists(sub + rel)) { found = sub; break; } + } while (FindNextFileW(find, &fd)); + FindClose(find); + return found; +} + +// Write one RCDATA resource to `path`. False if the resource is missing or the write was short. +static bool WriteResource(HINSTANCE hinst, int id, const std::wstring& path) { + HRSRC res = FindResourceW(hinst, MAKEINTRESOURCEW(id), RT_RCDATA); + if (!res) return false; + HGLOBAL h = LoadResource(hinst, res); + const void* data = LockResource(h); + const DWORD sz = SizeofResource(hinst, res); + if (!data || !sz) return false; + HANDLE f = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (f == INVALID_HANDLE_VALUE) return false; + DWORD written = 0; + const BOOL ok = WriteFile(f, data, sz, &written, nullptr); + CloseHandle(f); + if (!ok || written != sz) { DeleteFileW(path.c_str()); return false; } + return true; +} + +bool SetupApp::ExtractPayload(std::wstring& outPath) { + wchar_t tmp[MAX_PATH]; + GetTempPathW(MAX_PATH, tmp); + wchar_t name[MAX_PATH]; + if (!GetTempFileNameW(tmp, L"rcs", 0, name)) return false; + const std::wstring zip = std::wstring(name) + L".zip"; + DeleteFileW(name); // the 0-byte file GetTempFileName created + if (!WriteResource(hinst_, IDR_PAYLOAD, zip)) return false; + outPath = zip; + return true; +} + +// Delete a directory tree. Paths are checked by the callers to be inside the install folder or our own +// data folders; this never runs on anything it was not given. `keep` (a full path) is left in place. +static DWORD DeleteTree(const std::wstring& dir, const std::wstring& keep = L"") { + DWORD firstError = 0; + WIN32_FIND_DATAW fd; + HANDLE find = FindFirstFileW((dir + L"\\*").c_str(), &fd); + if (find != INVALID_HANDLE_VALUE) { + do { + const std::wstring name = fd.cFileName; + if (name == L"." || name == L"..") continue; + const std::wstring path = dir + L"\\" + name; + if (!keep.empty() && lstrcmpiW(path.c_str(), keep.c_str()) == 0) continue; + DWORD err = 0; + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // A junction is removed as a link; its target is not followed. + if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) err = RemoveDirectoryW(path.c_str()) ? 0 : GetLastError(); + else err = DeleteTree(path, keep); + } else { + if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + SetFileAttributesW(path.c_str(), fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY); + err = DeleteFileW(path.c_str()) ? 0 : GetLastError(); + } + if (err && !firstError) firstError = err; + } while (FindNextFileW(find, &fd)); + FindClose(find); + } + if (!RemoveDirectoryW(dir.c_str())) { + const DWORD err = GetLastError(); + // The kept file, or a missing folder, is not a failure. + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND && err != ERROR_DIR_NOT_EMPTY && !firstError) + firstError = err; + if (err == ERROR_DIR_NOT_EMPTY && keep.empty() && !firstError) firstError = err; + } + return firstError; +} + +// Whether a process is running from inside `dir` (Redrob Query itself). +static bool AnythingRunningFrom(const std::wstring& dir) { + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snap == INVALID_HANDLE_VALUE) return false; + const std::wstring prefix = dir + L"\\"; + const DWORD self = GetCurrentProcessId(); + bool found = false; + PROCESSENTRY32W pe = {sizeof(pe)}; + for (BOOL ok = Process32FirstW(snap, &pe); ok && !found; ok = Process32NextW(snap, &pe)) { + if (pe.th32ProcessID == self) continue; + HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID); + if (!h) continue; + wchar_t path[MAX_PATH]; + DWORD len = MAX_PATH; + if (QueryFullProcessImageNameW(h, 0, path, &len) && _wcsnicmp(path, prefix.c_str(), prefix.size()) == 0) + found = true; + CloseHandle(h); + } + CloseHandle(snap); + return found; +} + +// Run a command hidden and wait. Returns its exit code, or the CreateProcess error. +static DWORD RunHidden(std::wstring cmd, const wchar_t* cwd = nullptr) { + STARTUPINFOW si = {sizeof(si)}; + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + PROCESS_INFORMATION pi = {}; + if (!CreateProcessW(nullptr, &cmd[0], nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, cwd, &si, &pi)) + return GetLastError(); + WaitForSingleObject(pi.hProcess, INFINITE); + DWORD exitCode = 1; + GetExitCodeProcess(pi.hProcess, &exitCode); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return exitCode; +} + +// A shortcut to the app. It carries the AppUserModelID ai.redrob.query: Windows needs it to show +// toast notifications from an unpackaged app and to group the taskbar button (the MSI's ShortcutProperty +// for the same thing failed with "Warning 1946" on some machines; IPropertyStore is the direct way). +static bool MakeShortcut(const std::wstring& lnk, const std::wstring& target, const std::wstring& workDir) { + ComPtr link; + if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&link)))) return false; + link->SetPath(target.c_str()); + link->SetWorkingDirectory(workDir.c_str()); + link->SetIconLocation(target.c_str(), 0); + link->SetDescription(Tr(L"Install and update the Redrob apps", L"Redrob 앱을 설치하고 업데이트합니다")); + ComPtr store; + if (SUCCEEDED(link->QueryInterface(IID_PPV_ARGS(&store)))) { + PROPVARIANT pv; + if (SUCCEEDED(InitPropVariantFromString(kAppUserModelId, &pv))) { + store->SetValue(PKEY_AppUserModel_ID, pv); + store->Commit(); + PropVariantClear(&pv); + } + } + ComPtr file; + if (FAILED(link->QueryInterface(IID_PPV_ARGS(&file)))) return false; + return SUCCEEDED(file->Save(lnk.c_str(), TRUE)); +} + +static bool SetArpString(HKEY key, const wchar_t* name, const std::wstring& value) { + return RegSetValueExW(key, name, 0, REG_SZ, reinterpret_cast(value.c_str()), + (DWORD)((value.size() + 1) * sizeof(wchar_t))) == ERROR_SUCCESS; +} + +static ULONGLONG TreeSizeBytes(const std::wstring& dir) { + ULONGLONG total = 0; + WIN32_FIND_DATAW fd; + HANDLE find = FindFirstFileW((dir + L"\\*").c_str(), &fd); + if (find == INVALID_HANDLE_VALUE) return 0; + do { + const std::wstring name = fd.cFileName; + if (name == L"." || name == L"..") continue; + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) total += TreeSizeBytes(dir + L"\\" + name); + } else { + total += ((ULONGLONG)fd.nFileSizeHigh << 32) | fd.nFileSizeLow; + } + } while (FindNextFileW(find, &fd)); + FindClose(find); + return total; +} + +// The Settings > Apps entry. Everything Windows shows there: name, version, publisher, icon, size. +static bool WriteArpEntry(const std::wstring& dir) { + HKEY key = nullptr; + if (RegCreateKeyExW(HKEY_CURRENT_USER, kArpKey, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr) != ERROR_SUCCESS) + return false; + const std::wstring exe = dir + kAppExeRel; + const std::wstring uninstaller = dir + kUninstallerName; + bool ok = SetArpString(key, L"DisplayName", L"Redrob Query") && + SetArpString(key, L"DisplayVersion", REDROB_VER_WSTR) && + SetArpString(key, L"Publisher", L"Redrob") && + SetArpString(key, L"DisplayIcon", L"\"" + exe + L"\",0") && + SetArpString(key, L"InstallLocation", dir) && + SetArpString(key, L"URLInfoAbout", L"https://redrob.ai") && + SetArpString(key, L"HelpLink", L"https://github.com/redrob-labs/redrob-query") && + SetArpString(key, L"URLUpdateInfo", L"https://github.com/redrob-labs/redrob-query/releases") && + SetArpString(key, L"UninstallString", L"\"" + uninstaller + L"\" --uninstall"); + const DWORD one = 1; + const DWORD sizeKb = (DWORD)std::min(TreeSizeBytes(dir) / 1024, 0xFFFFFFFFull); + RegSetValueExW(key, L"NoModify", 0, REG_DWORD, reinterpret_cast(&one), sizeof(one)); + RegSetValueExW(key, L"NoRepair", 0, REG_DWORD, reinterpret_cast(&one), sizeof(one)); + RegSetValueExW(key, L"EstimatedSize", 0, REG_DWORD, reinterpret_cast(&sizeKb), sizeof(sizeKb)); + RegCloseKey(key); + return ok; +} + +// The payload-less build of this app (IDR_UNINSTALLER) goes into the install folder; Settings > Apps runs it. +bool SetupApp::InstallUninstaller(const std::wstring& dir) { + return WriteResource(hinst_, IDR_UNINSTALLER, dir + kUninstallerName); +} + +static std::wstring StartMenuShortcut() { + const std::wstring programs = KnownFolder(FOLDERID_Programs); + return programs.empty() ? L"" : programs + kShortcutName; +} + +static std::wstring DesktopShortcut() { + const std::wstring desktop = KnownFolder(FOLDERID_Desktop); + return desktop.empty() ? L"" : desktop + kShortcutName; +} + +// What the next setup preselects: the install +// folder, the language, the desktop-shortcut choice; and the App Paths entry for Win+R "redrob-query". +void SetupApp::RecordChoices(const std::wstring& dir) { + HKEY key = nullptr; + if (RegCreateKeyExW(HKEY_CURRENT_USER, kAppKey, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr) == ERROR_SUCCESS) { + SetArpString(key, L"InstallDir", dir); + SetArpString(key, L"Language", g_lang == Lang::Ko ? L"ko" : L"en"); + SetArpString(key, L"DesktopShortcutChoice", desktopShortcut_ ? L"1" : L"0"); + RegCloseKey(key); + } + if (RegCreateKeyExW(HKEY_CURRENT_USER, kAppPathsKey, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr) == ERROR_SUCCESS) { + SetArpString(key, nullptr, dir + kAppExeRel); + RegCloseKey(key); + } +} + +// The install proper. Unpack beside the old install, then swap, so neither a failed unpack nor a locked +// file leaves a half-updated folder behind. +DWORD SetupApp::InstallApp() { + const std::wstring dir = InstallDir(); + if (dir.empty()) return ERROR_PATH_NOT_FOUND; + if (AnythingRunningFrom(dir)) return kResultAppRunning; + + std::wstring zip; + if (!ExtractPayload(zip)) return ERROR_RESOURCE_DATA_NOT_FOUND; + + const std::wstring parent = LocalAppData() + L"\\Programs"; + CreateDirectoryW(parent.c_str(), nullptr); + const std::wstring staging = dir + L".new"; + const std::wstring aside = dir + L".old"; + DeleteTree(staging); // leftovers of an interrupted earlier run + DeleteTree(aside); + if (!CreateDirectoryW(staging.c_str(), nullptr)) { DeleteFileW(zip.c_str()); return GetLastError(); } + + // System32's tar is bsdtar, which reads zip. Its full path, so a tar.exe elsewhere on PATH is never used. + wchar_t sys[MAX_PATH]; + GetSystemDirectoryW(sys, MAX_PATH); + const DWORD tarExit = RunHidden(L"\"" + std::wstring(sys) + L"\\tar.exe\" -x -f \"" + zip + L"\" -C \"" + + staging + L"\""); + DeleteFileW(zip.c_str()); + if (tarExit != 0) { DeleteTree(staging); return kResultUnpackFailed; } + // The portable zip wraps its files in one folder (redrob-query--windows--portable\). + // Lift that folder's contents up so the exe sits in the install folder itself. + if (!Exists(staging + kAppExeRel)) { + const std::wstring inner = SingleSubfolderWith(staging, kAppExeRel); + if (inner.empty()) { DeleteTree(staging); return kResultNoExe; } + const std::wstring lifted = dir + L".lift"; + DeleteTree(lifted); + if (!MoveFileExW(inner.c_str(), lifted.c_str(), 0)) { const DWORD err = GetLastError(); DeleteTree(staging); return err; } + DeleteTree(staging); + if (!MoveFileExW(lifted.c_str(), staging.c_str(), 0)) { const DWORD err = GetLastError(); DeleteTree(lifted); return err; } + } + // A portable marker, if a zip ever carried one, would make the app run portable; it is dropped + // (settings next to itself, no self-update). An installed copy must not have it. + DeleteFileW((staging + kPortableMarker).c_str()); + + // Swap. The rename fails, rather than half-copying, if anything still holds a file in the old folder. + if (Exists(dir)) { + if (!MoveFileExW(dir.c_str(), aside.c_str(), 0)) { + const DWORD err = GetLastError(); + DeleteTree(staging); + return err == ERROR_ACCESS_DENIED || err == ERROR_SHARING_VIOLATION ? kResultAppRunning : err; + } + } + if (!MoveFileExW(staging.c_str(), dir.c_str(), 0)) { + const DWORD err = GetLastError(); + if (Exists(aside)) MoveFileExW(aside.c_str(), dir.c_str(), 0); // put the old install back + DeleteTree(staging); + return err; + } + DeleteTree(aside); + + // The rest is best effort: the app is installed and runs without any of it. + InstallUninstaller(dir); + const std::wstring exe = dir + kAppExeRel; + const std::wstring start = StartMenuShortcut(); + if (!start.empty()) MakeShortcut(start, exe, dir); + const std::wstring desktop = DesktopShortcut(); + if (!desktop.empty()) { + if (desktopShortcut_) MakeShortcut(desktop, exe, dir); + else DeleteFileW(desktop.c_str()); // an update where the box was unticked removes the old one + } + RecordChoices(dir); + WriteArpEntry(dir); + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); // refresh shortcut icons + + if (!silent_) ShellExecuteW(nullptr, L"open", exe.c_str(), nullptr, dir.c_str(), SW_SHOWNORMAL); + return 0; +} + +DWORD WINAPI SetupApp::UninstallThunk(LPVOID self) { + return reinterpret_cast(self)->RunUninstall(); +} + +void SetupApp::StartUninstall() { + progress_ = progressTarget_ = 0.0; + startTick_ = GetTickCount64(); + SetStage(Stage::Removing); + SetTimer(hwnd_, kTickTimer, kTickMs, nullptr); + if (payloadThread_) { WaitForSingleObject(payloadThread_, 5000); CloseHandle(payloadThread_); } + payloadThread_ = CreateThread(nullptr, 0, &SetupApp::UninstallThunk, this, 0, nullptr); +} + +// Remove what InstallApp made. The uninstaller itself is still running from the install folder, so it +// is kept here and removed by ScheduleSelfCleanup once this window closes. +DWORD SetupApp::RunUninstall() { + if (demo_) { + Sleep(3000); + PostMessage(hwnd_, kUninstallMsgDone, 0, 0); + return 0; + } + const std::wstring dir = InstallDir(); + if (dir.empty()) { PostMessage(hwnd_, kUninstallMsgDone, (WPARAM)ERROR_PATH_NOT_FOUND, 0); return 1; } + if (AnythingRunningFrom(dir)) { PostMessage(hwnd_, kUninstallMsgDone, (WPARAM)kResultAppRunning, 0); return 1; } + + const std::wstring start = StartMenuShortcut(); + if (!start.empty()) DeleteFileW(start.c_str()); + const std::wstring desktop = DesktopShortcut(); + if (!desktop.empty()) DeleteFileW(desktop.c_str()); + RegDeleteTreeW(HKEY_CURRENT_USER, kArpKey); + RegDeleteTreeW(HKEY_CURRENT_USER, kAppPathsKey); + // The folder is gone, so the record of it goes too; the language and the shortcut choice + // stay so the next install preselects them. + HKEY key = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, kAppKey, 0, KEY_WRITE, &key) == ERROR_SUCCESS) { + RegDeleteValueW(key, L"InstallDir"); + RegCloseKey(key); + } + + wchar_t self[MAX_PATH]; + GetModuleFileNameW(nullptr, self, MAX_PATH); + DWORD err = Exists(dir) ? DeleteTree(dir, self) : 0; + + if (deleteData_) { + // Where Redrob Query keeps its state on Windows: %APPDATA%\Redrob Query (connection profiles, + // workspace, eframe's ui.ron; apps/redrob-query/src/services.rs). Passwords live in the Windows + // Credential Manager and are removed by the app, not here. Its registry key goes too. + // %APPDATA%\Redrob\Shared (the launcher's sign-in every Redrob app reads) stays. + const std::wstring roaming = KnownFolder(FOLDERID_RoamingAppData); + if (!roaming.empty()) DeleteTree(roaming + L"\\Redrob Query"); + RegDeleteTreeW(HKEY_CURRENT_USER, kAppKey); + } + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + PostMessage(hwnd_, kUninstallMsgDone, (WPARAM)err, 0); + return err; +} + +// Once removed, this exe is the last thing left. A running exe cannot delete itself, so a hidden +// PowerShell waits for this process to exit, then removes it and the install folder. Only when this copy +// runs from the install folder: a copy started anywhere else is never deleted. +void SetupApp::ScheduleSelfCleanup() { + wchar_t self[MAX_PATH]; + if (!GetModuleFileNameW(nullptr, self, MAX_PATH)) return; + const std::wstring dir = InstallDir(); + const std::wstring me = self; + if (dir.size() < 4 || _wcsnicmp(me.c_str(), (dir + L"\\").c_str(), dir.size() + 1) != 0) return; + std::wstring cmd = L"powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -Command \"Wait-Process -Id " + + std::to_wstring(GetCurrentProcessId()) + + L" -ErrorAction SilentlyContinue; Remove-Item -LiteralPath '" + me + + L"' -Force -ErrorAction SilentlyContinue; Remove-Item -LiteralPath '" + dir + + L"' -Recurse -Force -ErrorAction SilentlyContinue\""; + STARTUPINFOW si = {sizeof(si)}; + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + PROCESS_INFORMATION pi = {}; + // From the parent folder: a process whose working directory is the install folder would hold it open. + const std::wstring parent = LocalAppData() + L"\\Programs"; + if (CreateProcessW(nullptr, &cmd[0], nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, parent.c_str(), &si, &pi)) { + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } +} + +int SetupApp::RunSilent(HINSTANCE hinst, bool deleteData) { + hinst_ = hinst; + silent_ = true; + deleteData_ = deleteData; + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); // the shortcut writer is COM + DWORD result; + if (mode_ == Mode::Uninstall) { + result = RunUninstall(); // hwnd_ is null: its PostMessage lands on this thread's queue, unread + if (result == 0) ScheduleSelfCleanup(); + } else { + result = InstallApp(); + } + CoUninitialize(); + return (int)result; +} + +DWORD SetupApp::RunPayload() { + PostMessage(hwnd_, kStageMsg, (WPARAM)Stage::Installing, 0); + const DWORD result = InstallApp(); + PostMessage(hwnd_, kPayloadMsgDone, (WPARAM)result, 0); + return result; +} + +} // namespace + +// ---- entry point ----------------------------------------------------------- + +int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE, PWSTR, int) { + bool demo = false, demoError = false; + bool silent = false, deleteData = false, explicitInstall = false; + std::wstring renderDir, lang; + Mode mode = Mode::Install; + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + for (int i = 1; i < argc; ++i) { + if (lstrcmpiW(argv[i], L"--demo") == 0) { demo = true; explicitInstall = true; } + else if (lstrcmpiW(argv[i], L"--demo-error") == 0) { demo = true; demoError = true; explicitInstall = true; } + else if (lstrcmpiW(argv[i], L"--uninstall") == 0) mode = Mode::Uninstall; + else if (lstrcmpiW(argv[i], L"--demo-uninstall") == 0) { demo = true; mode = Mode::Uninstall; } + else if (lstrcmpiW(argv[i], L"--silent") == 0) silent = true; + else if (lstrcmpiW(argv[i], L"--delete-data") == 0) deleteData = true; + else if (lstrcmpiW(argv[i], L"--lang") == 0 && i + 1 < argc) lang = argv[++i]; + else if (lstrcmpiW(argv[i], L"--render-stages") == 0 && i + 1 < argc) renderDir = argv[++i]; + } + if (argv) LocalFree(argv); + g_lang = DefaultLanguage(lang); + +#ifdef REDROB_NO_PAYLOAD + // The uninstaller carries nothing to install, so opened by a double-click it offers removal, not an + // Install button that could only fail. + if (!explicitInstall) mode = Mode::Uninstall; +#endif + + SetupApp app(demo, demoError, mode); + if (!renderDir.empty()) return app.RenderStages(hinst, renderDir); + if (silent && !demo) return app.RunSilent(hinst, deleteData); + return app.Run(hinst); +} diff --git a/installer/windows/redrob_query_setup.manifest b/installer/windows/redrob_query_setup.manifest new file mode 100644 index 0000000..ed2a83d --- /dev/null +++ b/installer/windows/redrob_query_setup.manifest @@ -0,0 +1,26 @@ + + + + + + + true/pm + PerMonitorV2 + + + + + + + + + + + + + + diff --git a/installer/windows/redrob_query_setup.rc b/installer/windows/redrob_query_setup.rc new file mode 100644 index 0000000..e35f3e6 --- /dev/null +++ b/installer/windows/redrob_query_setup.rc @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Resources for the Redrob Query setup and uninstaller. build.py writes build_defs.h with the file +// paths and version; a build without a payload (the uninstaller, or a demo build) defines REDROB_NO_PAYLOAD. + +#include +#include "resource.h" +#include "build_defs.h" + +IDI_APPICON ICON REDROB_ICON_PATH + +IDR_MANIFEST RT_MANIFEST "redrob_query_setup.manifest" + +#ifndef REDROB_NO_PAYLOAD +IDR_PAYLOAD RCDATA REDROB_PAYLOAD_PATH +#endif +#ifdef REDROB_UNINSTALLER_PATH +IDR_UNINSTALLER RCDATA REDROB_UNINSTALLER_PATH +#endif +IDR_BACKGROUND_PNG RCDATA REDROB_BACKGROUND_PATH +IDR_LOCKUP_PNG RCDATA REDROB_LOCKUP_PATH +#ifdef REDROB_FONT_PATH +IDR_FONT_SANS RCDATA REDROB_FONT_PATH +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION REDROB_VER_FILE + PRODUCTVERSION REDROB_VER_FILE + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Redrob" + VALUE "FileDescription", "Redrob Query " REDROB_SETUP_KIND + VALUE "FileVersion", REDROB_VER_STR + VALUE "ProductName", "Redrob Query" + VALUE "ProductVersion", REDROB_VER_STR + VALUE "LegalCopyright", "Copyright (C) 2026 Janghoon Lee (Redrob) and contributors" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/installer/windows/res/background.html b/installer/windows/res/background.html new file mode 100644 index 0000000..a978ff3 --- /dev/null +++ b/installer/windows/res/background.html @@ -0,0 +1,106 @@ + + + + + + + + +
+
+
+
+
+
+ + diff --git a/installer/windows/res/background.png b/installer/windows/res/background.png new file mode 100644 index 0000000000000000000000000000000000000000..31d9be87244e19881fb88fcc276ffc4740fde019 GIT binary patch literal 452013 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

bxL^!DvGnHyRREIj3K2e= zJc%agsT2=p{~xkz5CT!}*PJ^V%q7VRLymnp=AzJBjEj#~K)V&9dJpuC1LbL=NbdwX zzO8`7ZisS3D+X+HO`UFN7_}FVqubh)43_dloPulghCc?1-L_Kg2POxPjz}2vs~MaK z+|iLB1tNTeEup8B*3oP!UyJpYS1*ymcvTB6wiXuq1p!XrDYKzhVGqw6vshVmwZi1ZkJYzjs*Z0IIn`h*^!Xv!)6*I!kTwObh9 zEZr>|d^B6ev97E)e5>3A$6xlJ7%3@N_m1mabrjUxx5jJwD58D*-8sC|{jYp-_`fZG z%E81k?xO-WOpN$c-*XKYcm$l-S-5OfY$<)%f-<6a?T)q;IGV^^OBcioCPBabv+_G0+AIfPYjj{r&OZsbx zMxv~W_eI+%%~$t%0hfWjj|C;Nxh{#xF7e1IbF?L;2UMgOL-4(S{tsa+WlF@kLSJ8# zo{WQ*KU;w3Mj&f$;c?ZH|9&6F4(;8uwgJoqd?t@uMI60Lj^fu*g~1l?tu1AIB=1f;8LY~j%! zJ|LPtY;BK*WaFUt_3|>Bf$HRFPoB@)U7}*br*TMyS=9Swh7pvb(NkjktX$O^&n+vR z_G0(98c|aKn+={3p>t7#BC0T7kPFdn@e&l%)+^@0- zTs6X7mI<&&NpirIY(@}_Y9HJ;%G4YAWS%3xWa77Ok?6SJCA(q!^L{zHCx`&!G^Kji zP^!|Uitn;_q4CI?-bRFSMc*h2dFL1e&%}8nY&j*?b*Z8v_+X9(TdQ&E&U&>#oKN(> zS-@Ru!vyY**ai{A?|32_jZ^*0#Ww>_MiIOt#1k6Ln=(VoG3fPqcLDb9S&pBc*t|=} zk8PI|?&mTKPI6;@;EEQJ=8^~l9W{ES^xm-w!x2OxkWSu6rF6yBf)f9&2XB!1V0LRK z;)1H^lj~xl18TlU|2yT2F*Wa7Cnz3*bcsOU?{H&u?pkW^t&lg#AmRwHHhC(>SWPmm zG5yMbdjw*UE&`jRU*;sk*iBkfU9ap~$78{!#JFnmToE@O#-o^Ongk?~B{T(Mc3OG| z%75L}DlAR>iciV_;MFD!L6c+8{t&3p&WxM0KJE^<8#uQ$H%C6dE63Bu5o$D!ly*mo z{}7nl;PaMBo!D6gy*^|9mGpv&>Zff!mGQc2b!vLo0hMpxW!R0>6LFy7R&rRJvkEGhq)$bLu@aq58 zVb&%l4x>Q(N^%)zWQHCNjr`|+QLOwT_@U3o_3go8l-K#0WhI~`{<+(Bn-W)2)t1|g z6U7~Z;>Y@e*vLz7^IUF8m{ULr{Lyrhf9qJ3{|W zZ>QLSXi8Jcg_hXrk_|3KEnya|FH64NHiU_Kc>aC73rrH<8vf8ZAZ%H`aH3Ao9xtOI z^1Ws)hG7|vVMQS20N?n5Pz{Y?n�_=I0c6{=Jp7G`iR%7kzFDIgSit2pT2_;;vuk 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"g|ieW=t;H5#=d$f$wJ)Sm`A^EkAErkQS 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 KMNUMnLSTZnOoNR8 literal 0 HcmV?d00001 diff --git a/installer/windows/res/icon.png b/installer/windows/res/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1e5101f20142fef42d73d86cbe02afb7bc4e79c3 GIT binary patch literal 12129 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 LNkvXXu0mjfn+t(j literal 0 HcmV?d00001 diff --git a/installer/windows/resource.h b/installer/windows/resource.h new file mode 100644 index 0000000..dbee8d8 --- /dev/null +++ b/installer/windows/resource.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Resource identifiers shared between redrob_query_setup.rc and redrob_query_setup.cpp. +#pragma once + +// RCDATA +#define IDR_PAYLOAD 101 // the release's windows zip (absent from the uninstaller) +#define IDR_BACKGROUND_PNG 102 // res/background.png +#define IDR_LOCKUP_PNG 103 // res/lockup.png -- the Redrob Query lockup, white, for the deep ground +#define IDR_FONT_SANS 104 // Pretendard SemiBold, when the design system delivery was present +#define IDR_UNINSTALLER 106 // the payload-less build of this app, written into the install folder + +#define IDI_APPICON 105 // assets/app-icon/redrob-query.ico + +#define IDR_MANIFEST 1 // per-monitor-v2 DPI + common controls diff --git a/installer/windows/verify-install.ps1 b/installer/windows/verify-install.ps1 new file mode 100644 index 0000000..2464849 --- /dev/null +++ b/installer/windows/verify-install.ps1 @@ -0,0 +1,51 @@ +# Verify the Redrob Query setup end to end on this PC: install, upgrade over it, uninstall. Prints PASS/FAIL lines. +$ErrorActionPreference = 'Stop' +$setup = 'C:\Users\USER\redrob\setup-out\Redrob Query Setup.exe' +$dir = Join-Path $env:LOCALAPPDATA 'Programs\Redrob Query' +$exe = Join-Path $dir 'bin\redrob-query.exe' +$un = Join-Path $dir 'Uninstall Redrob Query.exe' +$arp = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\RedrobQuery' +$start = Join-Path ([Environment]::GetFolderPath('Programs')) 'Redrob Query.lnk' +$desk = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Redrob Query.lnk' +function Check($name, $ok) { if ($ok) { "PASS $name" } else { "FAIL $name" } } + +$p = Start-Process -FilePath $setup -ArgumentList '--silent' -Wait -PassThru +Check "install exit 0 (got $($p.ExitCode))" ($p.ExitCode -eq 0) +Check 'exe present' (Test-Path -LiteralPath $exe) +Check 'uninstaller present' (Test-Path -LiteralPath $un) +Check 'start menu shortcut' (Test-Path -LiteralPath $start) +Check 'desktop shortcut' (Test-Path -LiteralPath $desk) +$k = Get-ItemProperty -LiteralPath $arp -ErrorAction SilentlyContinue +Check "arp DisplayName=$($k.DisplayName) DisplayVersion=$($k.DisplayVersion) SizeKB=$($k.EstimatedSize)" ($k.DisplayName -eq 'Redrob Query' -and $k.DisplayVersion -eq '0.5.5') +Check "arp UninstallString=$($k.UninstallString)" ($k.UninstallString -like '*Uninstall Redrob Query.exe*--uninstall') +$lnk = (New-Object -ComObject WScript.Shell).CreateShortcut($start) +Check "shortcut target=$($lnk.TargetPath)" ($lnk.TargetPath -eq $exe) +$ver = (Get-Item -LiteralPath $exe).VersionInfo +Check "exe ProductName=$($ver.ProductName)" ($ver.ProductName -eq 'Redrob Query') +$sig = Get-AuthenticodeSignature -LiteralPath $exe +Check "exe signed ($($sig.SignerCertificate.Subject))" ($null -ne $sig.SignerCertificate) + +# Upgrade over the existing install. +New-Item -ItemType File -Path (Join-Path $dir 'stale-marker.txt') -Force | Out-Null +$p = Start-Process -FilePath $setup -ArgumentList '--silent' -Wait -PassThru +Check "reinstall exit 0 (got $($p.ExitCode))" ($p.ExitCode -eq 0) +Check 'reinstall replaced the folder (stale file gone)' (-not (Test-Path -LiteralPath (Join-Path $dir 'stale-marker.txt'))) +Check 'no .new/.old leftovers' (-not (Test-Path -LiteralPath "$dir.new") -and -not (Test-Path -LiteralPath "$dir.old")) + +# Running app blocks the update. +$app = Start-Process -FilePath $exe -PassThru +Start-Sleep -Seconds 3 +$p = Start-Process -FilePath $setup -ArgumentList '--silent' -Wait -PassThru +Check ("install while app runs refused (got 0x{0:X8})" -f $p.ExitCode) ($p.ExitCode -eq [int]0xE0000001 -or $p.ExitCode -eq -536870911) +Check 'app files intact after refusal' (Test-Path -LiteralPath $exe) +Stop-Process -Id $app.Id -Force +Start-Sleep -Seconds 2 + +# Uninstall, keeping data. +$p = Start-Process -FilePath $un -ArgumentList '--uninstall','--silent' -Wait -PassThru +Check "uninstall exit 0 (got $($p.ExitCode))" ($p.ExitCode -eq 0) +Start-Sleep -Seconds 6 +Check 'install folder gone' (-not (Test-Path -LiteralPath $dir)) +Check 'start menu shortcut gone' (-not (Test-Path -LiteralPath $start)) +Check 'desktop shortcut gone' (-not (Test-Path -LiteralPath $desk)) +Check 'arp key gone' (-not (Test-Path -LiteralPath $arp)) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index c42edd4..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3920 +0,0 @@ -{ - "name": "redrob-query", - "version": "0.2.4", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "redrob-query", - "version": "0.2.4", - "license": "GPL-3.0-or-later", - "dependencies": { - "@beekeeperstudio/bks-er-diagram": "1.1.2", - "@monaco-editor/react": "^4.7.0", - "@redrob-labs/ui": "^1.0.2", - "@tanstack/react-table": "^8.21.3", - "@tanstack/react-virtual": "^3.13.12", - "@tauri-apps/api": "^2.8.0", - "clsx": "^2.1.1", - "framer-motion": "^12.23.12", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "zustand": "^5.0.8" - }, - "devDependencies": { - "@tauri-apps/cli": "^2.8.4", - "@testing-library/jest-dom": "^6.8.0", - "@testing-library/react": "^16.3.0", - "@testing-library/user-event": "^14.6.1", - "@types/node": "^24.3.1", - "@types/react": "^19.1.12", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.2", - "@vitest/coverage-v8": "^3.2.7", - "jsdom": "^26.1.0", - "typescript": "^5.9.2", - "vite": "^7.1.5", - "vitest": "^3.2.4" - }, - "engines": { - "node": ">=22.12.0", - "npm": ">=11.0.0" - } - }, - "node_modules/@adobe/css-tools": { - "version": "4.5.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@beekeeperstudio/bks-er-diagram": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@beekeeperstudio/bks-er-diagram/-/bks-er-diagram-1.1.2.tgz", - "integrity": "sha512-633LYkE/mGaRS25VECpYl8cqgulfCr9o7vK4G74+NBXT/rSM0iZc3xGh2f0axbieGBKQ5gh91OjrukZ+gtf0wA==", - "license": "GPL-3.0" - }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", - "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@monaco-editor/loader": { - "version": "1.7.0", - "license": "MIT", - "dependencies": { - "state-local": "^1.0.6" - } - }, - "node_modules/@monaco-editor/react": { - "version": "4.7.0", - "license": "MIT", - "dependencies": { - "@monaco-editor/loader": "^1.5.0" - }, - "peerDependencies": { - "monaco-editor": ">= 0.25.0 < 1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-gnu": { - "version": "1.5.1", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^22.20 || ^24.12 || >=25" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redrob-labs/ui": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@redrob-labs/ui/-/ui-1.0.2.tgz", - "integrity": "sha512-QfjxT1AFq74oJYLuobviBXLR53FAopjZXZolVaqvSmZgDpU7A2CI342rBAtCZuis00WxMcu7xjkrcU7bfVPLWQ==", - "license": "MIT", - "peerDependencies": { - "react": "^18.2.0 || ^19.0.0", - "react-dom": "^18.2.0 || ^19.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", - "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", - "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", - "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", - "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", - "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", - "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", - "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", - "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", - "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", - "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", - "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", - "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", - "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", - "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", - "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", - "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", - "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.63.1", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", - "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", - "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", - "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", - "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", - "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", - "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.63.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", - "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tanstack/react-table": { - "version": "8.21.3", - "license": "MIT", - "dependencies": { - "@tanstack/table-core": "8.21.3" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/@tanstack/react-virtual": { - "version": "3.14.11", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.17.9" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@tanstack/table-core": { - "version": "8.21.3", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.17.9", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tauri-apps/api": { - "version": "2.11.1", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.11.4", - "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.11.4", - "@tauri-apps/cli-darwin-x64": "2.11.4", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", - "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", - "@tauri-apps/cli-linux-arm64-musl": "2.11.4", - "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", - "@tauri-apps/cli-linux-x64-gnu": "2.11.4", - "@tauri-apps/cli-linux-x64-musl": "2.11.4", - "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", - "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", - "@tauri-apps/cli-win32-x64-msvc": "2.11.4" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", - "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", - "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", - "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", - "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", - "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", - "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.11.4", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", - "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", - "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", - "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", - "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@testing-library/react": { - "version": "16.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@testing-library/user-event": { - "version": "14.6.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.13.3", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.18", - "devOptional": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.7", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.7.tgz", - "integrity": "sha512-NEGWJS2XNu2PfRLQwOO3CTKj1tTETxNBdk454vDxVBhxJYhPaA/eS0nAI0c+1El1P7a60z8+i+ZrQoGESweGKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "3.2.7", - "vitest": "3.2.7" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.7", - "@vitest/utils": "3.2.7", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.7", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.7", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.7", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.7", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aria-query": { - "version": "5.3.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", - "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.21", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/browserslist": { - "version": "4.28.9", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.11.20", - "caniuse-lite": "^1.0.30001810", - "electron-to-chromium": "^1.5.420", - "node-releases": "^2.0.54", - "update-browserslist-db": "^1.3.2" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "5.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/check-error": { - "version": "2.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "dev": true, - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "devOptional": true, - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/dompurify": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", - "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", - "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.422", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/entities": { - "version": "6.0.1", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.28.2", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.4.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/framer-motion": { - "version": "12.43.0", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.43.0", - "motion-utils": "^12.39.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/jsdom": { - "version": "26.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/loupe": { - "version": "3.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lz-string": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/marked": { - "version": "14.0.0", - "license": "MIT", - "peer": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/monaco-editor": { - "version": "0.56.0", - "license": "MIT", - "peer": true, - "dependencies": { - "dompurify": "3.4.8", - "marked": "14.0.0" - } - }, - "node_modules/motion-dom": { - "version": "12.43.0", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.39.0" - } - }, - "node_modules/motion-utils": { - "version": "12.39.0", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.18", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.54", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/nwsapi": { - "version": "2.2.27", - "dev": true, - "license": "MIT" - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse5": { - "version": "7.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/pathe": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.28", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.18", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/react": { - "version": "19.2.8", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.8", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.8" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/react-refresh": { - "version": "0.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rollup": { - "version": "4.63.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "@rollup/rollup-android-arm-eabi": "4.63.1", - "@rollup/rollup-android-arm64": "4.63.1", - "@rollup/rollup-darwin-arm64": "4.63.1", - "@rollup/rollup-darwin-x64": "4.63.1", - "@rollup/rollup-freebsd-arm64": "4.63.1", - "@rollup/rollup-freebsd-x64": "4.63.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", - "@rollup/rollup-linux-arm-musleabihf": "4.63.1", - "@rollup/rollup-linux-arm64-gnu": "4.63.1", - "@rollup/rollup-linux-arm64-musl": "4.63.1", - "@rollup/rollup-linux-loong64-gnu": "4.63.1", - "@rollup/rollup-linux-loong64-musl": "4.63.1", - "@rollup/rollup-linux-ppc64-gnu": "4.63.1", - "@rollup/rollup-linux-ppc64-musl": "4.63.1", - "@rollup/rollup-linux-riscv64-gnu": "4.63.1", - "@rollup/rollup-linux-riscv64-musl": "4.63.1", - "@rollup/rollup-linux-s390x-gnu": "4.63.1", - "@rollup/rollup-linux-x64-gnu": "4.63.1", - "@rollup/rollup-linux-x64-musl": "4.63.1", - "@rollup/rollup-openbsd-x64": "4.63.1", - "@rollup/rollup-openharmony-arm64": "4.63.1", - "@rollup/rollup-win32-arm64-msvc": "4.63.1", - "@rollup/rollup-win32-ia32-msvc": "4.63.1", - "@rollup/rollup-win32-x64-gnu": "4.63.1", - "@rollup/rollup-win32-x64-msvc": "4.63.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "dev": true, - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/saxes": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/state-local": { - "version": "1.0.7", - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-literal": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "9.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^10.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinypool": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tldts": { - "version": "6.1.86", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "dev": true, - "license": "MIT" - }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.3.2", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "7.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0 || ^0.28.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.7", - "@vitest/mocker": "3.2.7", - "@vitest/pretty-format": "^3.2.7", - "@vitest/runner": "3.2.7", - "@vitest/snapshot": "3.2.7", - "@vitest/spy": "3.2.7", - "@vitest/utils": "3.2.7", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.7", - "@vitest/ui": "3.2.7", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ws": { - "version": "8.21.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/zustand": { - "version": "5.0.15", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 9fbfadf..0000000 --- a/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "redrob-query", - "version": "0.2.4", - "private": true, - "description": "A JVM-free, AI-native desktop database workspace for PostgreSQL, MySQL, SQLite, and MongoDB.", - "license": "GPL-3.0-or-later", - "author": "Redrob", - "repository": { - "type": "git", - "url": "git+https://github.com/redrob-labs/redrob-query.git" - }, - "homepage": "https://github.com/redrob-labs/redrob-query#readme", - "bugs": { - "url": "https://github.com/redrob-labs/redrob-query/issues" - }, - "engines": { - "node": ">=22.12.0", - "npm": ">=11.0.0" - }, - "packageManager": "npm@11.4.2", - "type": "module", - "scripts": { - "dev": "vite --host 127.0.0.1", - "build": "npm run typecheck && vite build", - "preview": "vite preview --host 127.0.0.1", - "typecheck": "tsc --noEmit -p tsconfig.app.json --pretty false", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "release:check": "node scripts/validate-release.mjs", - "release:prepare": "node scripts/prepare-release-config.mjs", - "tauri": "tauri", - "rust:fmt": "cargo fmt --all -- --check", - "rust:clippy": "cargo clippy --locked --workspace --all-targets --all-features -- -D warnings", - "rust:test": "cargo test --locked --workspace --all-features", - "check": "npm run release:check && npm run check:upstream && npm run check:upstream-paths && npm run check:design && npm test && npm run build && npm run rust:fmt && npm run rust:clippy && npm run rust:test", - "check:design": "node scripts/check-design-system.mjs", - "check:upstream": "node scripts/verify-upstream.mjs", - "check:upstream-paths": "node scripts/check-upstream-paths.mjs", - "plugins:install": "node scripts/install-plugin-assets.mjs", - "predev": "npm run plugins:install", - "prebuild": "npm run plugins:install", - "prepreview": "npm run plugins:install", - "pretest": "npm run plugins:install", - "check:matrix": "node scripts/check-compatibility-matrix.mjs" - }, - "dependencies": { - "@beekeeperstudio/bks-er-diagram": "1.1.2", - "@monaco-editor/react": "^4.7.0", - "@redrob-labs/ui": "^1.0.2", - "@tanstack/react-table": "^8.21.3", - "@tanstack/react-virtual": "^3.13.12", - "@tauri-apps/api": "^2.8.0", - "clsx": "^2.1.1", - "framer-motion": "^12.23.12", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "zustand": "^5.0.8" - }, - "overrides": { - "dompurify": "^3.4.15" - }, - "devDependencies": { - "@tauri-apps/cli": "^2.8.4", - "@testing-library/jest-dom": "^6.8.0", - "@testing-library/react": "^16.3.0", - "@testing-library/user-event": "^14.6.1", - "@types/node": "^24.3.1", - "@types/react": "^19.1.12", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.2", - "@vitest/coverage-v8": "^3.2.7", - "jsdom": "^26.1.0", - "typescript": "^5.9.2", - "vite": "^7.1.5", - "vitest": "^3.2.4" - } -} diff --git a/resources/brand/redrob-query-lockup-white.png b/resources/brand/redrob-query-lockup-white.png new file mode 100644 index 0000000000000000000000000000000000000000..141bb97a5ae6b0f62616ea23ca33e60ad0a6907d GIT binary patch literal 150544 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

}}HXbtRFLXQ2v>4aOwjnQ5R2j!mT3UD=oTp&+1M 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

}CpO-4H_{jRG;qR-8(1nmn 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 V@T1}Q_kZKQmbxAk_3x8c{~w~v$V>nL literal 0 HcmV?d00001 diff --git a/resources/icons/hicolor/128x128/apps/redrob-query.png b/resources/icons/hicolor/128x128/apps/redrob-query.png new file mode 100644 index 0000000000000000000000000000000000000000..6812b56f0b7bd6e9962f5ca33308729306f65d59 GIT binary patch literal 5022 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 literal 0 HcmV?d00001 diff --git a/resources/icons/hicolor/16x16/apps/redrob-query.png b/resources/icons/hicolor/16x16/apps/redrob-query.png new file mode 100644 index 0000000000000000000000000000000000000000..11ca3c36d59fca5235d57cface3a3703dd227e5c GIT binary patch literal 653 zcmV;80&@L{P)0&k(qk)(6&YUxIo_Xdurm;)J0M-HqHd3&jfIuEJMIn`j znXdp)6oz0B5b&MG0>*%I5MS4|dK+}ML3=ASAA)oShTcGI1V%r=@-Ho3;DIq<8qSM& zAvk^zI$ELa7?c)kbrC)E*M6?T;28A1fQi{GiVJr|zAefo%lMo9={b?L_&>3Ek<-^j z0#)U(vkKB_Fjl$9v#_)RPsgD5IZV&P<8Fu)XiL_@{yIe(NM{sYFsP87gP8@mdJk6D zwAhvi*eq9}V6LT%5BBXW-0z0rcN)mc)85wNP+kH>1t37!)4fCz242CP2k?Cf0*NF% zia}2dsw&{zDLAkj!ubll*f;t{N5=m6y_DNTzh(7*%Lf6h_}ez*u}b literal 0 HcmV?d00001 diff --git a/resources/icons/hicolor/256x256/apps/redrob-query.png b/resources/icons/hicolor/256x256/apps/redrob-query.png new file mode 100644 index 0000000000000000000000000000000000000000..1e5101f20142fef42d73d86cbe02afb7bc4e79c3 GIT binary patch literal 12129 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 u&`)}cWkC6W45viN?fL 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 rFB-^V*={YH)LkgOQCDPq7gs2Dz(jCIk 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 literal 0 HcmV?d00001 diff --git a/resources/icons/hicolor/64x64/apps/redrob-query.png b/resources/icons/hicolor/64x64/apps/redrob-query.png new file mode 100644 index 0000000000000000000000000000000000000000..0182fc8f553c70e0429df341010b1c4000b0e9f0 GIT binary patch literal 2572 zcmV+n3iI`eP)% zZHyJ=6~}+iGjs3Wd+`O87b!1ktS=Q13Rpl~)Kt`p4T{_Q!Nd=3W74J{Oh2?qKh^ln zwCR^7ZIjldZPKJ^wiKiRR;XIpg%uPCEo|@&4KKO~3R2j&J2R&r&dkp2c6aaGd*@ya zo@BDwJI_3G&Uw!HpYxpOj9K)!2v-iB#qf@SZh;q$ZZdlTct$MG0+?X@1LjInfH7b! zU?A`bfNtPdM>m&C}zu?q7 z67bwSt~bo1eVfhJmRo=|;Diu#X+ST~->hi?1A>tN0e%WC!T@7*wMAfX7>0&nS`qGC zsQ2IKgUf?ZY=c6Zjt>GI-y{-10>c`&S#L1JCO%w&!69&6xOD-nTMZAbfyFoL{Uh%{ z*Isz(HF)a;R6HmZpe?8OgFrdXibM#UJdTXek+}s}t1IYvx~c-qpAGk{fQQ>*)ndr! zMi~KgJitf=_8)=m-SFx`ICVzvO)IGKL^8q)A+Rns@YtV;0&JLq`o5a@NCjq0hgEmL z`gRq5`qlAx-;dn_Vsm#AIogGbu=^lv?}2^)fpZri??SPl<0OEfe$K@hj0hjst$?Io z4-Ko}R~2FDZED`@R>Axm6G8{-7RKt|3AsWA6TnHp^T~er=RSCGFC2aoE@g-pH7Q`c zUk?mH&V^eRsKDR6JN9eP6%q+8q(+1n(dUgB8y$|k16_MmA#Z1Z7pW8wt#;C{=gm^n zUf&KY?}!-yHSq`wvY^QKlP)q+QPdaNRZWxbc472#KhYC&~*Z!i)14VfO)5#{MJl@dd?-e6G3@rc{72YGQL|!js>E z`&Yt(Ir05bzplf?$J1o~Kk0ahK`=S~#E0r0f8GWkoK+W{N(ocG%38QQ0BcvkcOO;Z z15v?6^u>*udLW_ogzs}Ke0m9vzB~1E#^_il1Y$po3Jd1I_a0LVy*yCeBC94s#z3W_ zOp2&~O$y^FDe`G+Lo;`D{x|v*>9@QBD;C1&fHj)D>^TJ6x}ogA=f-m$KLxv9(~R-xySgPWw!u{;#egVPPfIn0!AKFHpjIp>lmC7TUOx)I{;L)` z*SEucU)QQoBV{(+2xX-rOe^XCyAQy> zUe~Sj(p%w)hwDRrKY*9^!C$(x3Uuy*0$rgkOq?>xA)0b7FzR|);6PM3&0wmDsBR00 ziXTA$1A|&3yY8B}N|bbgEw9u*zhmM4ReFtgfloe-T_ED2-Y{E}0;2ky5EnvA1z2(` zJaiX4aF>E$L=`Vxabq%yKyb!%C_kXr@l&v~SFZ>JOKs5B7K30d%1FH(wgyH#U2Vr1 z8wQu%#;A=ZLspcW)JxH`$vJPD2R|so_rI+>$*w){(tda^5eD;ahG8(O2@VazoSACg z)e<2mp^PjjS@1|ELd$R0>qk%O4m0yaC=&k5UxKGL!hLtb%Ku{6Y6Yq^JdS3cUd(eErKeiMs3utNDz07vb7#T%|7!Wm zAGevwl4|9(sTH9}z^67sd7Ym4ZtvE^i?io5J?yNTT2>Uj_G;L0uU;q5XqwyA1Bc&M ztB4Le8?m%{sAWabBv8$t4d2;i*JUfpHSE6 z+5=l()#y_X1p;JU6U@1ZnqUJ*-VsGW1#upq&uO)(eVHaUr$5sBX)Gaml%5ME%B8Fc zPN{$(&~5JSgYbtJ5{ILq;8`4wj>dB0!_gVjtJe}l;!|YD#@2PBU)`zufbw3gPEgL@{vvdbZEy_{#I(?j!*>O$zlBpuk~#}mGlDrttH!GgBXheo&&JuvN(O4%~o%OZEhY{1g`~b0F51K)k7lT*CHcq z>)UL$mdoN85DUn{GP6y zWg75(I{G?If4PhW6Ig)gJGx_AOs@nyXoQoYJNO8nVGC1(B={ouZXVYNCney)V_Qsb zxhzg+_zCTpuovC9alfrh#$Va z&74jO4-*NJ_`rBkhdd5E4Xm{emhe>O^ZKb}Js(d`vXWwX26$FKH(%5tYYaRpuomHF iU}p2gXozb5eExr8JUfde+5mb00000Redrob diff --git a/resources/icons/redrob-query.icns b/resources/icons/redrob-query.icns new file mode 100644 index 0000000000000000000000000000000000000000..f4dbb54b2cd6a48204fc2cc257ba3cd6f7b90a29 GIT binary patch literal 193743 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 KMNUMnLSTXj)g!$C literal 0 HcmV?d00001 diff --git a/resources/icons/redrob-query.svg b/resources/icons/redrob-query.svg new file mode 100644 index 0000000..3cf4abf --- /dev/null +++ b/resources/icons/redrob-query.svg @@ -0,0 +1 @@ +Redrob diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 28bb9c5..8934eb4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.94.0" +channel = "1.95.0" components = ["clippy", "rustfmt"] profile = "minimal" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..4937083 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +edition = "2024" +max_width = 100 +use_field_init_shorthand = true diff --git a/scripts/build-app-icon.py b/scripts/build-app-icon.py deleted file mode 100644 index 5337584..0000000 --- a/scripts/build-app-icon.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -"""Build the Redrob Query app icon master, then hand it to `tauri icon` for the platform sizes. - -Geometry and rules are assets/Products/README.md, not invention: - - Tile square top-left corner, 22% radius on the other three from 48px up; 19% at 32px. - Symbol the shipped file, Solid White, on every tile. Never redrawn or recoloured. - Doorway "Redrob's doorway is empty. Something in the doorway means a product." Query's is a - three-tier store. Every shipped glyph is FILLED white with no stroke, and Desk's folder - measures x 212..300, y 207..314, which is the room a glyph has. - Perspective "Every row runs toward the door's vanishing point and slants less nearer the middle, as - Office's rows do." So the three tiers narrow with depth rather than stacking square. - Tile colour Code's band. 20-color.md closes the spectrum at seven and states there is no eighth - product colour; 10-logo.md's way out is that a new product ships as a feature of the band - it fits, and Query is a developer tool over a database. Pinned in DESIGN_SYSTEM_PIN.json. - -The symbol path is LIFTED from a shipped product icon rather than retyped, so it cannot drift. -""" -import json -import pathlib -import re -import subprocess -import sys - -REPO = pathlib.Path(__file__).resolve().parent.parent -DELIVERY = pathlib.Path.home() / "workplace/redrob-design-system/assets/Products" -MASTER = REPO / "src-tauri/icons/app-icon.svg" -SMALL = REPO / "src-tauri/icons/app-icon-small.svg" -PNG = REPO / "src-tauri/icons/app-icon.png" - -pin = json.loads((REPO / "DESIGN_SYSTEM_PIN.json").read_text()) -TILE_COLOUR = pin["product_band"]["tile"] - -TILE = ("M0 0H399.36A112.64 112.64 0 0 1 512 112.64V399.36A112.64 112.64 0 0 1 399.36 512" - "H112.64A112.64 112.64 0 0 1 0 399.36Z") -TILE_32 = ("M0 0H414.72A97.28 97.28 0 0 1 512 97.28V414.72A97.28 97.28 0 0 1 414.72 512" - "H97.28A97.28 97.28 0 0 1 0 414.72Z") - -source = DELIVERY / "redrob-code-icon.svg" -if not source.exists(): - sys.exit(f"design system delivery not found: {source}") -symbol = re.search(r'( str: - """Three tiers in the doorway, each narrower and shorter than the one above it.""" - tiers = [] - top, height, gap = 214.0, 26.0, 8.0 - for i in range(3): - inset = i * 7.0 - left, right = 212.0 + inset, 300.0 - inset - y = top + i * (height + gap) - lip = (right - left) / 2 * 0.26 # the ellipse that gives the tier its lid - tiers.append( - f"M{left} {y}H{right}V{y + height - lip}" - f"A{(right - left) / 2} {lip} 0 0 1 {left} {y + height - lip}Z" - ) - return f'' - - -MASTER.write_text( - 'Redrob Query' - f'{symbol.group(1)}{store()}\n' -) -print(f"wrote {MASTER.relative_to(REPO)} tile {TILE_COLOUR}") - -# "-32.svg and -16.svg carry tile and symbol only (the doorway is two or three pixels wide there)." -SMALL.write_text( - 'Redrob Query' - f'{symbol.group(1)}\n' -) -print(f"wrote {SMALL.relative_to(REPO)} tile and symbol only, for 32px and below") - -subprocess.run( - ["magick", "-background", "none", str(MASTER), "-resize", "1024x1024", str(PNG)], check=True -) -subprocess.run(["npx", "tauri", "icon", str(PNG.relative_to(REPO))], cwd=REPO, check=True) - -ICONS = REPO / "src-tauri/icons" -for name, size in (("32x32.png", 32), ("Square30x30Logo.png", 30), ("Square44x44Logo.png", 44)): - subprocess.run( - ["magick", "-background", "none", str(SMALL), "-resize", f"{size}x{size}", - str(ICONS / name)], - check=True, - ) - print(f"re-rendered {name} from the small master") - -ico_parts = [] -for src, sizes in ((SMALL, (16, 24, 32, 48)), (MASTER, (64, 128, 256))): - for s in sizes: - out = ICONS / f".ico-{s}.png" - subprocess.run( - ["magick", "-background", "none", str(src), "-resize", f"{s}x{s}", str(out)], check=True - ) - ico_parts.append(out) -subprocess.run(["magick", *map(str, ico_parts), str(ICONS / "icon.ico")], check=True) -for p in ico_parts: - p.unlink() -print("rebuilt icon.ico: 16/24/32/48 without the doorway, 64/128/256 with it") diff --git a/scripts/build-brand-assets.py b/scripts/build-brand-assets.py new file mode 100755 index 0000000..836a77e --- /dev/null +++ b/scripts/build-brand-assets.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later +"""Build every Redrob Query brand asset from the design system's published files, and pin them. + + scripts/build-brand-assets.py --design-system ~/workplace/redrob-design-system + +Nothing here draws a mark. Every symbol pixel and every symbol path comes from a file the design +system ships, and the files those come from are pinned by sha256 in brand/SOURCES.json together +with the sha256 of every output. scripts/check-brand.py, which tools/check_design_system.py and CI +run, refuses an output tree that differs from the pins, so a hand edit, a re-trace or a recolor +fails the gate. + +Redrob Query is not one of the seven suite products, so it has no doorway glyph and no product +color (20-color.md: "No eighth product color"; assets/Products/README.md: "Something in the doorway +means a product"). Its icon is therefore the group's icon, as Redrob Canvas's and Redrob Shape's are: +the favicon, a white symbol on a Redrob Blue tile with an empty doorway (10-logo.md, +assets/Favicons/README.md). Ported from redrob-canvas's scripts/build-brand-assets.py. + +What is derived, and how: + - Icon sizes the design system ships (16, 32, 48, 64, 512) are copied byte for byte. 128 and 256 + exist only as downsizes of the 512 file, whose spec (radius 22%, mark 66%) the design system + gives for the whole 192-512 band. The one upscale is the 1024 macOS element (ic10): the design + system ships nothing larger than 512, and the .icns would otherwise be blurred by macOS itself. + - The .ico, the .icns and the freedesktop hicolor set are containers around those pixels. + - redrob-query.svg is favicon.svg, byte for byte. redrob-query-small.svg carries favicon.svg's + own symbol path, unchanged, in Solid White with no tile: the mark for a dark ground that is + not Redrob Blue (the shell's chrome, a symbolic icon theme), as assets/Favicons/README.md's + symbol-white-*.png are. + - The lockups follow assets/Products/README.md's product-lockup rule: the Redrob Primary lockup + placed untouched, 400px tall, with the name in Pretendard SemiBold at 70% of the height, set 40% + of the height after the wordmark, on the wordmark's baseline. The construction is proven before + anything is written: the shipped Browser lockup is rebuilt the same way and must match. + - The splash / About image is Redrob Black with the white lockup unit at 56px, the maths of + redrob-shape's splash. + - The installer PNGs are the 256 icon and the white lockup scaled, never redrawn. + +Needs ImageMagick 7 (`magick`) for resizing and the lockups; the check does not. +""" + +import argparse +import hashlib +import json +import re +import struct +import subprocess +import sys +import tempfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SOURCES_JSON = ROOT / "brand" / "SOURCES.json" + +FAVICONS = "assets/Favicons" +SOURCES = { + "favicon-16": f"{FAVICONS}/favicon-16x16.png", + "favicon-32": f"{FAVICONS}/favicon-32x32.png", + "favicon-48": f"{FAVICONS}/favicon-48x48.png", + "favicon-64": f"{FAVICONS}/favicon-64x64.png", + "icon-512": f"{FAVICONS}/icon-512.png", + "favicon-svg": f"{FAVICONS}/favicon.svg", + "lockup": "assets/Logos/redrob-logo-primary.png", + "lockup-white": "assets/Logos/redrob-logo-primary-white.png", + "browser-lockup-white": "assets/Products/redrob-browser-lockup-white.png", + "pretendard-semibold": "fonts/Pretendard-SemiBold.woff2", + "tokens": "tokens.css", +} + +# Outputs, relative to the repository root. +ICONS = "resources/icons" +BRAND = "resources/brand" +OUT_ICO = f"{ICONS}/redrob-query.ico" +OUT_ICNS = f"{ICONS}/redrob-query.icns" +OUT_SVG_APP = f"{ICONS}/redrob-query.svg" +OUT_SVG_SMALL = f"{ICONS}/redrob-query-small.svg" +HICOLOR_SIZES = (16, 32, 48, 64, 128, 256, 512) +OUT_LOCKUP = f"{BRAND}/redrob-query-lockup.png" +OUT_LOCKUP_WHITE = f"{BRAND}/redrob-query-lockup-white.png" +OUT_SPLASH = f"{BRAND}/redrob-query-splash.png" +OUT_WIN_ICON = "installer/windows/res/icon.png" +OUT_WIN_LOCKUP = "installer/windows/res/lockup.png" +OUT_MAC_ICON = "installer/macos/res/icon.png" + +PRODUCT = "Query" +LOCKUP_HEIGHT = 400 # assets/Products/README.md: 400px tall, transparent +SPLASH_SIZE = (1000, 500) +SPLASH_LOCKUP_HEIGHT = 56 # px; the lockup's minimum on screen is 18 +# The setup window draws the lockup 34 dip tall (above the 18px minimum); 68px covers 200% displays. +SETUP_LOCKUP_HEIGHT = 68 +# Measured from redrob-browser-lockup-white.png (3218 x 400): the name's ink spans rows 128..328 and +# "Browser" has no descender, so the baseline is row 327 (annotate puts the cap tops on row 128). Pretendard SemiBold at +# 280pt (70% of 400) reproduces the shipped name's width to the pixel with this tracking. +NAME_BASELINE = 327 / 400 +NAME_KERNING = -4.5 / 280 # per point +NAME_TAIL = 6 # the shipped lockups end 6px after the name's ink +# The rebuilt Browser lockup differs from the shipped one in 0.64% of alpha pixels (antialiasing +# along the name's edges); moving the name 1px right or down raises that past 0.75%. +BROWSER_TOLERANCE = 0.0075 + + +def sha256(path): + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + + +def magick(*args, capture=False): + result = subprocess.run(["magick", *map(str, args)], check=True, capture_output=True) + return result.stdout if capture else None + + +def png_size(data): + if data[:8] != b"\x89PNG\r\n\x1a\n": + raise ValueError("not a PNG") + return struct.unpack(">II", data[16:24]) + + +def token(tokens_css, name): + """A token's hex value as the light theme resolves it (its block comes first in tokens.css).""" + match = re.search(rf"--{re.escape(name)}:\s*(#[0-9a-fA-F]{{6}}|var\(--[\w-]+\))", tokens_css) + if not match: + sys.exit(f"tokens.css has no --{name}") + value = match.group(1) + if value.startswith("var("): + return token(tokens_css, value[6:-1]) + return value.lower() + + +def write_ico(path, pngs): + """ICO with PNG entries (Windows Vista and later).""" + header = struct.pack("I", 8 + len(data)) + data for kind, data in entries) + path.write_bytes(b"icns" + struct.pack(">I", 8 + len(body)) + body) + + +def symbol_path(favicon_svg): + """The white symbol path from favicon.svg, unchanged.""" + paths = re.findall(r'', favicon_svg) + white = [d for fill, d in paths if fill.upper() in {"#FFF", "#FFFFFF"}] + if len(white) != 1: + sys.exit("favicon.svg no longer has exactly one white symbol path") + return white[0] + + +def symbol_svg(d, fill, label): + # favicon.svg's own 64-unit coordinates; only the tile is left out. + return ( + '{label}\n' + ) + + +def resize(source, size, out): + magick(source, "-filter", "Lanczos", "-resize", f"{size}x{size}", + "-define", "png:exclude-chunks=date,time", "-strip", f"PNG32:{out}") + return out.read_bytes() + + +def compose_lockup(logo, name, colour, height, font, out, scratch): + """The product-lockup rule: the Primary lockup untouched, the name after it on its baseline.""" + mark = scratch / f"mark-{height}-{out.name}" + magick(logo, "-filter", "Lanczos", "-resize", f"x{height}", "-strip", + "-define", "png:exclude-chunks=date,time", f"PNG32:{mark}") + mark_width = int(magick(mark, "-format", "%w", "info:", capture=True)) + size = 0.7 * height + gap = round(0.4 * height) + kerning = NAME_KERNING * size + text = ["-font", font, "-pointsize", f"{size:g}", "-kerning", f"{kerning:g}", "-fill", colour] + # Generous, then trimmed on the east only: the height stays `height` and the mark stays at x 0. + canvas_width = mark_width + gap + int(size * len(name) * 1.2) + magick( + "-size", f"{canvas_width}x{height}", "xc:none", + mark, "-geometry", "+0+0", "-composite", + *text, "-annotate", f"+{mark_width + gap}+{round(NAME_BASELINE * height)}", name, + "-define", "trim:edges=east", "-trim", "+repage", + "-gravity", "east", "-background", "none", "-splice", f"{NAME_TAIL}x0", "+gravity", + "-strip", "-define", "png:exclude-chunks=date,time", f"PNG32:{out}", + ) + + +def verify_construction(design, font, scratch): + """Rebuild the shipped Browser lockup this way; refuse to write anything if it differs.""" + shipped = design / SOURCES["browser-lockup-white"] + rebuilt = scratch / "browser.png" + compose_lockup(design / SOURCES["lockup-white"], "Browser", "white", LOCKUP_HEIGHT, font, rebuilt, scratch) + width, height = magick(shipped, "-format", "%w %h", "info:", capture=True).decode().split() + padded = scratch / "browser-padded.png" + magick(rebuilt, "-background", "none", "-extent", f"{width}x{height}", f"PNG32:{padded}") + # Alpha only: the mark is the same file either way, so the name is what is compared. + share = float(magick( + "(", shipped, "-alpha", "extract", ")", "(", padded, "-alpha", "extract", ")", + "-compose", "difference", "-composite", "-threshold", "50%", "-format", "%[fx:mean]", "info:", + capture=True, + )) + if share > BROWSER_TOLERANCE: + sys.exit(f"rebuilt Browser lockup differs from the shipped one in {share:.2%} of pixels; " + "the construction is not the delivery's, so nothing was written") + print(f"construction check: rebuilt Browser lockup matches the shipped one ({share:.3%} pixels differ)") + + +def build_splash(path, design, black, font, scratch): + width, height = SPLASH_SIZE + unit = scratch / "splash-unit.png" + compose_lockup(design / SOURCES["lockup-white"], PRODUCT, "white", SPLASH_LOCKUP_HEIGHT, font, unit, scratch) + unit_width = int(magick(unit, "-format", "%w", "info:", capture=True)) + left = (width - unit_width) // 2 + top = (height - SPLASH_LOCKUP_HEIGHT) // 2 + magick( + "-size", f"{width}x{height}", f"xc:{black}", + unit, "-geometry", f"+{left}+{top}", "-composite", + "-alpha", "off", "-define", "png:exclude-chunks=date,time", "-strip", f"PNG24:{path}", + ) + + +def build(design): + missing = [rel for rel in SOURCES.values() if not (design / rel).is_file()] + if missing: + sys.exit(f"design system files missing: {missing}") + read = {key: (design / rel).read_bytes() for key, rel in SOURCES.items()} + tokens_css = read["tokens"].decode("utf-8") + blue, black, ink = token(tokens_css, "blue-6"), token(tokens_css, "redrob-black"), token(tokens_css, "ink-primary") + favicon_svg = read["favicon-svg"].decode("utf-8") + if blue.upper() not in favicon_svg.upper(): + sys.exit("favicon.svg's tile is not tokens.css --blue-6") + d = symbol_path(favicon_svg) + font = design / SOURCES["pretendard-semibold"] + + with tempfile.TemporaryDirectory() as temporary: + scratch = Path(temporary) + verify_construction(design, font, scratch) + icon_512 = design / SOURCES["icon-512"] + resized = {size: resize(icon_512, size, scratch / f"icon-{size}.png") for size in (128, 256, 1024)} + pngs = {16: read["favicon-16"], 32: read["favicon-32"], 48: read["favicon-48"], 64: read["favicon-64"], + 128: resized[128], 256: resized[256], 512: read["icon-512"]} + + def write_lockup(logo_key, colour): + return lambda p: compose_lockup(design / SOURCES[logo_key], PRODUCT, colour, LOCKUP_HEIGHT, font, p, scratch) + + def scale_lockup_white(p): + magick(ROOT / OUT_LOCKUP_WHITE, "-filter", "Lanczos", "-resize", f"x{SETUP_LOCKUP_HEIGHT}", + "-strip", "-define", "png:exclude-chunks=date,time", f"PNG32:{p}") + + outputs = { + OUT_SVG_APP: lambda p: p.write_bytes(read["favicon-svg"]), + OUT_SVG_SMALL: lambda p: p.write_text(symbol_svg(d, "#FFF", "Redrob"), encoding="utf-8"), + OUT_ICO: lambda p: write_ico(p, [pngs[16], pngs[32], pngs[48], pngs[64], pngs[256]]), + OUT_ICNS: lambda p: write_icns(p, [ + ("icp4", pngs[16]), ("icp5", pngs[32]), ("icp6", pngs[64]), + ("ic07", pngs[128]), ("ic08", pngs[256]), ("ic09", pngs[512]), ("ic10", resized[1024]), + ("ic11", pngs[32]), ("ic12", pngs[64]), ("ic13", pngs[256]), ("ic14", pngs[512]), + ]), + **{f"{ICONS}/hicolor/{s}x{s}/apps/redrob-query.png": (lambda data: lambda p: p.write_bytes(data))(pngs[s]) + for s in HICOLOR_SIZES}, + OUT_LOCKUP: write_lockup("lockup", ink), + OUT_LOCKUP_WHITE: write_lockup("lockup-white", "white"), + OUT_SPLASH: lambda p: build_splash(p, design, black, font, scratch), + OUT_WIN_ICON: lambda p: p.write_bytes(pngs[256]), + OUT_MAC_ICON: lambda p: p.write_bytes(pngs[256]), + OUT_WIN_LOCKUP: scale_lockup_white, + } + for rel, make in outputs.items(): + target = ROOT / rel + target.parent.mkdir(parents=True, exist_ok=True) + make(target) + + copied = { + OUT_SVG_APP: SOURCES["favicon-svg"], + f"{ICONS}/hicolor/16x16/apps/redrob-query.png": SOURCES["favicon-16"], + f"{ICONS}/hicolor/32x32/apps/redrob-query.png": SOURCES["favicon-32"], + f"{ICONS}/hicolor/48x48/apps/redrob-query.png": SOURCES["favicon-48"], + f"{ICONS}/hicolor/64x64/apps/redrob-query.png": SOURCES["favicon-64"], + f"{ICONS}/hicolor/512x512/apps/redrob-query.png": SOURCES["icon-512"], + } + pins = { + "//": "Written by scripts/build-brand-assets.py; checked by scripts/check-brand.py. Do not edit.", + "design_system": "redrob-design-system (the published distribution)", + "sources": {rel: sha256(design / rel) for rel in SOURCES.values()}, + "symbol_path_sha256": hashlib.sha256(d.encode("utf-8")).hexdigest(), + "tokens": {"blue-6": blue, "redrob-black": black, "ink-primary": ink}, + "copied_unchanged": copied, + "owned_dirs": [ICONS, BRAND], + "outputs": {rel: sha256(ROOT / rel) for rel in sorted(outputs)}, + } + SOURCES_JSON.parent.mkdir(parents=True, exist_ok=True) + SOURCES_JSON.write_text(json.dumps(pins, indent=2) + "\n", encoding="utf-8") + for rel in sorted(outputs): + print(f" {rel} {pins['outputs'][rel][:12]}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) + parser.add_argument("--design-system", required=True, type=Path) + args = parser.parse_args() + build(args.design_system.expanduser().resolve()) + + +if __name__ == "__main__": + main() diff --git a/scripts/check-brand.py b/scripts/check-brand.py new file mode 100755 index 0000000..2a8ed67 --- /dev/null +++ b/scripts/check-brand.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later +"""Refuse brand assets that are not exactly what build-brand-assets.py made from the design system. + + scripts/check-brand.py + +No dependencies: the gate and CI run it without ImageMagick and without the design system. A build +reads no pixels, so a hand-drawn or recolored mark passes every other check for as long as nobody +looks; this one compares bytes against the pins brand/SOURCES.json recorded when the files were +built from the design system, and refuses any file under resources/icons or resources/brand that +the pins do not account for. +""" + +import hashlib +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SOURCES_JSON = ROOT / "brand" / "SOURCES.json" +# Subtrees under the owned directories that are not brand assets (none today). +NOT_BRAND: set[str] = set() + + +def sha256(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def main(): + errors = [] + pins = json.loads(SOURCES_JSON.read_text(encoding="utf-8")) + outputs = pins["outputs"] + + present = set() + for owned in pins["owned_dirs"]: + for path in (ROOT / owned).rglob("*"): + rel = path.relative_to(ROOT).as_posix() + if path.is_file() and not any(rel.startswith(f"{skip}/") for skip in NOT_BRAND): + present.add(rel) + for rel in sorted(present - set(outputs)): + errors.append(f"{rel} is not built from the design system (not in brand/SOURCES.json)") + for rel, digest in sorted(outputs.items()): + path = ROOT / rel + if not path.is_file(): + errors.append(f"{rel} is missing") + elif sha256(path) != digest: + errors.append(f"{rel} was changed by hand: rebuild it with scripts/build-brand-assets.py") + + # Files the design system ships are carried unchanged, so their pins must agree. + for rel, source in pins["copied_unchanged"].items(): + if outputs.get(rel) != pins["sources"].get(source): + errors.append(f"{rel} is not byte-identical to the design system's {source}") + + # Every SVG mark carries favicon.svg's own symbol path, untouched, and no effect on it. + for rel in outputs: + if not rel.endswith(".svg") or not (ROOT / rel).is_file(): + continue + text = (ROOT / rel).read_text(encoding="utf-8") + for fill, d in re.findall(r'', text): + if fill.upper() in {"#FFF", "#FFFFFF", "#000", "#000000"}: + if hashlib.sha256(d.encode()).hexdigest() != pins["symbol_path_sha256"]: + errors.append(f"{rel} carries a symbol path that is not the design system's") + if re.search(r"\b(filter|mask|opacity|transform)=", text): + errors.append(f"{rel} applies an effect to the mark (filter, mask, opacity or transform)") + + if errors: + print("\n".join(f"error: {error}" for error in errors), file=sys.stderr) + sys.exit(1) + print(f"brand assets match the design system pins: {len(outputs)} files") + + +if __name__ == "__main__": + main() diff --git a/scripts/check-compatibility-matrix.mjs b/scripts/check-compatibility-matrix.mjs deleted file mode 100644 index 6f45a6a..0000000 --- a/scripts/check-compatibility-matrix.mjs +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/env node -// SPDX-License-Identifier: GPL-3.0-or-later -// -// Check docs/compatibility.md: every `parity` row cites a harness, and every citation names a check that -// harness actually makes. -// -// WHY. `parity` is stage 3's output -- the claim that this product matches its authority, as against `native`, -// which claims only that an implementation is here. This repository had NO compatibility matrix at all until -// stage 3 asked for rows to be promoted and there was nothing to promote them in. -// -// Two failures are possible and the second is the worse one: -// -// * a row claiming parity with nothing to justify it -- a judgement dressed as a measurement; -// * a row citing a check no harness makes -- a citation that cannot be followed, which looks verified and is -// not. A reworded label, a deleted check, or a key invented while writing the row all land here. -// -// The citation keys are read from the harnesses themselves (`--list`), not hardcoded, so this cannot drift from -// what they cover. Both list their keys WITHOUT needing a database, which is what lets this guard run in -// `npm test` while the harnesses themselves cannot. - -import { readFileSync, existsSync } from "node:fs"; -import { execFileSync } from "node:child_process"; - -const MATRIX = "docs/compatibility.md"; -const STATUSES = new Set(["planned", "native", "adapter", "parity"]); - -// harness key prefix -> how to ask that harness what it covers. -const HARNESSES = { - dialect: ["npx", ["tsx", "scripts/verify-dialect-sql.mjs", "--list"]], - "upstream-sql": ["node", ["scripts/verify-upstream-sql.mjs", "--list"]], -}; - -const problems = []; - -if (!existsSync(MATRIX)) { - console.error(`error: ${MATRIX} is missing`); - process.exit(1); -} - -// Ask each harness for its keys. A harness that cannot be asked is a failure: without its list every citation -// into it would be unverifiable, and passing anyway would be the silent-pass this repository refuses elsewhere. -const covered = {}; -for (const [prefix, [command, args]] of Object.entries(HARNESSES)) { - try { - covered[prefix] = new Set( - execFileSync(command, args, { encoding: "utf8" }) - .trim() - .split("\n") - .map((line) => line.trim()) - .filter(Boolean), - ); - } catch (error) { - problems.push( - `harness '${prefix}' could not list its coverage: ${error.message.split("\n")[0]}`, - ); - covered[prefix] = null; - } -} - -const rows = []; -let inFeatureTable = false; -for (const [index, line] of readFileSync(MATRIX, "utf8") - .split("\n") - .entries()) { - if (line.startsWith("| Domain")) inFeatureTable = true; - else if (!line.startsWith("|")) inFeatureTable = false; - if ( - !line.startsWith("| ") || - line.startsWith("| Domain") || - /^[|\-\s]+$/.test(line) - ) - continue; - const cellCount = line - .trim() - .replace(/^\||\|$/g, "") - .split("|").length; - if (inFeatureTable && cellCount !== 6 && cellCount !== 7) { - // Measured: a stray `|` made a row 8 cells wide and it silently left the parse, so the guard passed with - // one row fewer. Inside the feature table every row must be counted or rejected. - problems.push( - `line ${index + 1}: feature row has ${cellCount} cells; expected 7`, - ); - continue; - } - const cells = line - .trim() - .replace(/^\||\|$/g, "") - .split("|") - .map((cell) => cell.trim()); - // A 6-cell row is kept so it can be rejected: a row that drops the Works column must fail, not vanish - // from the parse and pass. - if ((cells.length === 6 || cells.length === 7) && STATUSES.has(cells[4])) - rows.push({ line: index + 1, cells }); -} - -if (!rows.length) { - // A parser that finds nothing passes every check vacuously, which is the one outcome this must never call - // success. - console.error( - "error: no matrix rows parsed; the table shape changed and this guard is now blind", - ); - process.exit(1); -} - -const counts = { planned: 0, native: 0, adapter: 0, parity: 0 }; -const cited = new Set(); -const verifiedNotParity = []; -// `Works` answers a different question from `Status`: `native` meant "the code is here", and the shipped app had -// native rows no button reaches (docs/user-paths.md). `yes` needs a citation of a test that drives the feature -// the way a user does -- `e2e:::`, where exists and contains . -const E2E = /`e2e:([A-Za-z0-9_./-]+)::([A-Za-z0-9_]+)`/g; -let working = 0; - -for (const { line, cells } of rows) { - if (cells.length !== 7) { - problems.push( - `line ${line}: ${cells[0]}/${cells[1].slice(0, 40)} has no Works column`, - ); - continue; - } - const [domain, feature, authority, route, status, evidence, works] = cells; - counts[status] += 1; - if (works !== "yes" && works !== "no") { - problems.push( - `line ${line}: ${domain}/${feature.slice(0, 40)} Works must be yes or no, not '${works}'`, - ); - } else if (works === "yes") { - working += 1; - if (status === "planned") { - problems.push( - `line ${line}: ${domain}/${feature.slice(0, 40)} is planned but claims Works: yes`, - ); - } - const e2e = [...evidence.matchAll(E2E)]; - if (!e2e.length) { - problems.push( - `line ${line}: ${domain}/${feature.slice(0, 40)} claims Works: yes but cites no \`e2e:::\``, - ); - } - for (const [, path, name] of e2e) { - const target = path; // relative to the repo root, like MATRIX - if (!existsSync(target)) { - problems.push( - `line ${line}: cites \`e2e:${path}::${name}\` but ${path} does not exist`, - ); - } else if (!readFileSync(target, "utf8").includes(name)) { - problems.push( - `line ${line}: cites \`e2e:${path}::${name}\` but ${path} has no ${name}`, - ); - } - } - } - const keys = [...evidence.matchAll(/`([a-z-]+):([a-z0-9-]+)`/g)].map( - (match) => [match[1], match[2]], - ); - - if (status === "parity" && !keys.length) { - problems.push( - `line ${line}: ${domain}/${feature.slice(0, 40)} claims parity but cites no harness. ` + - "`parity` is a citation, not a judgement", - ); - } - if (status !== "parity" && keys.length) { - // NOT an error, and this was a correction. The first version rejected evidence on a non-parity row, - // reasoning "promote it or drop the citation". redrob-recall produced the case that disproves it: its - // grammar harness verifies that its search grammar is bloop's REDUCED, with three constructs that are - // its own. That is a real, checkable relationship and it is not equivalence, so the row must stay - // `native` while carrying its evidence. Reported so nothing hides. - verifiedNotParity.push(`${domain}/${feature.slice(0, 40)}`); - } - for (const [prefix, key] of keys) { - cited.add(`${prefix}:${key}`); - if (!(prefix in covered)) { - problems.push(`line ${line}: cites unknown harness '${prefix}'`); - } else if (covered[prefix] && !covered[prefix].has(key)) { - problems.push( - `line ${line}: cites \`${prefix}:${key}\` but that harness does not make a check by that name`, - ); - } - } - if (!authority) - problems.push( - `line ${line}: ${domain}/${feature.slice(0, 40)} names no authority`, - ); - if (!route) - problems.push( - `line ${line}: ${domain}/${feature.slice(0, 40)} describes no route`, - ); -} - -console.log(`compatibility matrix: ${rows.length} rows`); -console.log( - ` works ${working} of ${rows.length} (driven end to end by a cited test)`, -); -for (const status of ["parity", "native", "adapter", "planned"]) { - console.log(` ${status.padEnd(8)} ${counts[status]}`); -} -// The reverse direction, reported rather than enforced: a check nobody cites may mean a missing row. -for (const [prefix, keys] of Object.entries(covered)) { - if (!keys) continue; - const uncited = [...keys].filter((key) => !cited.has(`${prefix}:${key}`)); - console.log( - ` ${prefix}: ${keys.size} checks, ${keys.size - uncited.length} cited`, - ); - if (uncited.length) console.log(` no row cites: ${uncited.join(", ")}`); -} - -if (verifiedNotParity.length) { - console.log( - ` cited without claiming parity: ${verifiedNotParity.join(", ")}`, - ); -} - -if (problems.length) { - console.error(""); - for (const problem of problems) console.error(`error: ${problem}`); - process.exit(1); -} -console.log("every parity claim cites a check its harness makes"); diff --git a/scripts/check-design-system.mjs b/scripts/check-design-system.mjs deleted file mode 100644 index d2a6699..0000000 --- a/scripts/check-design-system.mjs +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env node -// Guards the design system against the four ways it drifts back, each of which a build happily passes. -// -// 1. A raw colour literal in CSS. Once one exists the next edit copies it, and the app stops -// following `data-theme`, because a literal cannot retint. -// 2. A lucide import. 45-icons.md: "Do not import Feather, Lucide (lucide-react), Material or -// framework icons." The package can come back as a transitive dependency of a UI kit, so the -// manifest is checked too, not only the source. -// 3. A brand file edited. 10-logo.md forbids recolouring, restretching and effects, and nothing in a -// build reads a PNG's pixels -- so a nudged gradient ships silently. The hash is the only gate. -// 4. A typeface that is not in the system. A second display face is a second voice. -// -// Reverse-verified: each check was confirmed to FAIL with the defect it names put back. - -import { createHash } from "node:crypto"; -import { readdirSync, readFileSync, statSync } from "node:fs"; -import { join, relative } from "node:path"; -import { fileURLToPath } from "node:url"; -import { findInkOnGroundCollisions } from "./ink-on-ground.mjs"; - -const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), ".."); -const failures = []; - -function walk(dir, out = []) { - for (const entry of readdirSync(dir)) { - if (entry === "node_modules" || entry === "dist" || entry.startsWith(".")) continue; - const full = join(dir, entry); - if (statSync(full).isDirectory()) walk(full, out); - else out.push(full); - } - return out; -} - -const files = walk(join(ROOT, "src")); -const rel = (f) => relative(ROOT, f); - -// 1. Colour literals. The token imports at the top of a sheet are the only place a colour may be -// named, and they name a file, not a value. -const COLOUR = /#[0-9a-fA-F]{3,8}\b|\brgba?\(\s*\d|:\s*(?:white|black)\b/g; -for (const file of files.filter((f) => f.endsWith(".css"))) { - readFileSync(file, "utf8") - .split("\n") - .forEach((line, i) => { - if (line.trimStart().startsWith("@import")) return; - if (line.trimStart().startsWith("*") || line.trimStart().startsWith("/*")) return; - for (const hit of line.match(COLOUR) ?? []) { - failures.push(`${rel(file)}:${i + 1} raw colour ${hit} -- use a token from tokens.css`); - } - }); -} - -// 2. Lucide, in source and in the manifest. -for (const file of files) { - const text = readFileSync(file, "utf8"); - if (/from\s+["']lucide/.test(text)) { - failures.push(`${rel(file)} imports lucide -- 45-icons.md forbids it; use icons from @redrob-labs/ui`); - } -} -const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")); -for (const field of ["dependencies", "devDependencies"]) { - for (const name of Object.keys(pkg[field] ?? {})) { - if (/lucide|feather-icons|@mui\/icons/.test(name)) { - failures.push(`package.json ${field} carries ${name} -- 45-icons.md forbids a framework icon set`); - } - } -} - -// 3. The pinned brand artwork, byte for byte. -const pin = JSON.parse(readFileSync(join(ROOT, "DESIGN_SYSTEM_PIN.json"), "utf8")); -for (const asset of pin.brand_assets) { - let actual; - try { - actual = createHash("sha256").update(readFileSync(join(ROOT, asset.file))).digest("hex"); - } catch { - failures.push(`${asset.file} is missing -- it is pinned in DESIGN_SYSTEM_PIN.json`); - continue; - } - if (actual !== asset.sha256) { - failures.push( - `${asset.file} does not match its pin.\n expected ${asset.sha256}\n actual ${actual}\n` + - ` 10-logo.md: the artwork is never redrawn, restretched, recoloured or otherwise modified.\n` + - ` Re-copy it from the delivery at ${asset.delivery_path}, or update the pin deliberately.`, - ); - } -} -if (pin.version !== (pkg.dependencies?.["@redrob-labs/ui"] ?? "").replace(/^[^\d]*/, "")) { - failures.push( - `DESIGN_SYSTEM_PIN.json pins @redrob-labs/ui ${pin.version} but package.json asks for ` + - `${pkg.dependencies?.["@redrob-labs/ui"]} -- the pin records which delivery the values came from`, - ); -} - -// 4. Typefaces. Only the system's own font tokens may set a family. -for (const file of files.filter((f) => f.endsWith(".css"))) { - readFileSync(file, "utf8") - .split("\n") - .forEach((line, i) => { - const m = /font-family:\s*([^;]+);/.exec(line); - if (!m) return; - if (/^(inherit|var\(--font-(sans|sans-kr|sans-hi|mono|serif|serif-kr|serif-display|display)\))$/.test(m[1].trim())) return; - failures.push( - `${rel(file)}:${i + 1} font-family ${m[1].trim()} -- use var(--font-sans) or another font token`, - ); - }); -} - -// 5. Ink that cannot be read on its own ground, in either theme. See ink-on-ground.mjs for why this is -// a check rather than a review: a class that sits on both grounds is right in one screen and -// invisible in the other, and a screenshot only shows the screen you rendered. -for (const problem of findInkOnGroundCollisions( - files.filter((f) => f.endsWith(".css")), - join(ROOT, "node_modules/@redrob-labs/ui/dist/styles/tokens.json"), -)) { - failures.push(problem); -} - -// 6. A button with a visible label and no icon. The first audit found letters standing in for icons -// ('i', '‹', '›', '×') and action buttons with none, and nothing failed. Dialog footers and the -// command-bar trigger stay text-only on purpose, so they are named here rather than exempted by a -// pattern that would also wave through the next bare button. Menu items are checked too: an -// `items` entry with a `label` must carry an `icon`. -// A JSX opening tag ends at the first `>` outside any `{...}`: `onClick={() => x}` contains one. -function openingTagEnd(tag) { - let depth = 0; - for (let i = 0; i < tag.length; i++) { - if (tag[i] === "{") depth++; - else if (tag[i] === "}") depth--; - else if (tag[i] === ">" && depth === 0) return i; - } - return tag.length; -} -// Drop every brace-balanced `{...}` expression, nested ones included, leaving the literal text. -function stripExpressions(text) { - let out = ""; - let depth = 0; - for (const ch of text) { - if (ch === "{") depth++; - else if (ch === "}") depth = Math.max(0, depth - 1); - else if (depth === 0) out += ch; - } - return out; -} -const TEXT_ONLY_BUTTONS = new Set([ - "Cancel", // dialog footers: AiSettingsModal, Navigator - "Discard all", // ChangesPanel footer, beside its own staged list - "Search or run a command", // the command-bar trigger (its key hint is written from shortcuts.ts), which is a field, not an action -]); -for (const file of files.filter((f) => f.endsWith(".tsx") && !f.includes(".test."))) { - const source = readFileSync(file, "utf8"); - for (const m of source.matchAll(//g)) { - const body = m[0]; - if (/".length); - const visible = stripExpressions(inner) - .replace(/<[^>]+>/g, " ") - .replace(/\s+/g, " ") - .trim(); - if (!/[A-Za-z\u2039\u203a\u00d7]/.test(visible) || TEXT_ONLY_BUTTONS.has(visible)) continue; - const line = source.slice(0, m.index).split("\n").length; - failures.push(`${rel(file)}:${line} button "${visible.slice(0, 40)}" has no icon -- add one from @redrob-labs/ui`); - } - for (const m of source.matchAll(/\{\s*id:\s*'[^']+',\s*label:\s*'([^']+)'(?![^}]*\bicon:)[^}]*\}/g)) { - const line = source.slice(0, m.index).split("\n").length; - failures.push(`${rel(file)}:${line} menu item "${m[1]}" has no icon`); - } -} - -if (failures.length) { - console.error(`design system guard: ${failures.length} problem(s)\n`); - for (const f of failures) console.error(" " + f); - process.exit(1); -} -console.log("design system guard: clean"); diff --git a/scripts/check-design-system.verify.py b/scripts/check-design-system.verify.py deleted file mode 100644 index a094c4d..0000000 --- a/scripts/check-design-system.verify.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -"""Prove the guard fails when the thing it guards is broken. - -A guard that has never failed is a guard nobody has tested, and a green suite is exactly what a broken -guard produces. Each defect is put back, the guard is run, and the file is restored from bytes held in -memory -- not from git, because none of this is committed yet and a checkout would delete the work. -""" -import pathlib -import subprocess -import sys - -REPO = pathlib.Path(__file__).resolve().parent.parent -GUARD = ["node", "scripts/check-design-system.mjs"] - -CASES = [ - ("src/styles/app.css", lambda t: t + "\n.guard-probe { color: #ff0000; }\n", "raw colour literal"), - ("src/ui/Icon.tsx", lambda t: 'import { X } from "lucide-react";\n' + t, "lucide import"), - ("src/styles/app.css", lambda t: t + '\n.guard-probe { font-family: "Comic Sans MS"; }\n', - "off-system typeface"), - ("src/assets/brand/redrob-symbol.png", None, "edited brand artwork"), -] - -failed = [] -for path, mutate, label in CASES: - target = REPO / path - original = target.read_bytes() - try: - if mutate is None: - target.write_bytes(original + b"\x00") # one byte is enough to change the hash - else: - target.write_text(mutate(original.decode())) - result = subprocess.run(GUARD, cwd=REPO, capture_output=True, text=True) - if result.returncode == 0: - failed.append(f"{label}: guard PASSED with the defect present") - else: - first = next((l.strip() for l in result.stderr.splitlines() if l.strip().startswith(("src/", "package.json"))), "") - print(f" caught {label}\n {first[:110]}") - finally: - target.write_bytes(original) - -clean = subprocess.run(GUARD, cwd=REPO, capture_output=True, text=True) -if clean.returncode != 0: - failed.append("the tree did not restore cleanly after the probes:\n" + clean.stderr) - -if failed: - print("\nreverse verification FAILED:") - for f in failed: - print(" " + f) - sys.exit(1) -print("\nall four checks fail on their defect, and the tree restored clean") diff --git a/scripts/check-upstream-paths.mjs b/scripts/check-upstream-paths.mjs deleted file mode 100644 index 43128bf..0000000 --- a/scripts/check-upstream-paths.mjs +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env node -// -// Refuse any file copied from Beekeeper Studio's commercially-licensed directories. -// -// WHY THIS EXISTS AND WHY IT IS A MACHINE CHECK. -// -// Beekeeper Studio's repository is MIXED licence. LICENSE-COMMERCIAL.md puts every -// file under a `src-commercial` directory under a commercial licence that forbids -// use in a separate project; all 1,817 other files are GPLv3 and are what this -// product ports. Measured at the pinned commit: 55 commercial files, 544 KB. -// -// 55 of 1,872 is far too small a fraction for review to catch reliably. One file -// pasted in a hurry is a licence violation that looks exactly like ordinary work, -// reads correctly, passes every test, and would ship. So the boundary is enforced -// here rather than trusted to attention. -// -// THIS GUARD HANDLES NO COMMERCIAL CONTENT. It compares git blob SHA-1 values, -// taken from the upstream repository's git TREE listing -- which publishes each -// blob's hash without its bytes. Nothing under src-commercial was fetched to build -// the fingerprint, nothing is stored here, and this file could be published without -// disclosing a line of it. A guard that embedded the material it protects against -// would be the very copy it exists to prevent. -// -// Four layers, because each catches a different mistake: -// -// 1. CONTENT. Every tracked source file in this repository is hashed and compared -// against the 54 recorded hashes. Catches the realistic failure: a verbatim -// copy. That is what a paste IS at the moment it happens, and this guard runs -// in `npm run check`, so it fires before any adaptation can disguise it. -// -// 2. PROVENANCE. Every upstream path recorded in UPSTREAM_NOTICES.md's subsystem -// table is checked for a commercial path. Catches a DECLARED copy -- somebody -// writing down that they took a commercial file, which review would also -// catch, but which should never reach review. -// -// 3. WORKING TREE. If a Beekeeper checkout exists under upstream/, it must not -// have commercial files staged into git. Catches the accident of adding a -// whole upstream tree to the index. -// -// The empty blob is deliberately absent from the fingerprint. src-commercial/.keep -// is an empty file and every empty file shares its hash; recording it would flag -// every empty file here. Excluded by hash, not by name, so a future empty -// commercial file is still only excluded for being empty. - -import { readFileSync, existsSync } from "node:fs"; -import { execFileSync } from "node:child_process"; -import { createHash } from "node:crypto"; -import { fileURLToPath } from "node:url"; -import { dirname, join } from "node:path"; - -const root = join(dirname(fileURLToPath(import.meta.url)), ".."); -const fingerprintPath = join(root, "docs/beekeeper-commercial-fingerprint.json"); - -if (!existsSync(fingerprintPath)) { - console.error("error: docs/beekeeper-commercial-fingerprint.json is missing"); - console.error(" Without it nothing stops a commercial file being copied in."); - process.exit(1); -} - -const fingerprint = JSON.parse(readFileSync(fingerprintPath, "utf8")); -if (!Array.isArray(fingerprint.files) || fingerprint.files.length === 0) { - console.error("error: the commercial fingerprint records no files; it cannot guard anything"); - process.exit(1); -} - -const byHash = new Map(fingerprint.files.map((f) => [f.sha1, f.path])); -const problems = []; - -/** git's blob hash: sha1 of "blob \0". The same value the tree listing - * publishes, so an identical file here and there produces an identical hash. */ -function gitBlobSha1(bytes) { - return createHash("sha1") - .update(`blob ${bytes.length}\0`) - .update(bytes) - .digest("hex"); -} - -// --- 1. content --------------------------------------------------------------- -// Tracked files only. An untracked file is not in this repository yet; the check -// that matters runs before a commit, and `git ls-files` is what "in the repository" -// means. -let tracked = []; -try { - tracked = execFileSync("git", ["ls-files", "-z"], { cwd: root, maxBuffer: 64 * 1024 * 1024 }) - .toString() - .split("\0") - .filter(Boolean); -} catch { - console.error("error: could not list tracked files; is this a git checkout?"); - process.exit(1); -} - -// The fingerprint itself would not match anything, but skip it and the guard -// explicitly: reasoning about a file that contains hashes is a distraction. -const selfPaths = new Set([ - "docs/beekeeper-commercial-fingerprint.json", - "scripts/check-upstream-paths.mjs", -]); - -let hashed = 0; -for (const rel of tracked) { - if (selfPaths.has(rel)) continue; - let bytes; - try { - bytes = readFileSync(join(root, rel)); - } catch { - continue; // deleted or unreadable; not our concern - } - // An empty file can never match: the empty blob is excluded from the - // fingerprint, and skipping here makes that explicit rather than incidental. - if (bytes.length === 0) continue; - hashed += 1; - const sha = gitBlobSha1(bytes); - const upstream = byHash.get(sha); - if (upstream) { - problems.push( - `${rel} is byte-identical to ${upstream}, which is under Beekeeper Studio's ` + - `COMMERCIAL licence and may not be used in this project`, - ); - } -} - -// --- 2. provenance ------------------------------------------------------------ -// Scoped to the "Which subsystems" table, and that scoping is not fussiness: the -// first run of this guard failed on UPSTREAM_NOTICES.md's own boundary table, the -// one whose whole purpose is to state that src-commercial must never be copied. -// A file that documents a prohibition mentions the prohibited thing. Only the -// subsystem table is a claim about what this repository CONTAINS, so only it is a -// provenance claim. -const noticesPath = join(root, "UPSTREAM_NOTICES.md"); -if (existsSync(noticesPath)) { - const notices = readFileSync(noticesPath, "utf8"); - const lines = notices.split("\n"); - let inSubsystemTable = false; - for (const [index, line] of lines.entries()) { - if (/^#{2,4}\s/.test(line)) { - inSubsystemTable = /which subsystems/i.test(line); - continue; - } - if (!inSubsystemTable) continue; - if (!line.trimStart().startsWith("|")) continue; - if (!line.includes("src-commercial")) continue; - problems.push( - `UPSTREAM_NOTICES.md line ${index + 1} records a commercial upstream path in the ` + - `subsystem table; that file may not be ported`, - ); - } -} - -// --- 3. working tree ---------------------------------------------------------- -// If somebody fetches Beekeeper into upstream/ to port from it, the tree itself is -// fine -- reading GPLv3 files is the whole point. What must never happen is those -// paths entering git. -for (const rel of tracked) { - if (/(^|\/)src-commercial(\/|$)/.test(rel)) { - problems.push(`${rel} is a tracked path under src-commercial and must not be committed`); - } -} - -// --- 4. the ignore rule itself ------------------------------------------------ -// Layer 3 catches a commercial path that reached the index. This asserts the rule -// that should stop it getting there. The probe uses a path INSIDE upstream/ rather -// than the directory name, because the rule is written `/upstream/` with a trailing -// slash to match a directory, and `git check-ignore upstream` misses it when the -// directory does not exist locally -- which is exactly the state a fresh clone is -// in, and therefore the state this check has to work in. -for (const probe of [ - "upstream/beekeeper/apps/studio/src-commercial/backend/handlers/awsHandlers.ts", - "upstream/beekeeper/apps/studio/src/common/appdb/models/user.ts", -]) { - try { - execFileSync("git", ["check-ignore", "-q", probe], { cwd: root, stdio: "ignore" }); - } catch { - problems.push( - `${probe} is not ignored by git, so an upstream checkout could be committed ` + - `wholesale -- 284 MB of it, including the 54 commercial files`, - ); - } -} - -if (problems.length > 0) { - console.error("error: Beekeeper Studio commercial-licence boundary violated"); - for (const p of problems) console.error(` ${p}`); - console.error(""); - console.error("Only files OUTSIDE src-commercial are GPLv3 and portable. See"); - console.error("UPSTREAM_NOTICES.md and docs/upstream-sources.toml."); - process.exit(1); -} - -console.log( - `commercial boundary: clean (${hashed} tracked files hashed against ` + - `${fingerprint.files.length} commercial blobs at ${fingerprint.commit.slice(0, 12)})`, -); diff --git a/scripts/fix-danger-vs-brand.py b/scripts/fix-danger-vs-brand.py deleted file mode 100644 index 8e15f4e..0000000 --- a/scripts/fix-danger-vs-brand.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -"""Tell an error apart from the old brand red, which the classifier cannot. - -Both were #ff405c, so classifying by hue sent every one of them to `status-danger`. That makes the DEMO -badge, the Redrob AI mark, the bot avatar and the REDROB DATA label in the status bar all render in the -colour this app uses for failure -- a workspace that looks broken while nothing is wrong. - -A site keeps `status-danger` only when its own selector says it is a failure or a destructive action. -Everything else was the brand, and 20-color.md sends the brand signal to Redrob Blue: `ink-brand` where -it is ink on dark, which is `blue-4` and reads at text size, not `action-primary`, which is a fill. -""" -import pathlib -import re -import sys - -SRC = pathlib.Path("src/styles/app.css") - -# A failure, or an action that destroys something. These keep the danger colour. -KEEP = ( - "error", "danger", "invalid", "del", "remove-confirmation", - "change-card > button:hover", "profile-actions-menu button.danger", -) - -text = SRC.read_text() -lines = text.split("\n") -changed = kept = 0 -report: list[str] = [] - -for i, line in enumerate(lines): - if "var(--status-danger)" not in line: - continue - # One rule per line in this sheet, so the selector is whatever precedes the first brace. - for rule in re.finditer(r"([^{}]+)\{([^{}]*)\}", line): - selector, body = rule.group(1).strip(), rule.group(2) - if "var(--status-danger)" not in body: - continue - if any(k in selector for k in KEEP): - kept += 1 - report.append(f" keep {selector[:70]}") - continue - new_body = body.replace("var(--status-danger)", "var(--ink-brand)") - lines[i] = lines[i].replace(rule.group(0), selector + " {" + new_body + "}", 1) - changed += 1 - report.append(f" brand {selector[:70]}") - -SRC.write_text("\n".join(lines)) -for r in report: - print(r) -print(f"\n{changed} site(s) were the brand, {kept} are genuinely a failure or destructive") - -# A border in the danger colour on the PRIMARY button was never right either: the fill is the action -# colour, so its edge cannot be the failure colour. -text = SRC.read_text() -before = text -text = text.replace( - ".primary-button { border-color: var(--ink-brand); background: var(--accent); color: white;", - ".primary-button { border-color: var(--action-primary); background: var(--action-primary);" - " color: var(--ink-on-brand);", -) -text = text.replace( - ".danger-button { border-color: var(--status-danger); background: var(--action-primary);" - " color: white;", - ".danger-button { border-color: var(--status-danger); background: var(--status-danger);" - " color: var(--ink-on-brand);", -) -if text == before: - sys.exit("neither button rule matched -- check the selectors before trusting this script") -SRC.write_text(text) -print("primary button: action fill and edge; danger button: danger fill; both take ink-on-brand") -print("remaining literal 'white' keywords:", len(re.findall(r":\s*white\b", text))) diff --git a/scripts/ink-on-ground.mjs b/scripts/ink-on-ground.mjs deleted file mode 100644 index 2b0242d..0000000 --- a/scripts/ink-on-ground.mjs +++ /dev/null @@ -1,87 +0,0 @@ -// Fails when a rule sets both an ink and a ground whose tokens are too close to read, in EITHER theme. -// -// Why a check and not a review. The same class can sit on a light ground in one screen and a dark one -// in another -- `.search-submit` is inside the dark onboarding AND inside the light search page -- so a -// colour chosen for one is invisible in the other, and a screenshot only shows the screen you rendered. -// This found seven such pairs in recall and one in query, of which exactly one had been noticed by eye. -// -// 3:1 is the floor here rather than 4.5:1 because these pairs include badges and glyphs, which WCAG -// treats as non-text; the design system's own marks are set where their symbol clears 3.3:1. A rule -// that genuinely needs a lower ratio is decoration and should not be setting an ink at all. -import { readFileSync } from "node:fs"; - -const THRESHOLD = 3.0; - -function srgbToLinear(channel) { - return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4; -} - -function luminance(hex) { - const h = hex.replace("#", ""); - if (h.length !== 6) return null; - const [r, g, b] = [0, 2, 4].map((i) => srgbToLinear(parseInt(h.slice(i, i + 2), 16) / 255)); - return 0.2126 * r + 0.7152 * g + 0.0722 * b; -} - -function contrast(a, b) { - const [la, lb] = [luminance(a), luminance(b)]; - if (la === null || lb === null) return null; - return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05); -} - -/** - * @param {string[]} sheets absolute paths to the app's own stylesheets - * @param {string} tokensJson absolute path to @redrob-labs/ui's dist/styles/tokens.json - * @returns {string[]} one message per pair below the threshold - */ -export function findInkOnGroundCollisions(sheets, tokensJson) { - const resolved = new Map(); - for (const token of JSON.parse(readFileSync(tokensJson, "utf8")).tokens) { - if (token.kind === "color") resolved.set(token.name, { light: token.light, dark: token.dark }); - } - - // The app's own aliases, so `var(--ink)` resolves to the token it points at. - const aliases = new Map(); - for (const sheet of sheets) { - const text = readFileSync(sheet, "utf8"); - for (const [, name, target] of text.matchAll(/^\s*(--[\w-]+):\s*var\((--[\w-]+)\)\s*;/gm)) { - aliases.set(name.slice(2), target.slice(2)); - } - } - - const tokenOf = (value) => { - const found = /^var\((--[\w-]+)\)$/.exec(value.trim()); - if (!found) return null; - let name = found[1].slice(2); - const seen = new Set(); - while (aliases.has(name) && !seen.has(name)) { - seen.add(name); - name = aliases.get(name); - } - return resolved.has(name) ? name : null; - }; - - const problems = []; - for (const sheet of sheets) { - const text = readFileSync(sheet, "utf8"); - for (const rule of text.matchAll(/([^{}]+)\{([^{}]*)\}/g)) { - const selector = rule[1].trim().split("\n").pop().trim(); - const ink = /(?= THRESHOLD) continue; - problems.push( - `${selector} — ${theme} theme: ${inkToken} on ${groundToken} is ${ratio.toFixed(2)}:1, ` + - `below ${THRESHOLD}:1. The same class can sit on both grounds, so pick tokens that pair ` + - `in each: an action fill takes ink-on-brand, a neutral surface takes ink-primary.`, - ); - } - } - } - return problems; -} diff --git a/scripts/install-plugin-assets.mjs b/scripts/install-plugin-assets.mjs deleted file mode 100644 index 20ae819..0000000 --- a/scripts/install-plugin-assets.mjs +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// -// Copy installed plugin packages' prebuilt files into public/, where the app serves them. -// -// Generated rather than committed. The ER diagram's dist is 1.8 MB of third-party build output, and -// committing build output means every version bump lands as a megabyte-scale binary diff in this -// repository's history. The npm package is the pinned artefact -- docs/upstream-sources.toml records -// the version and the licence -- so this script only places what that pin already decided. -// -// Runs before dev, build and preview, because all three serve public/ and a missing plugin there is -// exactly the failure the registry's `installed` flag is there to prevent. - -import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; -import { createRequire } from 'node:module'; -import { dirname, join, relative } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const require = createRequire(import.meta.url); -const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); - -/** - * Plugins whose files this app serves. - * - * `id` must match the registry entry in src/plugins/registry.ts, since that is what maps a view key - * to the base URL these files land on. - */ -const PLUGINS = [ - { - id: 'bks-er-diagram', - package: '@beekeeperstudio/bks-er-diagram', - // Copied from the package root, so the manifest travels with its own dist. - include: ['dist', 'manifest.json', 'LICENSE'], - }, -]; - -/** - * Source maps are excluded, and the reason is arithmetic rather than taste. - * - * The ER diagram ships a 3.9 MB .js.map beside a 1.3 MB .js -- three quarters of the package by - * size. It is debug data for third-party code we do not debug, and including it would put 5.6 MB into - * every build instead of 1.8 MB. If someone ever does need to debug inside a plugin, the map is one - * `npm pack` away. - */ -const EXCLUDE = /\.map$/; - -const pluginRoot = (packageName) => { - // Resolve through the package's own manifest rather than guessing node_modules/: that is what - // makes this work under a workspace, a pnpm store or a hoisted install. - const manifestPath = require.resolve(`${packageName}/package.json`); - return dirname(manifestPath); -}; - -/** - * Injected into every plugin's entry HTML, before its own scripts run. - * - * The sandbox that makes plugin content safe (`allow-scripts` WITHOUT `allow-same-origin`) gives the - * frame an opaque origin, and reading `window.localStorage` from an opaque origin throws - * SecurityError rather than returning null. That is fatal at startup, not merely degraded: the ER - * diagram reads localStorage in a ref initialiser (`show-all-columns`) and in a store's state factory - * (`debug-ui`), both of which run while the app is being created, so the throw would leave a blank - * frame with no error the user can see. - * - * Measured in the installed bundle: 4 localStorage references, 0 sessionStorage, 0 document.cookie. - * - * The shim is in-memory and per-mount, so a plugin's own UI preferences do not survive reopening the - * view. That is the honest cost, and it is the right way round: the protocol already carries - * getViewState/setViewState for state a plugin wants persisted, and the alternative -- granting - * allow-same-origin -- would put untrusted third-party code in the application's own origin to save a - * checkbox's position. - * - * It installs itself ONLY if the real storage is unreachable, so a future arrangement that serves - * plugins from their own distinct origin gets genuine persistent storage and this steps aside. - */ -const STORAGE_SHIM = ``; - -let copied = 0; -let skipped = 0; -const report = []; - -for (const plugin of PLUGINS) { - let source; - try { - source = pluginRoot(plugin.package); - } catch { - // A missing package leaves `installed` false in the registry and the app shows its empty state, - // so this is a warning rather than a failed build -- an optional view must not stop the app from - // being built. - report.push(` ${plugin.id}: package ${plugin.package} is not installed, skipping`); - skipped += 1; - continue; - } - - const destination = join(ROOT, 'public', 'plugins', plugin.id); - rmSync(destination, { recursive: true, force: true }); - mkdirSync(destination, { recursive: true }); - - for (const entry of plugin.include) { - const from = join(source, entry); - if (!existsSync(from)) { - report.push(` ${plugin.id}: ${entry} is not in the package, skipping`); - continue; - } - cpSync(from, join(destination, entry), { - recursive: true, - filter: (path) => !EXCLUDE.test(path), - }); - } - - // Two edits to the shipped entry HTML, both recorded because editing third-party content should - // never be silent. - const indexPath = join(destination, 'dist', 'index.html'); - if (existsSync(indexPath)) { - const before = readFileSync(indexPath, 'utf8'); - // 1. The plugin references /vite.svg absolutely, which resolves against the APP root rather than - // the plugin's directory -- the app's own favicon would answer a request the plugin made for - // its own. - let after = before.replace(/(href|src)="\/(?!\/)/g, '$1="./'); - if (after !== before) { - report.push(` ${plugin.id}: rewrote absolute asset paths in dist/index.html to relative`); - } - // 2. The storage shim, inserted at the top of so it runs before the plugin's own module - // script, which is deferred by virtue of being type="module". - if (!after.includes('redrob-plugin-storage-shim')) { - const shim = STORAGE_SHIM.replace('

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 qIPhCyj5it9Jbpx0*S#IF;;Qs(hh$)Ex literal 0 HcmV?d00001 diff --git a/resources/brand/redrob-query-lockup.png b/resources/brand/redrob-query-lockup.png new file mode 100644 index 0000000000000000000000000000000000000000..9169cf856c768b726cb0b3f101f451bfe1833962 GIT binary patch literal 166580 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Гd=!Wq;! z;ZMcuf}hrzmA>YK2yMvFL^49e)3EIkIm6E|(_Z%)2e8`oLiEw1Bhw-&DA?;-TP-35 z4e{YpfV;0pI4E$Z38o|HK`poJ8-7-&+%i>`nuLSgDlLrtFO-R zef;XWV`p$tONgWt6B}ZJ%noy;2;WceCh$>! zH2K6W+6a_8L&k;#yyTaA*1NwMv<8qaW61 zV1R9^Rq{3vwhL@o_^!0}Mkju;@=sdxPkxVOA0Q?*rwkUx$8j#Tk5*b3AB)Nm(dRS8P3C~^XSaB)4UT8EsNoF#ERa^KiJJHoMlMq5f=XamWL)ZJ3yk;CB2h2n3zFB62QDVOkx8{QtN;%ZGJf)4twh;g=BNoQ z7w#b8t5Em~{y8Q<4ZGUVft4u%NO5Y=VSRbq4aRFXnLxyRHnBIinmM}tisnss+R6_x zzcT7ec@BMQGBipb{xfM>#{ahp1&vGI`r0Qxs{ArXNnMJUsOcMOI1a++M2y-7Cu~TNCKW? zIym4Qw9q=EGAj{;uAaDhC56%Dh|*EO?=}6OVO(SKgMiIXL6$E$t(`)MQvFPOM|owt z1a(YCO00lOJcSBxG_V_phMr-wYsktf&={e?2>=~=o{!QIS1u$HIYCmPgndhtxIT-z z=K17+x3ZO@J#^C!&W)irNfCQ}GA{z4D!Yhk94icSY>NDnOc|_DW-~S?fDhWSGKM6ouMN^_#J_{!R^WS2G7Wu{pxzjDE;%iR<8 zmbTv-o<@JN-FI+17o+WzzPC~W3n+kKzLuZPdJ9Goig~q9{~9MAS)w4Faugu|qZ`%>`YEtB;3rfO5|CX-tu!0O@cT4vNSA=BIE z$ulvyA^QtdWjNpRI}^0M-mXH<#JFlTY{^gpK`t@;@3-38&l%|P&V1Jr18~s%v1{;(Vd2!x8%%z^=VY^bhpNByYhnvMfw^Aq(xYF{wntKE_7}KTcdf z;+-1Z!l;*hti1%-_HT?XR|aee#EVxUKl;=g>o@5YWqE>@ua1Z=0|zjUIj#H&vjTWk zW=s}PxXpnbxtPYRpFL#nkvLm5)vv6IiO!~8PunGrcBivSoS=a}fpLtv8GjyRSl|`$ zb@|UmcgH+&%*O6n2T_|qRDZj{!sq47Z7;w|FZ`-l!5<3{22C0MQVy_63ZD7$NPN7^ zp$ftPNbl5rBmtS5SFZVwdskq2Y6p=JRF}n5P zeGmnv*-?JzF%f`H`wom#bo8LiNI&b&9Ay1;@SGEn%kvPxA60Ns8A|bA3F8vg4K|VA zu9h<>Vxu{u*i<;T&~yHq-rerqw5s?F8tinWsv%^zPv5nq$&w^1CyR%2Vxs9;_W ziV4fomp*eud22%Bzb2_)@@WqmpWliItKu33)}T+x+x?(VBl6#Xw4o%Cu zs2y)z-D-WntDMxXY*p_BjM-N?ciioU!EaqR>?R|t1Cr3Vkyf6=ehqApJ-_q9!*Yg$ zclt7E#F6l?(0Bb25?kNfB9~uM)Rqq_cpUZ#3KZf_#nYC1UXyKVzKRC8Q|LH|emQ2t z_14Vj+d=E9T`7uqrcvLT_><(LGCv|T43ZsVE`|G6j*e~W&o6-S^i}9pVm=l5F=Pk^ z+zPj+MXcNlnBW`%W`F79aHLlU)83Mqd{y+}?Vw)?!&MtB5)KOq<%C~LrmdzipX?kc z(sEykSk&QYmU=0!t*4@O^@J3ygq%(@zo=w62?yjL5O4`Vdk`)txb5h}(o>m*gm3Sv z{iREkv46DcB!PwZX*Lc!%{Zpn-A8N#nTgB~EP(6o+7Pp7ZSCU5$8Jj6Td_}tKNR!lo^`P+ z(Mgs)4c$;!3kT3@GAAMCwijf!14!=qgNpMdk7^lk`Gbhwp}Wun5bDu6+B17cC>=Kn;g~Lkv;YIA9%jEEN7943C zey{B0?eFx=Eo#TZ3|pBoxO$x2JW9kVfnZk`#!12csI=BVAG6m_tglNyiJ?1llu|kC z!5!-whFdh#rd{d8z|BGr;fmMeZ=Rn#WJ6mrXY<$i6M^_3 zBsjWY;k=>aC9Ya<^Hqa-t0%w9i!_rA))NR?7jcS2*RfsOo0$s>k{xdM4tkPERN@H4 zfQtsuW_`@lAze$L-mzP$K{*$KkFx}fePq6M2BlL+N}t$$xnuAb3PQ*j4;&G4`XbRH z(b|Mirc3wQjwz{YUs?O#=r-msDX*__abdzjyqKr<-{qt8y)XO$YBOSY&v2bFU95NR zMnBV%g~juhi8F27GMcws<%;@CeFDT*g=UgZnY>4$%G~>s5w1*iK6)9fsemA&9y;oz ze$E-uRTxYp?7v>cYmZ|bdzj$RV;_GgHT3*N)3CwFBRqHsmylFhze4cV zk6@bdJ7hBfR=;fP6e`@&=Gi!>)p;o5_{JBc!N#w}7)Z2U48AWsQ8<430Pd{6MwdG` z2cZ4Sey^6nd)^fn1{6~*Vz_P0DS8-~W=m5t-tGW3u7|PwJU^Nd0G;Mtj3-GBA^bg{ z!)O$j@xSDA^c8qv@#99S_`36o7VZi~V&m&o551Y;`Q#9)9@M6YixS-*ndry}T<{*F zaZ?R9f90sZd{$!%1xWMxx6(`XzQmY{maVbZb67u3G6Q?t=|8vr zk%hVP^Swp*n( zb`jETh->0sK}kqmaZgQ&L^6%SXrRc;v>Tk!yFmq-_MD`PC$wQXisXf^S-<%2);Uzn z$@@`GG7L&5Xc6lE)%o-J%Gd5Htm=5Xt}v9?Q=M6oBX3K_7qkNqA#n>>;m}?P2uI6F zVV)wN7HDC{O_iuXxo)O-Rg%)%J`g9S%`m@ltxvoW`y}(MR3NIeF2`{>HQgkB{{du$ zoqK3`T4|aO(k1i+nVC(!KEKu22^*`vCx)h<2qT7%#s!-U57dZuPx!tn48YzouKT|E?6fm0FFJYh^ z3lL5i8*|i@ac~rlYhQTsn{nNV9N3gg;4{W|dpwT0viYMp#dm#4!yt+V++)`?8p3Jj z?Vf#)EG;wAJg&WP!>HIV;@kx`(Q-k7kBvMY_1{w8c{F{f?Wi0f>s}i)3e!Jw zXFk4gx{t9dE~R{t8-uE)v~A%09Km6y|MDUV|-P<&4FczoK^?aa#neJNb zIDd{ZY4m#uSRrG8HC|iA1ZU;%(mt<3zA)d0+B1K#Q>x1ML*bAP-F7-$5Z|-D3-(m2 zX9*N6wxi1XdjKbZtH*Ne=`!zegu~F4TGF+Ll;I_SA*$eYx_Y-RwJlkujb%UTOXg=} zG6qgY(oSCS27dj6riqeD7UcYCIn;axu6n7-r!*ORkQH4+RWo#5CSQ&vuwBpBOhvUi zs@UVT>zLDXcY+0?RA}S|Ai=2>6;;0!?p|0uXac0(OP)m<7Raj)jZ7X&6b&9WBlV4U zxrQWaCO^zQ0`9tkj*?iXz!7bH2Z%1E^`TKszobHTgC<)k-Aim+|EEIpbX~Dtd2Yy@ z%(I#p-76&9wf-tcly+QIL%nziuiP%fTWOp%g6{;aQrPgoZ?~7MP4BpYkKsf;38;Un zMFjnqa)q^zq~R#)jdC@TfLh-nty+uRYE*`IqowT4o!Ib1Z(4e0 zm$*x|UoXdoR(LmPB!-l@QI5-PR7y7<`XTcS$jycO%bzr@`O;~V%Y^ecZR8w?fXLOi zc8$pA%~8cgVe`gCJ?k%f80oYJ#N~gaID@_3hZ%D6RN{i&;+>;&f2-&IF83Sn!vDM~ zf0L>iKO*N%;D zbnVBx{Qv;bzQMJp3HKtphrM$)V~o+>K&O(c!IIjLVRiP`1Rkpl4 z_r$h*V6|30L$##cqqGwN0A~rd8m=eDmD40YAZRE_NS>q!fJzkeGS{qtPV8fBfbfA> z5xXf`mnI;qAoMs!4EM46|Ggi*s^XQux1b;K<*x^Sb-C~dd(W7Iy(e;aJ~^(Y#MMb8 zP}0=C^eDIPi6?QN*(fMtn6v8NXe{Z6b&QXdkz_TpmdTbd01XGIkwfu84IY$0%<|-F z=x`moQw1KFnrS8?<%fPCu~{R-PLp-%pZ#s3m6BvG*3vBt9I@(`t(E)2q8QA}(pfx~ zD`Tzlo;AWX8}miJUR=+1?p~x#oYW@m+ISNdz94a&;`BVs!kP zVhF4x>FFkL?zw9j+KvBoH)_;dyfn4q%zvEX6j}g%U9xflkp9GPH1Q*qo}T>(53?1o z(DAu^gdygIyHy7$u`d@QcK)}c$Rpkw%S9n6Eh*noVP-Sm!RF!Fs7XX~C_c(tL)Jm` zDoTfcm6(V*e}^JwYw0Z=r$2qrNj-CvNkCUH4kwD`Ki^E}|46QyzcW;eB^Fo zF1vQXZ&s&@OW8Z~w+m6vTU$D8#SmU+H{XdcaF%$I5UEo3wnC}#rLW^+jyzGHD;2$H z`7&u68PI~c@p8jn3@ARnKDMfq_M|izv!&3R3Tx%F*uj zYdF6Y{BwwUj1y%Sr7>w<7ApL(t!Tfv5EvJlf6Pkn#3fBY8RHzDGu3L6Nz%4pBh|k4?8-3FgTH*yr=b>hpg~wpJ ztP&Rz;m5XBdXL_<3dHC5^*)#tRbeP@d|b-Jw#{&(8BCv-asD=7Vl6~$HE{I3N%W0K zqblEIj+3JCQH=r(-}gjRGGHa74GSjf(Lui!4SK{etwE$&g@IpE`G8b$)(*i$AO2$< zl39?@|M03w0^+o#ms&35u>+lx4RJhbHs~8_IKQzjD{A>(xhHferlo0sE$~9=m-kl9 zUIhb1B}2VRtO_9s<+QS0^r_H#Pe$Vtw(gqtVpJ zm*_gC0NFb9RC9}B2f^j_%<+rHQ5ORV4s6`ndC^r)Qejcc#YMU}J2PRYgF%=xOr|Z+ zfA+eJA^}5{V7JH(TSP!d{31F_Ov`;vKEOvj-ood8^E00Sl$b6lopaM^{2TA7xbuo# z>&UOKnj~R9-B*JBQ}LrHQ8MJ1BC?MAlh=VHqr$o{=2|Td9g|9bgi-cxSh<~Ud{ zoYi(aJTR$j@cb3i{g^-J;ONM(<3OlDMlFRqd)l3mDDs!uDp34ON*pWjLxG=9jQuz5l%A}QRp{UXMpiA0{0Q!SD>T3s)Z>YeHWEU}x3s1B zTXq7sz7tbaDukDBm*L!lM3TIvrug zkp)Y_PNYvYRQiUtT1g>DjfLIFU-lGCo70~mi}S8Cl~(%p@ObazRKq#_|50wUdA(k8d65=Uok69hb|M}-TixsfT2sTxBov>Ltf9%7x>0Xa%s5b4 z9TqPuO;3wFnwqd%92aSj-`lOSUxZqlchsIj(HDoz?66dS?rA04w~4c+3R(yn@A#zH zZPz(}z;=u3wvj()xol#c*76z!_wmH92`8nz_aXqwFWIVbT*M>*RnTgB-zn=C2feY6 zGKq}q`7Nd26^CVb`9v_httdJ|&xr}o@2jU*XjteThdPa6-=~yQSo3|}v*C)j= zH=*hX{$Axir=JjdOs5VOcSKkwv%(`cvhMA}P_BPO0xV^aRzR{=R0a~T^HUPyfI0vx z9RZPK^iHv&Qwi1=u#A%%pz>pa3}Pq?tci%9e{~8*?pV(~iZ5pVBkAAzAZA_tM}FogUpfL6Thj~;~4~%g+#!5;;B;U`{~|q>@Q%$F+x6Z|B>@- zQ*0aOthxwoPjS~6rhHbdfz$j7^&5!!w18DqhmW(T!?d&k{)i{yl)>(QL`eR3WZZe5 zZDEVB$1b@#k#?L}Sb8bV$6@utkYQFlqp^2>v5-Zpj&7O=)npwP*NPpY`DMJhl#9^u zLxTy~a{ZRh^7XHtO=tmbn4w#PyOvHTv+yE*)kd{`6QkZYA;F7W=t(Bo`U+3{e1`G+ zG}Q$Z3O)sbsYohoY6Fy3*Smf8_IWvbI-Kc(0+3d*ly#@u|FW zo!87yfciM*shRdNDKmEWy*d@)S|J*U)FFnF8&ujo-*VI+zJ! ze&d_&{cRJHgc-=tVx_n2jV}Fp*K7F*%@AKou=|_9_;%u6;uO+6c!*TrCoy zVsTLZ1_ALGN5e%yNoGBjV+}_Vf<04(ZkyX)J3jvBT?)Z_M@PYr@H#V>i7A7Iw?n~t zd+hUOWTeczdo>1&^-W(D-X%xKCkj~LIK+8KneqkjuRJCF(DPgP+IP}4cZDGp($nZ1MX|bFQQy(^QdGDxS=U;Cg zZCZ7vMe;tmzCibR0uaQjbi_2)FT{I*KinIusEYUFpqxk%FRIcT`*otw`2#q*{h4|- z@Qr3P4=nHeN!pi*Vz>eF+`oV9H!@-b-Dl}rU`WyYp{B2lxLCGS;iD24y)(BEph$A~?L6{n!bAoyui6RT4aX1XfrvCW3j*s-?m{ zn+f@FBU(x$t#G(~^_0qEA{^ReFQz$Qa>2O;yErP`AF@=v?-86J2UH+9gkp8kH~$yb zeXX0%6iv&mKkB0KKI{s|ZI5e> zW$WtMH!ppFWD(Rgchm1U9&r9vp52}^NDN!_KSX>lj~W3!nYt;oAK`i40ba7Aw07zr z9{lTml8yXm*qlUTcTtqceM%Dy5`y~wwp?-)jj4)>1J}~O$-j?}k=hh1<@BSMp$0^p z^cQt_T<^UZR_7yEBawX$V0dC9JMc!gnPe)JiLQoxsJ|RTrF7yV6_^-3Kn^zsH;6f= zx5R)YeeQ~3Fa`L1ffN@h$Jz|fb_oDxL=XkaGMO=?NKYiOCawkjaJ+tE{9Q6j@Zg`6 z{_{fdFQJ7W>)D;ZaK*G|;|uOb3?O{_(bFe~YhVaAG|6yN^*>X?nSNy$MnW1UH|Fdt z3LuFM^Q!txbw{ya3>bHvqD#YhStg0E?4;**{?N2i22dk-Sj<+TnF&9K#kt@-n$HIQ zhgn7|Ai#QdVd?$(g8cRVgp)hAa1HnBi4#t@2`+m*;wY*;lPr=bru182z$DBYw*aip_AnY z;KHrfpfcjed_;u8S;Nk^Ch=L*Z;r`DM5%fT%k@2kAq_0GK4=O3I>8UrLe$;c8)JWzH%TXZxm)o*3T4l`fPJO-_s}F7GT4UW(qc>wfqt1&R zZJgjH1wu+AblHCVHLmwq)34-EU}k+0K|+s7;9yM__1meUv+obhiO}LTcnYMJT-Y%J zBK}kWIPoqbqtE2LyH{Vg)-~+Y{2lg@z&^&G5E=y?X@!pWz9=2Y=c0M7nAicdzq7rg zMT<{g;=e{oM(dK0MP0)OvSRhc*pvmxU|}vee(I>-R$aONv&erHat>dZDHhi%G>G6B zXLP9jnK9GM{&^<@sFBNROOz02{r)jOUS;Tb)UNdJ#rKQX`y9=#f<_#2bFC41t-4)i z`YpCLnB1<8!>%Uou4M;46{FPJ{gO@`KmE#}&8?cC2d7C8-%ZK?_po{?r>gLeXC>r8 z*co3e^W=h-2$+fU9g3kT5)1WZ!dvY2dVS%k5qgWEUIw;k+13}Znbq;huRCjC;z$3a z-`NkX)cu{ntmZ-bAcY0LV1d0uq`JO$YD%AKpSejzm1Zq;iQ;=MvY>Ubt)oJ2)IUfb zTm??lc^g&<23Q-n;yL<7=o>|CS=g_3g|Traa&PCR-SBL7bm~7BL;DL~F}24H*u8uB z*?w{23hy2N+}GaD9^g$JwPo(8x;ENRO~G&~Sc}yvYk@ik0(F~OEHA4JkaMvQ68guv zXj zpiY7(Rx4&EuyJopp#A_G<{_aafWdoC6Z?H+K8j>?_$&~Wh0za__X)#!Y4!IP-}wt2 zK>f|XkJ=9MuL)RPR|wZg0hM6&HCVSAFlfa-7Qy)s*rM(C9B*yNHPLIG)em%O8o+f; zvD&jfuL$|koHE-eem*J}#UE(qi1Iv;aXPr%7WFOh>p1sAJ2Wl@{g!MJO=s#E-$qBO z&-w0DAN1k|#To5_Rp`A-e-0R!2*MAn(Io@83>;P}`m==PIte{Qt;jEmoOVVf_qU*L zw0*69{3Lg6);vC#i6Ihva#}2U-M0I3C3?^moq8}R$_r-h$N$aKc4w(NNS>nZ(zVt$ zKYojSr=-5E#olhjR_yHYH4ajsl#6R?*T>cZ2t`<{W7cfyKodN{@l8gbBt~k(PX?mS zt#(EgxZ;#*%^G+LWdoU?Y57>ZMOCm-9Dr?ad4QOetR*zv zu*C-+h?I_Hkqd*fpoR#EB1o%ucm25C<#DpsvvZ?O5;+ z2K{sg0tyJ%->|yw+$qEVS13H%r_>8FYHgio7|?R)UNoi4tAcWUc2%foS{&NKS{AR{*k>~bMOrFOOW9BO zA#sk}s1(Idj)yxIged|>?c}^K!S109apMT-H*H_OBe^+;m6$xHhP+VuAaH#9d%J@wRrpFW(Q4pr?4NT6~EEEUIG);W_baB8rbK&%vLbciBD< z-6Cs?G@Np-SF0 zaa=ahSP03Cr#}II((~C>E-cZO@fhhnHAujFH5A`xU52kv*UBOH08G3kR&LI$9&$|$ zuGDt__gV&fDiG$6VB8><7iie*@6;pgm5jyf$@;xtY)|0Pvg(g0B|jRczY*wx(=S$I zmfZe0EWwiyxJVnR6(E523YDyXe={yZ#;IwlN@ue+E$xRPn_2iHxZ>hmOv2xZpm@a0 zwy!sG|Arq4u3XSUSd`_sz8PxWlG#USTSM<+SB^?DQZsM&JFWbU^+%nMsiyd*?r-K+;ah7EehmXiTEnP8lXKPk$9 zl*kwG(wNrHAi@4faR!~>)T*{|2@*BWm1<+4#N+ByN2y0 zU<@AzaQY`%xBu)*ZROsQ01CuWJBL>`-I0I%2D93jmWc4S-K9qSIxD%Q8xL&LViJKz zi0l9@_!Vm^D|M8$!#R6Q7WR) zL%t9dAz*LBSCkFS8yOs1k>P{jXGRcV$s3h6sOW0k$A_g?lw zvs-9W90C-eD@Mf0tl_<`rt_AnI8p!UO!I1eqtj08bZkQ6p6C1Z-OI*^Mm#?v)gFG_ zgC9+frNTmjNI8Yp?(716;i3*Q4kIxn0|rNtIzOW;X%F0@Ta(T+W85z-n3zn8K24F& z_GYLj#g#10Agwqwd~>_HmJf7VK4Bdf%(_*7Rytp<%^rlMD>o6@ADKDi^8N@?V4eYz z)KG5;-MG?6_qq&nmL=nTti>gYLh;P%62;v zd-+qoSyz1!h1`*^Vz*K*R+z71AH9)PFp)r=unmYw!O^dg~`; zb8P^=z0;F|WQ>-lAR7k&YGA+uk2eVr+1X*S9)~i7iZeIt+_cKcpqRDmkUqgD0Ve#= zAiiOv0&vzkqoN)IPv3m>k^FjxC_RS=vXT@qK|sG_UnkhNtJD>z-3ZmBVqfY%jN~@1 zIY~dyMhu32!2Q~ajQNI0k0If?Zit13%kFdBCOolNX5sM_OiB6crS=gaZ)-O^t=L7g za~oc7EBx%(!2+t4?PH=hG2Sv_WBjbbPB@$1(N9lIGzzX)f%YcMb>qqpRe$?SF?(QGd~N~+?kr?e^;*v%+Ku_nkr z;KA;nZ_%>MP~FzYs{T=u${YUuMarIz{&e$l9ARC{4z-!8Iw7{GZIHK1t<_?iRc@g- zrb7khPEF^2y5pGZ`ri`;u!Fx;v}~n8(+El(lLF|xNM6z&inaG}12C9>q1C)5K8i;cb;HG!T?sfQx7<7z^M6-r>8XV#Mn-tXKAN&UpVz zH$QV7|G8I&u2x7~ZU@&P5lc&dqRcD(Dha<-Z$siEdHYrXt}+wz=}F)52WyTEF^2%9 zN4Z_~(H{fb{oCbj>&C~nT_T$+6R(fd5hJLVhY@6Aw0-2|vLEtip05^_3VKBZh zoto36I=vPLQrT!=bRF5wXM4V8tu)%cXAPQFW!oPQlSU>e**AMp*+!ozd>(eTWr$-BrYX@@<=4W51hHv+}R=$095Bz)jn?e|u)l zn##sXw7%}h-t#%y!GVes7VeHGK$3y}$|JjTc#;=c&tRO)O*Yg%$g`9nhPi1&qCwx}h;AJ1K$=2;krgr4~96*%g% zuBxq;hmL!wVo|tmd0wYk*gSP(A^6YDW*GO>dHZ*!b1PG^l=))A?PXAsbPn#0HX!}M zufat)*nsIFK6p1RvL=_F6ajI2G+9^P+W%QGK14P@oXRh+d0cYbQIs2;kls!p7nfLO z(qdV{t%k4d0q!&6$>#uQLO$OvDzYzApYZAzkjsG5yVppDW%;w)HL*`2FS(g?<26=M zFNk&j@@c@)pS3KdW`es>R4W93kCpL|IZ!-`J#jR|yV|fZ zeE7)mp3!Y|L+5Q7SDjE0+Yhz|6+!nTtGz{m65_qBkjOLsbri!XWJV6u7w=n03F#Y% z8n$VIL%%MR1P`#UmLsY;Wzso?jBjKeEH{6?IJyWT^JXwZ_2pY&VC=tWVGM2PA9uK- zvaaWb`ffW(#-go&A`bO|#d}Slm}gQWl-%VyqKS2Rs}_xURp6u3?@Jr!#^ zsHgkWLTbN@QYs4FVDYJWqZ{@e4I8KoxVe4kGx_>dKU>|Y2*B8O%>BDUUXNjf>CNK=zdzC0e43pKbzD8vsH)B=}BkzM7{J4GE$zB?Z{A0Lq zg{`>GJgGb6i5qMH&Pp;6pXZ*EXH@ut5s5emA$w*uw{K-_0P6ES)V6j*N8Oyc5RO z7SD&JWCcmp0f|wYMmZNHE%}l@bkkljGn6XmnLZ(rUho@jyU|GkYV&V}riA#!tDZY| zrh2jDA&I5U8DWY2vc_CnxjmZ)-*nmsbrOBt3;8`hgzT|nPp4`enW=15i8>Mu&Htp+ z?~!xEK})PbY+)C?n3o&+V!`zTS!v{c3DHV)uilkj$NjmehXRCz>ss03774fAr*qIH zLd9ol;{)cbv<#wi=TE+4&Ma`Xp%qR5Aa)_XZIjAXa5wOdzjg<75{Y-RyNAmfZ0T#b z@4vqLYco7Mq_t1sR!K*J4v(5-Jk-hw^KcDu#aU|5VOamM7x?5H#CMyF^-2vT{=f(& z2!gs+9a%6vaC{-5D$6PTqL~={BU`lR0dMod@-hg`bB-zmy4}bgW^l5fZ(iktt15dg z^5d^T*n1ap%Lfao-SZPpGqTXJicU6B8d>o-XQ^BkQ6PK^9(QB4(H6*W2g-g6Tf-r10 z3z`|Js(rmIW>NC2^V}!PSrb1=J9bh3dG|gQ9Ew(d8$z~Iax=uTF~#@0Ft&+AAFid?8{)5|Sv=$x zR4#MFwi zj4{3g&d=0&l1CyrSc67zZjO!Uj%-d7tBsbh#AHIlJvmBL<9k z`CD^h>@g}q1@vP(H5C94H$9!|>A-$Fkc%ae{=_mCX8^lgi#o9a{86Q3g3Z3r3`5YB z8Ih%Av6+bmLw?X9{zklxvUc=Q!Qt?BtbK`CdC5D^H8eAY;xX`~Al{4yqf{TRoN~3H z{00Ps$NsVXL0``_o3%LmP4M+c2Y6EVfd3-;1m7bRE0rbw(xQX+wb{&Cb*gOXc$cXM zPXO_&o@FA_lOTvsdQyTnvh*B+2--0O{+_@hplj)j4-f_Jeg8{~4vVzahY)9FptsrI zp%sZzj76eN83hP$B>wdURy!W(NA`b&Nz%a`fo19*LC2|J8KC5ANPhK`?ejXyK+70` z)K5_xrh1QuMT5wzU_j{n1n%pW7I^%eu7220O?KU0saTyX0q;lmhMyCuv0ZW!Gl?F2 z#h1?7l1p?kpZ)rO1o&cNcTg-)9Y`}PZSw>-i@@bUU=r1GglaA0`S%=FubhX_=z+oy;C%sOr zyG9jH8q8{}LHyaer7hl!c{cyarbWnplk)`4h_vj*>}_XtYTubto%IBR1tr^6f`{ue zcGs8&$5DAyC-Y43!Q z`#LfwPCrTbcns=hx`lf8&`aD?%DQiW%7B&_i`AuvaLpFr2VzeA&U06sN4AK9E4TmU za=O?i7j3qBb6=FEApsPAt*0pjYfQOC1h7WkeTvHy z3d2YnQ4WighfuEL^p$pV8!Qb_ageXJBWH`v;0Q>W_N0BK#UIGXc=R@A*o{J1_X|4ZVU8w=G0(!^ZY zppdRA8A8w8{uL|#J?hZQ2yLdvhAhp{?wI@A{_PFfJxE%dfE-S+uzY;AcwRq3BV_xk z-Jb#pz9UBA=WOfSmNlp`_mnh4Hk^wx9tE-KzF&H3c#Foqs|IPck07f4z8@Ye-r4MC z3AAT$CMbM?zMh-|WjvAj$u;0tM}zrttsY{Qm=)`N)oo>}1YdhW1;r9h1_Z%GE;wL$ zivpmlu54Yhr&yH19k18_uo|HLC;N)4^k711Crb4#DL~=aO3V0FH$db#ajyYTMRHg_ zr0#Aw74s9~rBac-dNu86_qCLRFdR%_EHs{So#oI*59fj{c> z5*fMX4g1nnZ_X z@kj=6W&XTow$#iq;RJ4ae|EDw2H3J|9uqhPN27^(U&b3hW@`54}7< zDQ|dZI~Jk&UsjKMUmU978!F~c%O8)H(Z8HDUpIeZL9=3y{Z6IEgmQD&StB@navx-m z^RD%!2C_?p2}jSR&vPlQCGoL2QbaF|cH#j0cid9SIKd(!Xx@M-io+e$43{yMDnMrvqj2wwYw;>VqgjH&SVBj3-Tm#h8r~TK zg$CjH#VyVIBS&1v<*##t712%gUphC|KjXR~LNDt$YIn|j8rNq$kFV0j-y~WAJ<~Ku zR7{)o&-XL*-0E`5yWN|ltCsjTE1KN(xG#UOx~Z@(>(6CWQhk1WL2O^6p^%Z>;IeE# zv^axa|K-rjnKvUx*Nj<^Dxx!cIj`N9U4B-)&1Untv|wHuJdo5_1U0&%>RX(7_rZOx zUr_@DP!OA826|9@RuuV`4iUh3GA=nV?=L~9{_=^IgC_hWGXw=1Td$XUj=l zwWiQodSG?9q@-lSKUYVIcR>rx>o(UFJNe2wThVgs4-q3vZfOR&is5lHjcedHwb|5> zY0gi57gD29;sG`Zg9HZdJCVN$b<^J}x-0NbXoj!azO%>88gVu5<6JlMEKMx3D&u4L z_|h9Fp{~5LXIJ^o*{pOn*aZviP>n=MtC;T+2tjiL_vCs0L-hxUEr|45uMGT)m zN?$yvFH-&ZRZUpT|GfqwXSs8pX>`MPzbKISWFa!-6X2NRmv z^FYk=2C2V1n`4US?+gjg5ZeUb$K`)IGhlc|1u-7e6Cr|`81(bfZZ?z;KV=pL5mNqr zpYa|4NpF?!MGvxY^`vr|q*w*D9TbR*L<6 zZ|S~nY6!qP#NTZ8BTX>^mW%x70;kFx1x61B6!>Hl3}SFiD%l$|{F%-#Z5g${moizm4dgKEV?+e~L`eaDyIpz($Y5!|vn9x%=P3lZ=E zB#|A216B#2peS4pkLCl%i?@IeFa+4?+_&Hc=mA1_FkAp@_ZD`E*Z`auiJ(y@4V3-M z2>VC0B)9ENxvUir0$d7Wqd4SrTU8>*vV@T9mjM-2r@=A%@$ce{`(&7NEh=+cF7Upp z`ELm?Y`*f|MN3rDzu58+v?4pFWOhj|%*(J|O#OhDs@gY#;go9DU(zs8ao}kyWU6Q6 zpsj4cVXvL3ecEXC*-_L{BrvWqxupaYZ=E)uIvQ+=c&(I`S1+ZGVd>ui>N+00dtWS3 z2x8odXa7lSl*V1(8kZNPM^(!yyE1)#IU)36w!=BO2F+lC;0bi>jt^-iD1BZR+BdVYV6T=6Fc z*O-yV@cwivUQ)T?o$Mbyq+p+K>Mc04IJO%wS{mMXKkK%gMH@8sMq6tRy9;WDM^?$Q zt!&~9fg7N}i=>2Q`peAXPQ!Z(d&$d+yIE6Rq^uRiny{&~Z1VqL$In(^oIjYh21`F3 zTC>~z#7Cg#1zyoOPOp2Heru?{8ZXC6%8A)w!@wNY5#)zrKwW zVPrXSO=M_)zkh;(a};vZF26|3*N?1}Q|yJFetefyw7V&Rk7lcLl^n&Jn61HWyn2lfFzOw)3C+US}x?833ys?rTNd1sp*lxt;_OHs3scQ zKIymIWMO7JxuL#-qMSeg>c-`b8?Ck+PYhh~=P(25CFkIN8NkrsMkf-6Zc^`C?-r8g zkRqI$`J?r`+*#WEZnBwv#cs&La=A>U_#mZi_SX6^tcFr!!z0V(K*ULK>6a3uaen|) zuq{y`J?=qbk3yAx#~=-}jAoG~NNdh^x%zFRW9t6f3pn^sDlJB6+ zP=fwBm^4YGVpDeaOf-B`7biEh@7X?0+Lbd8E=Ix>g~y3>@bsVJee{(=8j6q`qS$9& zxC%VP$J|Kft!u(tSPw9`jTBA`_XC2nxO6V_J^0N|1kqeLhuxd%UKN}1*zf-K=yy%M zlQtIB580(f{B2fbUyh9ugovf9B?`^FPfy=Q;3)G>d%~9G?6iqQcTc`=dOc1JxegVT zuB92~6+ue{vVwhJn{18(0iYWvp=UhAJ8d=v#Gh|~T=qw4)U`l>3JdN5_(v{1cx!pY zV0GEQaQxf-f15AD%OKUEYqgn zcqI4LC7c*~V&zjl_kWfT zHMs$H?AzQkn4aPmc^q?g!L_~axXMj3f`Q+FJwm2+JPV)aFygVNv@3D#7FTn>;dm#c3KbT5i`epR4(r`))bA$)ARas~Jtd&Tir zhz#$b;;rEVl1MO2Kxb!{_AGWJg~?hwd^omi-n9+-PWRNgFr8|HUf=oLP8X>mnCquf%@{bs?GkpMOakP`vu2=dXzacil!kpg{AH$4rT?b zSFvvhW8b=;I<4xXwe{;<@=m)8o-MmxKuv$92*h>t5IlJS#8A?|-WEQP=rO~pPNVkQ z#5R6F==L~mn7=G7eICc?7fT6>LM>`b826IS}7OA)6o=3lv?8p>G;s!Q}`1YdqOIn%q&w zmjR#RedAz*zR_)bL?02rp>N`fiv?#b4^;VlpP;M_JjOeD{XWQLC;6V8W!#qN?lwEwrwX*9MWA z$swpoq&PgA*h{}ICSGV8D!P8CM^AlffwpFf6>As-q+1(go5xl*b0k5n4Kv@WTUFUk zp1sbv#pT*dC|@{RYx7re=_D_b*24kPQ{ z$~3am+`?FEb(}~VBnjR~%qT1Y+9X0T=@L+qC-)VF*42gb*hi%TWfUH5i|tl?@A(xD zY_O5?h7oe(FSqINRA}fgqR+&gP11E84Th8t-KNX(J)&3(RB>wBx8(hOg;#EO4JLew zcyekEu5jQ{YYRnNrX3M;dLv>3X*Zd@#kIWn!IVN>(asT-l(0aYR0&XG{CttAG!}t5 zpw^KCCBU@L9o4a#iX+UhmP|2sBADwnUO|xh3rClxnY9p_B{aHra)0Gd01yBu%1Xay zvS`N+5KK%PRwl1Me0X8cuw(G_n|$K9;yzo9SQ*WVf){yeQHK=6TjW|Ioa&jC{efP7 z<|P4TJJ!9_)IXe|-1K}E5|qAN|yD?9~BloJBVU?#vrxJYmF zVFyH_**)Vw|Buc%bfG(P6XqoLO2r;jkl;TjBElH;dg`)=_+Y^LV?Az9m=VOKa3uqu z^>4=3BjH$vRAxwNecwQ7TQ=Ho0Gx1>ba9EemH3J@AqyH8q5HD7423c{uxeR;6tj3>>U~irqJ#RODUnGYD@T z+G6*U#DoYW2QjVOp#c%VUlZDy+y>e|!)G=TqAN))A!_AaZ8-i3n^FK^YzU*h~|fC=sUwmCzxyf-H& z8+bkYSqiE8gHiDuyjv*Wvfy0l%dBM3Xlh1SAIbRKX@+sCACfO_?MS&uLmT4k!0*rR<&@^^Z99LOKA8s znC@>k<3j4ol@6lind{A10nWWw?d7~&--r@7S`F|G^df|a70Yf1U03YJpXNlmlCa@( z2b5Y>9Rk`8ZY2&azXlxF6{w8b(+<{4va5G;c8h^_n`mLHMhice@%~#d5B~JY)?>aI zf&01)+hZLM4A`h7|3s-?3l4*K?buywplZ-Z+N@$-6lfPQqPud!S8{wZ<|*?M*N?ox zb6+HW3WGFC1gxr_IP#_t5klPeM7}=j91U}(4T2&930G_Qav6*iNG6=I1(+Sg`pUBI zA!xQBMMe9X(12aG-<5JG*u=Y6gKI|~fv2U9!BkY!UreNbd&wm3A@nmO%)a9&gZEaN z8DWhj=sfT_%qMj#BKW*hjSG(c{_(&~d~;~~rr{SRZg?y*klga=S=SPn_Js|(cLnb> z3OVqI2`fz$ifqBMum@k@QGAIo+Z$Z`y`^YxGd1v zCfa>VwQxMecy7}iy7bS58j);pD>(01GOj~^3E`&h|LWz}wmJr3S^(ft7%`kZrQk?t}Y%r?^e{Lg}|TxzO?GV2XO0`UfZq)CtrmRK-! z-ftvh72RJ2CeCFPd)_oul&=R7@>UAcy5jXM*Thq>P`tALq92YmfcWl|tJ4x%(K}y{ zh@F>wDEd7+ovYDdm;N^?kQ01krTveNtOaR0*7VBfCKUFDxi|A;x=Oczb6f(mXW*V-N`*$Nuz|pmh^I3rj@Is^psa&0hbFxy&{2lw`tmi1J{>Xmp3Sl8Ap2aZE_E^Y`1EG6If_=p1P z-@gy5qSg-^Vs%wO2q@3GzMEj+YvChOW+B6whfQ*25bwM$>mU4{0y>Q=yRUrTOuDR$ zMR6ImV!#fYu}q1Lfn(sLiy?NV8Oy>bvF9xU{DTNu2NE0+B$q^r*k1vU8mMcOJ8SO$ zkxk7JD~B{LZrxvTk^$S{mKcgGTHgblS6V=>3LA3$JAUj#2Rn;cjubY+1{Do~vl9=! zf73yyre$IQqH9l9@`>}kZ1|>)+LUs4Ik^MN59mbY$d7}a^-GEj=n7OnJ0k*)W)@{j zG458U)}k1tBn;Z)V}BSJ#tk!Q*%*j4&c;+xw$-p-(|1`+p)2qc1lk&~9=0o8xG$f! zyj~(Oj9d7kXL(rUdrh+Mkja@*_x*92e}A-QXD_(Y`}slpVB}Cb1n~=9Xtiq?spQ9^ ziZogY&&N9=j_MZCy$|G6yol+Rqc_-U!JHtNy90{bB%7~!DmcwCfVco71UMMd8dX5p zbg9gxA9=j1-J9Bk@5=S((x9IUt3+O51?A>^tF?vI$&rc&#~_IlC#pT-`yU$PAoTRg zY&2BUkHP=o!C&c*2EI4j06RS~XmRN0rzKc+yq5uJ5JT?o(eQh4U>7Oa-%GU{j!%M4 zUg0^q`z^^TAOKe%UCxq>b0HkR-nBd31O$C*Z08t5##h{RJ2wMBZE?mk(+@1imw+n$ zJJIX{EgTC_zY5ZVY6ksP=w*P1Dfn{Nfymbf6+DGpFo?_yCeDxaec!q?bPidE5gTyBso_(y-|=Xhijt@K{lj$$IZ2|^lRLnMnc>x zr_L9on>;F-le(#0N9+<6_NF4RSghu1Lc4ZeNH8xx%f`yq4&KqsL{xEVJ0+=s=*m;Bm8n>3aqxO$C8r)k&)H zJ3aSIqlOf<#kW!dzmY`RSU7Y0!oNnT1e^Ae!O6A{h;Q(4-PP!}vQ*MP6e1tfZE_;) z6xxD>aj4@F@d2Ex2}^TM6@GpuxjCgww+{v|;cx(RH1HjeeTHmuQj;ifvfo4vUQW9j zp(Zg_5x@r-kGU)#dqWkgL%!`5TCpJxF_HG&oG}1-UTc#Z_rgznD1?apNG2Q}lmUg% zpgFo!1dmT(45tg_>wmSn3A75(xu5u%{`;J%do=gPcnY)sw{NcHu3NWDMSnC4gkZ#Z zU7~Q4Y<7xX8GJ^BVFUMYtuc6Ns251qh!X@W;5g6#rB+l3>awxe0=8!;#;lZ-!Ed-# z^(UJa%vD4kuE^sLE!>yuGe>)v)e(KIbU|+Lh{~V0fKxpuU85n+N zAb9C~2@Am|n0y{E<{Ua!!PE^t4FaKD#EkVik{`nm)`)Z%_4b_ZEf{upSnCi z$FU*WXQW9$&*ifPVkbd`e#_TL*1TnFUw4?5x_QYNsqzZLcqNp+;x%61Jjw)7*_i!e*bX!aRsb)9jFx+T} zpPkN*%F<$|h#hGmnZ7Z3Al6RrxatSBd*N(yY}0%&Ii1~9$ha9}KDq0$9fL#9xXrms zc6(Iyhlrli3B^t3hD^G{A|-eBa{O?7!I;gB=FOI>#n*2lnzGsRP#q-pI)96#bp&BN zwNeJ9b*nI~LwaVxx1@SnYa3q&caw9Ev4-G0 z>sTbQ*q=4Tv^S$q{_vTRux1THBGSbjWQsxu-h0q%9$~{2tDm_)XBo%}X~zS?WobpL z)OT^bpPlX3dl#flU06_Sk z6!3yZovA9M5wAq={<3GQ|3g|CeZ=>`{(r zF&OoAAA8R2cwK|R&Vkvsg)@I;*y8ab;0ofg`u3^PQ32tJg+=UHG`;lJ4cE*}Dy&IH z{zhjq-_Vt^GV>vdpS$+|QT3HkRd&(V&!I~=lz`MBB&C%GNdZAp5v3cI?&i=Xt#qiA zC@I~CZctD<4oFC+gw)-<_l|Fj@0ULmhGRc_ueIh}bIrBcG{n;I;_dslYdZ@DTj2(6 z)s2dr@YS>Y&4n#;8ox*pIw*X3RB>s#^4E+g%c-vWj&5i2P17zesv8YCdWdKF6{K+#{ zJ4c|42Ki@j=eW5)g^(YZ&%r+*m@tD)5H=<7Dz6%Wuad1ZI*-SV`-iE8FunsC>eum*P`eN4w( z%ey)8orU*borPYTT79O`n+UKgWp{F&h|zxGUy=SNjN6^3QcJ|%11{Hsd^GwLzesE7 zC0*T{z>kAzvn#rs!@?Z=5ayE6tC^uAeST^4u%l4bmd4ZSWhDy;%}}QItgoNhsbFxb z%mCh&3F?#1_=RVrULx{W+P?F~+9g8eRaF%8@pw}F7VuFDlr6>T?tD;u^o$hqEF6kp zQ|>kGVdJ{OM0&4`o`pfk$N2D^RE6RxC~<{Sfo%*!u&_jfP( z$yL+4i-GY)=U4e{>=Y_Po5L%gDdE-Cyi)|kymQ;?4t)p~`WC2nuv_raiz;-t8i*|t zRhItDiQ`csJzVJr)IBqa+eN_1dH=tFO6bA_sHwH>==|K1AJ=t@McBcW3+3;!E|c>e zpwn*f65TO)(DQkFtY#NXc;V0VU3Bq6DW?C46SP%v`KM7E-wgilh?Ztb7ueN#elT*adCh_*%L`rWJyo7bkDSN;h4zO z5)IM!$5&KcYfaZC$2_A<5F-(~_C_6do~i_^lb?{?3L%6fABW88vqa{4Y#^Fwkf53Y zA4mZCyOH?+G`BBPl>$}!#2ktg(mcG|($~)9Go9K;t08Gzw2RVZ$3LEDIoBZ(IapmD z?98ojTiU1eU%R3&3cRcKMXLXHi1T)YDtsV&BQZlRa)3^d(sW_`siuWrSyUQKe>R-& z=$FZ?o2ux!T2aMo>}{e0U)2ibE7INc8JE`*5`@+HUE$QG)lIiuQVk7yZNDe#SATTjyJZvw;oWy?)c{-9Q%`_5Q^q*QoBA9!VfD8yx0$<0FOv&tq>O#1x3uN?R zAn!%8{6ON9_ZdWkJm!C>jRSdHtUU-a(~ka7Y1Hh=i6nteQS`=D?pWTU*{O^KBpwyR za|#xr^90o?A4~UsP9;zp>QovJkPeTJ*7!OQ=o8w#Hk_P8rq8rZmD*K?oqN<5xp^h~ z*Qa%I-f@G~G?KX~ifBFaoe7y=X}4-C4r9}dHb1e^+<9%UvOyGN@+Cj-pEJ%RL5QUF zVGbgfk7(j2=UR)`{=LT2x|A|6T3Qq8z4bDq#OQhu-YCAGU`9QEE(n=)^Ax2^Th1rY z)~#DG43tW(Zeq&h_%w~WDPS8pv_TN5{ixeL@}TCq`Qh&3Rn@$rG>dUu%z1ZIVAVHs zAFz)@l=GP?*mF~>^?2&V1AC);0IY>diKI{l^ecL|FZ%Z1!8x>4Y`v7cwmMEjhe>^B zqDAeRjKuL*I4-(3@kq*)C`hnb#g~?Ij(_xKIA?gt^xdv8_GaZZ=&ul>OF<~Z--H-I zIZ*~e8EC#l0PiGhlpRDj<0fgY30>i%jQ^Vv_MrmIJf?lT7?y=tww_4jWW z3ejU4@S6^Qd)3FN0i@B-=+9WG2Z@h|1No&U3sY%7L$1$!dCp~J3?*96S_*#APgoNE z-24d7mfv{aJk>Ia)IG#zrps^*>-gyRrGf54ZbPgR`0yHkyxx&)U=e4WL^~5=K{%@- zP$msas7hC=^W*k`hFD9C^0g%#1RWmQ0|YPpf@Uh2aN^d|fg%GU<%{0VX6KMc%r6{mXXW_6IEP%!U z_#VrV4+0CaSdlZi-qzo^@7=ym&a%w7RYm&;aY2I=f-;RR&Q-u~G0aI)c?3H9sYHH% z9;nc5j}3Fuhe#tIV(uZA7D|a{pY|#)NF(LD^CDppb^4PIYH}-Cdc$60+G0y3+7t=P-Ze%{L=PU&LEfQsmJ$_kl;x^w=?Tl~yr6Aw}OxX`keEL{oH%f_bz9 z&@#y7f4B1Rv%^wgN$vN3K;_+d{`KofBx=SO5ppasyG2NoW%K@31X3++VCzHcP_2ebrNQYCsd zrNMjWP>b$`g#2u?(kUBTZcHQ7m#f(3#{xoqIuKplKa;@AAY!@DS<9>LbDxEbc&N^Z zOzG7`pxPZk`9=F8`}0tcy_o a0a6gq$W*pda(_-bdPyVs*KWbH9>6>cBF+k5tDx zn(hMl8XWQGN^|Q4sR;Qkm?xsZyH$BHb#^1&>@tGK{toX&#Gz&VK+W%sxzsHFiSG^e zy}F1up$oUP?AFI^fA9X=B0MLbpl>YN@tCWKEK-yEoLQ$-wV1yx9HTLF|6LgmJ3r<8 z?BFo*oL@ODAB)HOv9lJ)Bi}_Pzo%5Xxhw&Do7S2PhV%Zw9w>w9^oTq;%rGYajQE+3 zRJ6;PztzpQmY`maeFQJSm3X(ON%Gp1B?rUK48ct5@m#OA_UoXDo%xLHgwAGPi<=Sr zm_hA=HvOFW9$x!>^R-W7gnqZ`g+(gpTb_T;ro#OEFD^8)tD>R~rzXIqr7PJhj_-$Q zm-WOx-ZuZ^3F|ArYUNbhsDBplz|+_8Ig62l49*5JZv6HuG-D&N3AsNZ8bok2T_Cct zpbxpmN7ESg2!>g2Abde@;Hyvedq6b?naO--fh6fr0){=6_|ec+-Ra3oXadkVlE@_J z!huW)*z|F1(YgXM$rIz?JR`U9?cGQg;HI0D3Ray!_YYt+IlBO_fYHal@gw5=D!v|> z<+O086JD3&&GUT9V|wEz*)I=|YUaMA_-*zPf^;u|tYtjhIO1TFeEE0J2CS0WkQDm7 z^)7$(t{2wCTxy#_tjUa4b+Y)l6`O&*M7ni!)b0CRi##=U0#3JAkqtsM`o~`=3`$*8 z@hRoW3~(LommmK=d8|(0ezH)W$fy-3glPfkD)I%y-3p|k`Vs$d1cyEQZ~xT``+U*k zkSdY4oDzr6{G4ujEU8il(U?UY&^^~14RqsBVCN&u2pf!90A6jG+l3)qvzAcd+Y(Es zk0I((lBTw48F5v1Mv1?p4r?t~ghBk<%UMsNiP+UQX569NuI4Z+x+Y{i{r(0B<~fn-yhV#y~oSLTUJ&Q zjn+)%1UmhY*&%0bFZKTlBeQOxm?P=7sE>G=3st){rlNj-Y3fTJG^(RvBU-LA!wsiEfg_j6D{Sje)+7VG)g+k`(W)d-L}8 zhjgRrd`M3FHmRsla46Q_>+w8>k9D&g$%uDo058%7=KoiKz4($dl~Eyz5i`!7$dN?B zWQpj#VNLa5sUZ92v?0~c8r2fRecOST&SO7p+)LfvGvE2JzFE)6pfQ+pEy{AoC3Soj zk^ay2yy0U_Caw2Q)?e&~v>4UR;;)GkF<}N>%mF?VXS(wp;0CkEaIccpp2(t zqVgE=tG5cG^iUPZwk6{)qgYs}O?MnM>&{<%BxzuPS5^_%c*B>|$r$bO%XH4Q5AL%< zm!-kf9V#kxex;iTs~FZ;cL3srkt$NP0k=p!2-_8al3E(NB1Uz@OPG!j;9`1;BMUbX zk|Zb}m@gR2c`r4f`v;iXd*JNtLb03T$!scgStY@H7!dF*yd56Cj>uz8rtzR>_~8cK z8i)CMS7I^m%&TU)1dTvpO05<$%}9Ky0wH7NsTX`b&mrHH;WA|-)8swG%MU&9MgKp) z3`D}PIFN@4uvoEuu6lxcS0t%qISqLZ}PT;TNEEA&RHu1c?3%4(bvIK@vzMb!t;@B)W_GeO*CW3M0v z47i`)e5W+u17Qdq3~ey&@+Bsh473dhRZ%knz`~$9rtg^nPg4=LIjV_TygREZUasWAD<6W1KW&ya#zs$JglsS{ zKSeP?ygT9u*~tM(&?oG6q(`=Hi$}b;8<{(@iMXIZ_PEol5_*iudMf-k2N8i7WC=ar zmxr40P|cw9c?e1z4@}Qo;B|!Q3A}w$Jm}w*KLDW+c1pEesjc>HeY6MGR0C)lVr)kd zsL&0ta36xpNzgFEo*!scAZBZPfkEoAYdP#?xbUSI2K8jdd#j3=NN-=SQ$p-8(e+gF zr5UrpD}}$OeS3~Xcz3WIOQ&l1nC{%(GazV${p^}y87OKoT!*PwKdIBx$hUO)-AK7G zexMVhVd{=<5OZhRcvSj<*YZwH-qYAQ^(k}b(fbFM5}v4EZ}hcl8x_stXYln+k-GUq1R?PRBKVreqLf= zE5j|)vB0j0QuCsq{w�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