From 6c533e7ae9e55b64e5ffe63a9c9a6934e9250938 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Wed, 24 Jun 2026 09:04:09 +0100 Subject: [PATCH 01/78] fix(upgrade): compare prerelease versions per SemVer `isOutdated` stripped the prerelease suffix before comparing, so beta-to-beta bumps like 5.0.0-beta.0 -> 5.0.0-beta.1 both collapsed to [5,0,0], compared equal, and `dcd upgrade` reported "Already on the latest version". Same nudge in cloud.ts was affected. Replace the naive major.minor.patch compare with a SemVer 2.0.0 `compareSemver` helper that handles prerelease precedence (a prerelease ranks below its final release; identifiers compare dot-by-dot, numeric numerically and below alphanumeric). Add unit coverage for the regression and related cases. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/services/version.service.ts | 79 +++++++++++++++++++++++-------- test/unit/version.service.test.ts | 35 ++++++++++++++ 2 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 test/unit/version.service.test.ts diff --git a/src/services/version.service.ts b/src/services/version.service.ts index ceda057..953a4b9 100644 --- a/src/services/version.service.ts +++ b/src/services/version.service.ts @@ -3,6 +3,58 @@ import { CompatibilityData } from '../utils/compatibility.js'; const DEFAULT_MANIFEST_URL = 'https://get.devicecloud.dev/latest.json'; const MANIFEST_TIMEOUT_MS = 3000; +/** + * Compare two semantic versions per SemVer 2.0.0 precedence rules. + * Returns a negative number if `a < b`, positive if `a > b`, and 0 if equal. + * + * Implements the prerelease rules that the previous naive comparator dropped: + * - A version WITH a prerelease has lower precedence than the same version + * without one ("1.0.0-beta" < "1.0.0"). + * - Prerelease identifiers are compared dot-separated, left to right: + * numeric identifiers compare numerically, alphanumeric ones compare + * lexically (ASCII), and numeric always sorts below alphanumeric. A longer + * set of identifiers wins when all preceding ones are equal. + */ +function compareSemver(a: string, b: string): number { + const split = (v: string): { release: number[]; pre: string[] } => { + const [core, ...preParts] = v.trim().replace(/^v/, '').split('-'); + const nums = core.split('.').map((n) => Number(n) || 0); + const pre = preParts.join('-'); + return { + release: [nums[0] || 0, nums[1] || 0, nums[2] || 0], + pre: pre ? pre.split('.') : [], + }; + }; + + const left = split(a); + const right = split(b); + + for (let i = 0; i < 3; i++) { + if (left.release[i] !== right.release[i]) { + return left.release[i] - right.release[i]; + } + } + + // Equal release: a version with no prerelease outranks one that has it. + if (left.pre.length === 0 && right.pre.length === 0) return 0; + if (left.pre.length === 0) return 1; + if (right.pre.length === 0) return -1; + + const len = Math.min(left.pre.length, right.pre.length); + for (let i = 0; i < len; i++) { + const lp = left.pre[i]; + const rp = right.pre[i]; + if (lp === rp) continue; + const ln = /^\d+$/.test(lp); + const rn = /^\d+$/.test(rp); + if (ln && rn) return Number(lp) - Number(rp); + if (ln) return -1; // numeric identifiers sort below alphanumeric + if (rn) return 1; + return lp < rp ? -1 : 1; + } + return left.pre.length - right.pre.length; +} + /** * Service for handling version validation and checking */ @@ -29,28 +81,15 @@ export class VersionService { } /** - * Compare two semantic version strings - * @param current - Current version - * @param latest - Latest version - * @returns true if current is older than latest + * Compare two semantic version strings (SemVer 2.0.0 precedence, including + * prerelease tags). Returns true if `current` is strictly older than `latest`. + * + * Prerelease handling matters here: a beta-to-beta bump such as + * "5.0.0-beta.0" -> "5.0.0-beta.1" shares the same major.minor.patch, so we + * must compare the prerelease identifiers to detect that an upgrade exists. */ isOutdated(current: string, latest: string): boolean { - // Strip any prerelease suffix ("1.2.3-beta.1" -> "1.2.3") and default - // missing segments to 0 so short/prerelease versions still compare. - const parts = (version: string): number[] => { - const nums = version.split('-')[0].split('.').map(Number); - return [nums[0] || 0, nums[1] || 0, nums[2] || 0]; - }; - - const currentParts = parts(current); - const latestParts = parts(latest); - - for (let i = 0; i < 3; i++) { - if (currentParts[i] < latestParts[i]) return true; - if (currentParts[i] > latestParts[i]) return false; - } - - return false; + return compareSemver(current, latest) < 0; } /** diff --git a/test/unit/version.service.test.ts b/test/unit/version.service.test.ts new file mode 100644 index 0000000..4ffe6ff --- /dev/null +++ b/test/unit/version.service.test.ts @@ -0,0 +1,35 @@ +import { expect } from 'chai'; + +import { VersionService } from '../../src/services/version.service.js'; + +describe('VersionService.isOutdated', () => { + const svc = new VersionService(); + + it('detects a release-level upgrade', () => { + expect(svc.isOutdated('5.0.0', '5.0.1')).to.equal(true); + expect(svc.isOutdated('4.9.0', '5.0.0')).to.equal(true); + expect(svc.isOutdated('5.1.0', '5.0.9')).to.equal(false); + }); + + it('detects a beta-to-beta prerelease upgrade', () => { + // Regression: both reduce to 5.0.0 under a naive major.minor.patch compare. + expect(svc.isOutdated('5.0.0-beta.0', '5.0.0-beta.1')).to.equal(true); + expect(svc.isOutdated('5.0.0-beta.1', '5.0.0-beta.0')).to.equal(false); + expect(svc.isOutdated('5.0.0-beta.10', '5.0.0-beta.2')).to.equal(false); + }); + + it('ranks a prerelease below its final release', () => { + expect(svc.isOutdated('5.0.0-beta.1', '5.0.0')).to.equal(true); + expect(svc.isOutdated('5.0.0', '5.0.0-beta.1')).to.equal(false); + }); + + it('returns false when versions are equal', () => { + expect(svc.isOutdated('5.0.0', '5.0.0')).to.equal(false); + expect(svc.isOutdated('5.0.0-beta.1', '5.0.0-beta.1')).to.equal(false); + }); + + it('tolerates a leading v and short versions', () => { + expect(svc.isOutdated('v5.0.0', 'v5.0.1')).to.equal(true); + expect(svc.isOutdated('5.0', '5.0.1')).to.equal(true); + }); +}); From d543981b0e2dc274d664bf378da4154a77a6d2e8 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Wed, 24 Jun 2026 09:21:43 +0100 Subject: [PATCH 02/78] fix: suppress refresh countdown in quiet mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When --quiet is passed (geared at CI), the live results footer no longer renders the "next refresh in Ns" / "refreshing…" countdown. The realtime connection indicator is still shown. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/services/results-polling.service.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/services/results-polling.service.ts b/src/services/results-polling.service.ts index 9146f69..881af60 100644 --- a/src/services/results-polling.service.ts +++ b/src/services/results-polling.service.ts @@ -166,6 +166,7 @@ export class ResultsPollingService { realtimeEnabled, subscription?.isConnected() ?? false, nextPollAt, + quiet, ); ux.action.status = footer ? `${statusBody}\n${footer}` : statusBody; }; @@ -644,12 +645,13 @@ export class ResultsPollingService { * Build the live footer shown under the status display: whether realtime * updates are connected (for logged-in users) and how long until the next * backstop poll. While a fetch is in flight (`nextPollAt` is null) the - * countdown reads "refreshing…". + * countdown reads "refreshing…". In quiet mode the countdown is omitted. */ private buildStatusFooter( realtimeEnabled: boolean, realtimeConnected: boolean, nextPollAt: null | number, + quiet: boolean, ): string { const parts: string[] = []; @@ -661,11 +663,15 @@ export class ResultsPollingService { ); } - if (nextPollAt === null) { - parts.push(colors.dim('refreshing…')); - } else { - const secondsLeft = Math.max(0, Math.ceil((nextPollAt - Date.now()) / 1000)); - parts.push(colors.dim(`next refresh in ${secondsLeft}s`)); + // The countdown to the next backstop poll is noise in quiet mode (geared at + // CI), so suppress it there while keeping the realtime indicator. + if (!quiet) { + if (nextPollAt === null) { + parts.push(colors.dim('refreshing…')); + } else { + const secondsLeft = Math.max(0, Math.ceil((nextPollAt - Date.now()) / 1000)); + parts.push(colors.dim(`next refresh in ${secondsLeft}s`)); + } } return parts.join(colors.dim(' · ')); From ea62f724653b3e1173036c4abe66aa4e110c0a0e Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:14:47 +0100 Subject: [PATCH 03/78] feat(cloud): warn on deprecated iOS 16 (removal 2026-08-23) --- src/commands/cloud.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 3476c0d..a8fa847 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -461,6 +461,21 @@ export const cloudCommand = defineCommand({ logger: (m: string) => out(m), }); + // iOS 16 deprecation notice (soft warning during the grace period; + // removed on 23 August 2026). Only fires on an explicit --ios-version 16 — + // when omitted the API defaults to iOS 17, so no false warning. + const DEPRECATED_IOS_VERSIONS = ['16']; + if (iOSVersion && DEPRECATED_IOS_VERSIONS.includes(iOSVersion)) { + warnOut(ui.warn(colors.bold('iOS 16 is deprecated'))); + warnOut( + ui.branch([ + 'iOS 16 will be removed on 23 August 2026; after that, tests targeting it will fail.', + 'Switch to iOS 17 or newer — iPhone 14 also supports 17 and 18.', + `${colors.dim('See:')} ${colors.url('https://docs.devicecloud.dev/getting-started/devices-configuration')}`, + ]), + ); + } + deviceValidationService.validateAndroidDevice( androidApiLevel, androidDevice, From 62c767295cb99339cbc3326c6bf319caf637d649 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:13:25 +0100 Subject: [PATCH 04/78] feat(cloud): drop legacy Maestro removed-versions block; soft-warn on deprecated 1.39.5/1.41.0 --- src/commands/cloud.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index a8fa847..c7bcc00 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -367,11 +367,17 @@ export const cloudCommand = defineCommand({ }, ); - const REMOVED_MAESTRO_VERSIONS = ['1.39.1', '1.39.2', '1.39.7', '2.0.3', '2.4.0']; - if (REMOVED_MAESTRO_VERSIONS.includes(resolvedMaestroVersion)) { - throw new CliError( - `Maestro version ${resolvedMaestroVersion} is no longer supported. ` + - `Please upgrade to a newer version. See: https://docs.devicecloud.dev/configuration/maestro-versions`, + // Soft deprecation notice for Maestro versions slated for removal on + // 26 June 2026. Non-fatal — these still run during the grace period. + const DEPRECATED_MAESTRO_VERSIONS = ['1.39.5', '1.41.0']; + if (DEPRECATED_MAESTRO_VERSIONS.includes(resolvedMaestroVersion)) { + warnOut(ui.warn(colors.bold(`Maestro ${resolvedMaestroVersion} is deprecated`))); + warnOut( + ui.branch([ + `Maestro ${resolvedMaestroVersion} will be removed on 26 June 2026; after that, tests pinned to it will fail.`, + 'Upgrade to Maestro 2.6.0 or above.', + `${colors.dim('See:')} ${colors.url('https://docs.devicecloud.dev/configuration/maestro-versions')}`, + ]), ); } From ec16bccd044f892f7fd1997aac977c77aa14376d Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Wed, 24 Jun 2026 12:52:40 +0100 Subject: [PATCH 05/78] fix(installer): make beta opt-in, add stable/beta channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The install scripts resolved the version from /latest.json, which (until a stable release exists) synthesized the newest prerelease — so the default `curl … | sh` was silently installing betas. Pair the proxy's new channel support (get.devicecloud.dev now serves stable on /latest.json and prereleases on ?channel=beta) with explicit opt-ins: - DCD_BETA — request the beta channel (latest prerelease). - DCD_VERSION — already pins an exact version; documented for rollback. - Default (no opt-in) installs the latest *stable* only. When no stable release exists yet, the installer errors with guidance pointing at DCD_BETA / DCD_VERSION instead of falling back to a beta. The manifest fetch is separated from parsing so a transient network/proxy failure (curl -f non-zero) is reported differently from a channel that has no release yet (HTTP 200 with "version": null). Co-Authored-By: Claude Opus 4.8 (1M context) --- install.ps1 | 35 +++++++++++++++++++++++++++++++---- install.sh | 45 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/install.ps1 b/install.ps1 index f96cbb4..07a70b7 100644 --- a/install.ps1 +++ b/install.ps1 @@ -4,7 +4,8 @@ # irm https://get.devicecloud.dev/install.ps1 | iex # # Env vars: -# DCD_VERSION Pin a specific version (default: latest) +# DCD_VERSION Pin a specific version, e.g. for rollback (default: latest stable) +# DCD_BETA Set to any value to install the latest beta/prerelease (opt-in) # DCD_INSTALL_DIR Override install location (default: $env:USERPROFILE\.dcd\bin) # DCD_DOWNLOAD_BASE Override the download host (default: https://get.devicecloud.dev) @@ -25,13 +26,39 @@ if ([Environment]::Is64BitOperatingSystem -ne $true) { $asset = 'dcd-windows-x64.exe' # --- resolve version --- +# Precedence: explicit DCD_VERSION pin > DCD_BETA opt-in > latest stable. if ($env:DCD_VERSION) { $version = $env:DCD_VERSION } else { - Write-Host 'Resolving latest version...' - $manifest = Invoke-RestMethod -Uri "$DownloadBase/latest.json" + if ($env:DCD_BETA) { + Write-Host 'Resolving latest beta version...' + $manifestUrl = "$DownloadBase/latest.json?channel=beta" + $channel = 'beta' + } else { + Write-Host 'Resolving latest version...' + $manifestUrl = "$DownloadBase/latest.json" + $channel = 'stable' + } + try { + $manifest = Invoke-RestMethod -Uri $manifestUrl + } catch { + throw "Could not reach $manifestUrl" + } + # A null version means the channel has no release yet (HTTP 200), as opposed + # to a transient failure (which throws above). Stable is the default and beta + # is strictly opt-in, so refuse to silently fall back to a prerelease. $version = $manifest.version - if (-not $version) { throw "Could not resolve latest version from $DownloadBase/latest.json" } + if (-not $version) { + if ($channel -eq 'stable') { + throw @" +No stable dcd release is available yet. + Install the latest beta: `$env:DCD_BETA=1; irm '$DownloadBase/install.ps1' | iex + Or pin a version: `$env:DCD_VERSION='5.0.0-beta.1'; irm '$DownloadBase/install.ps1' | iex +"@ + } else { + throw "No beta release is available yet from $manifestUrl" + } + } } $url = "$DownloadBase/download/$version/$asset" diff --git a/install.sh b/install.sh index ac94e43..4d25dc8 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,8 @@ # curl -fsSL https://get.devicecloud.dev/install.sh | sh # # Env vars: -# DCD_VERSION Pin a specific version (default: latest) +# DCD_VERSION Pin a specific version, e.g. for rollback (default: latest stable) +# DCD_BETA Set to any value to install the latest beta/prerelease (opt-in) # DCD_INSTALL_DIR Override install location (default: $HOME/.dcd/bin) # DCD_DOWNLOAD_BASE Override the download host (default: https://get.devicecloud.dev) # @@ -23,6 +24,17 @@ info() { printf '%s\n' "$1" } +# Stable is the default channel and beta is strictly opt-in, so when no stable +# release exists yet (only prereleases published) we refuse to silently install a +# beta and instead point the user at the two explicit opt-ins. $DOWNLOAD_BASE is +# echoed so a custom host shows the right command. +no_stable_release_err() { + printf 'error: No stable dcd release is available yet.\n' >&2 + printf ' Install the latest beta: curl -fsSL %s/install.sh | DCD_BETA=1 sh\n' "$DOWNLOAD_BASE" >&2 + printf ' Or pin a version: curl -fsSL %s/install.sh | DCD_VERSION=5.0.0-beta.1 sh\n' "$DOWNLOAD_BASE" >&2 + exit 1 +} + # Find a dcd on PATH other than the one we just installed — usually a leftover # `npm install -g @devicecloud.dev/dcd` that can shadow this binary. Runs in a # subshell so the temporary IFS change never leaks back to the caller. @@ -121,17 +133,40 @@ main() { asset="dcd-${os_id}-${arch_id}" # --- resolve version --- + # Precedence: explicit DCD_VERSION pin > DCD_BETA opt-in > latest stable. if [ -n "${DCD_VERSION:-}" ]; then version="$DCD_VERSION" else - info "Resolving latest version..." - # /latest.json returns { "version": "5.1.0", ... } + if [ -n "${DCD_BETA:-}" ]; then + channel=beta + manifest_url="$DOWNLOAD_BASE/latest.json?channel=beta" + info "Resolving latest beta version..." + else + channel=stable + manifest_url="$DOWNLOAD_BASE/latest.json" + info "Resolving latest version..." + fi + + # Fetch the manifest separately from parsing so we can tell a transient + # network/proxy failure (curl -f returns non-zero → empty $manifest) apart + # from a channel that simply has no release yet (HTTP 200 with + # "version": null → $manifest non-empty but $version empty). + manifest=$(curl -fsSL "$manifest_url") || manifest="" + [ -z "$manifest" ] && err "Could not reach $manifest_url" + # /latest.json returns { "version": "5.1.0", ... }; a null version is unquoted + # and so won't match this quoted-string pattern. version=$( - curl -fsSL "$DOWNLOAD_BASE/latest.json" \ + printf '%s' "$manifest" \ | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ | head -n1 ) - [ -z "$version" ] && err "Could not resolve latest version from $DOWNLOAD_BASE/latest.json" + if [ -z "$version" ]; then + if [ "$channel" = stable ]; then + no_stable_release_err + else + err "No beta release is available yet from $manifest_url" + fi + fi fi url="$DOWNLOAD_BASE/download/${version}/${asset}" From 77cf138c80e1d39441565fec4c9d6e83dae5fd6c Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:53:55 +0100 Subject: [PATCH 06/78] chore: add open-source contribution governance Scaffolding to open dcd-cli to external contributors: - LICENSE (MIT), CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CLA templates - CODEOWNERS, PR template, issue forms + config, dependabot, .editorconfig - pr-title-lint workflow: Conventional Commits on PR title (squash-merge model, types kept in sync with release-please changelog-sections) - cla workflow: CLA Assistant Lite - release-please: use a GitHub App token (falls back to GITHUB_TOKEN until the App secrets exist) so Release PRs trigger required checks under branch protection - cli-ci: also run on production so the dev->production promotion PR is gated --- .editorconfig | 15 +++ .github/CODEOWNERS | 16 +++ .github/ISSUE_TEMPLATE/bug_report.yml | 63 +++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 35 +++++ .github/PULL_REQUEST_TEMPLATE.md | 36 +++++ .github/dependabot.yml | 34 +++++ .github/workflows/cla.yml | 49 +++++++ .github/workflows/cli-ci.yml | 6 +- .github/workflows/pr-title-lint.yml | 43 ++++++ .github/workflows/release-please.yml | 27 +++- CLA.md | 140 ++++++++++++++++++++ CODE_OF_CONDUCT.md | 132 ++++++++++++++++++ CONTRIBUTING.md | 147 +++++++++++++++++++++ LICENSE | 21 +++ README.md | 17 +++ SECURITY.md | 55 ++++++++ 17 files changed, 843 insertions(+), 4 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/cla.yml create mode 100644 .github/workflows/pr-title-lint.yml create mode 100644 CLA.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..16cdc57 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig — https://editorconfig.org +# Keep editors aligned with the Prettier config (.prettierrc). +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +# Markdown uses two trailing spaces for hard line breaks — don't strip them. +[*.md] +trim_trailing_whitespace = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..1867eb6 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,16 @@ +# Code owners for dcd-cli. +# Listed owners are requested for review automatically and — when the branch +# ruleset has "Require review from Code Owners" enabled — must approve before a +# PR can merge. +# +# NOTE: replace @devicecloud-dev/cli-maintainers with the real maintainer team +# slug (or individual @handles) before enabling Code Owner review in the ruleset. + +* @devicecloud-dev/cli-maintainers + +# Release pipeline and CI are sensitive — keep them owned by maintainers. +/.github/ @devicecloud-dev/cli-maintainers +/release-please-config.json @devicecloud-dev/cli-maintainers +/release-please-config-beta.json @devicecloud-dev/cli-maintainers +/.release-please-manifest.json @devicecloud-dev/cli-maintainers +/.release-please-manifest-beta.json @devicecloud-dev/cli-maintainers diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..4a865eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,63 @@ +name: Bug report +description: Report a problem with the dcd CLI or dcd-mcp server +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a bug! Please fill in the details below. + + ⚠️ **Do not report security vulnerabilities here** — see our + [Security Policy](https://github.com/devicecloud-dev/dcd-cli/blob/dev/SECURITY.md). + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug, including what you expected to happen instead. + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: The exact `dcd` command(s) you ran and what followed. Redact any API keys. + placeholder: | + 1. Run `dcd cloud --apiKey *** app.apk flows/` + 2. ... + 3. See error + validations: + required: true + - type: input + id: version + attributes: + label: CLI version + description: Output of `dcd --version`. + placeholder: "e.g. 5.0.0" + validations: + required: true + - type: dropdown + id: os + attributes: + label: Operating system + options: + - macOS + - Linux + - Windows + - Other (note in description) + validations: + required: true + - type: input + id: install + attributes: + label: How did you install dcd? + placeholder: "binary (curl/irm), npm global, npx, …" + validations: + required: false + - type: textarea + id: logs + attributes: + label: Logs / output + description: Relevant output. Re-run with more detail if you can. This is automatically formatted as code. + render: shell + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..8769523 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Questions & help + url: https://discord.gg/gm3mJwcNw8 + about: For usage questions and general help, ask in our Discord rather than opening an issue. + - name: Documentation + url: https://docs.devicecloud.dev + about: Check the docs for installation, usage, and command reference. + - name: Report a security vulnerability + url: https://github.com/devicecloud-dev/dcd-cli/blob/dev/SECURITY.md + about: Do not file security issues publicly — email security@devicecloud.dev (see our Security Policy). diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..ece12e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: Feature request +description: Suggest an idea or improvement for the dcd CLI or dcd-mcp server +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: Thanks for the suggestion! Please describe the problem before the solution. + - type: textarea + id: problem + attributes: + label: What problem are you trying to solve? + description: What are you trying to do, and where does the CLI get in the way today? + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + description: What would you like to happen? A concrete command/flag/output sketch helps. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches or workarounds you've thought about. + validations: + required: false + - type: textarea + id: context + attributes: + label: Additional context + description: Anything else — links, screenshots, related issues. + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..288cc94 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,36 @@ + + +## What & why + + + +## Type of change + + + +- [ ] `fix` — bug fix +- [ ] `feat` — new feature +- [ ] `perf` — performance improvement +- [ ] `refactor` — code change that's neither a fix nor a feature +- [ ] `docs` — documentation only +- [ ] `chore` / `ci` / `build` / `test` — tooling, no user-facing change +- [ ] Breaking change (title has `!` or PR notes a `BREAKING CHANGE:`) + +## Checklist + +- [ ] PR title follows the Conventional Commits format (see comment above) +- [ ] `pnpm lint` passes +- [ ] `pnpm typecheck` passes +- [ ] `pnpm build` passes +- [ ] I have **not** bumped the version or edited `CHANGELOG.md` (release-please handles this) +- [ ] I have signed the CLA (the bot will prompt on first contribution) +- [ ] Docs / `README.md` / `STYLE_GUIDE.md` updated if behaviour or output changed + +## How to test + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e792454 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +version: 2 +updates: + # npm / pnpm dependencies. + - package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + target-branch: dev + open-pull-requests-limit: 10 + commit-message: + # Conventional Commit prefix so the squashed PR title matches our PR-title + # lint and release-please picks dependency bumps into the changelog. + prefix: deps + prefix-development: chore + groups: + # Collapse the noise: one PR for all non-major updates. + minor-and-patch: + update-types: + - minor + - patch + + # GitHub Actions used by our workflows. + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + target-branch: dev + commit-message: + prefix: ci + groups: + actions: + update-types: + - minor + - patch diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 0000000..bcab57c --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,49 @@ +name: CLA Assistant + +# Gates merges on a signed Contributor License Agreement. +# +# Uses CLA Assistant Lite (contributor-assistant/github-action): signatures are +# stored as a JSON file committed to a branch of THIS repo (no third-party +# service holds the data). Contributors sign by commenting the configured phrase +# on their PR; the action records it and flips the check green. +# +# SETUP REQUIRED before this can work: +# 1. Create a token with repo write access and add it as the `PERSONAL_ACCESS_TOKEN` +# secret (a fine-grained PAT or the release GitHub App token both work). The +# default GITHUB_TOKEN is also passed, but a PAT is needed to commit the +# signature file back to the repo. +# 2. Create the `cla-signatures` branch (e.g. an empty orphan branch) so the +# action has somewhere to write `signatures/version1/cla.json`. +# 3. Finalise CLA.md (legal review) — it's the document contributors agree to. +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +permissions: + actions: write + contents: write + pull-requests: write + statuses: write + +jobs: + cla: + runs-on: ubuntu-latest + # Only act on the signature comment or on PR events (not every comment). + if: (github.event.issue.pull_request && contains(github.event.comment.body, 'I have read the CLA Document and I hereby sign the CLA')) || github.event_name == 'pull_request_target' + steps: + - uses: contributor-assistant/github-action@v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + with: + path-to-signatures: "signatures/version1/cla.json" + path-to-document: "https://github.com/devicecloud-dev/dcd-cli/blob/dev/CLA.md" + branch: "cla-signatures" + # PR target branches the CLA applies to. + allowlist: dependabot[bot],renovate[bot],*[bot] + # Customise the bot's prompts if desired: + custom-notsigned-prompt: "Thanks for your contribution! Please sign our Contributor License Agreement before we can merge. Comment the line below to sign:" + custom-pr-sign-comment: "I have read the CLA Document and I hereby sign the CLA" + custom-allsigned-prompt: "All contributors have signed the CLA. ✍️ ✅" diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 71d6ae3..d4bb529 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -2,9 +2,11 @@ name: CLI CI on: push: - branches: [ dev ] + branches: [ dev, production ] pull_request: - branches: [ dev ] + # `production` is included so the dev→production promotion PR is also gated + # by lint/typecheck/build (and is required by the production ruleset). + branches: [ dev, production ] workflow_dispatch: permissions: diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml new file mode 100644 index 0000000..c16aa6d --- /dev/null +++ b/.github/workflows/pr-title-lint.yml @@ -0,0 +1,43 @@ +name: PR Title + +# Enforces Conventional Commits on the PR *title*. Because PRs are squash-merged +# with the title as the commit subject, this is what release-please parses to +# compute version bumps and the changelog — so the allowed types below must stay +# in sync with `changelog-sections` in release-please-config.json. +# +# Uses pull_request_target so it also runs (and reports a required status check) +# on PRs from forks. It only reads the title — no untrusted code is checked out. +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + +permissions: + pull-requests: read + +jobs: + validate: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Keep in lockstep with release-please-config.json changelog-sections. + types: | + feat + fix + perf + deps + revert + refactor + docs + chore + test + ci + build + style diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 284b290..f28914a 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -22,15 +22,28 @@ jobs: release-please-prod: if: github.ref_name == 'production' runs-on: ubuntu-latest + # Empty until the release GitHub App secrets are configured. We use an App + # token (not GITHUB_TOKEN) so the Release PR triggers CI / PR-title / CLA + # checks — PRs opened by GITHUB_TOKEN do not, which would deadlock branch + # protection. Falls back to GITHUB_TOKEN (today's behaviour) until the App + # is set up, so this is safe to merge before then. + env: + RELEASE_PLEASE_APP_ID: ${{ secrets.RELEASE_PLEASE_APP_ID }} outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} version: ${{ steps.release.outputs.version }} steps: + - uses: actions/create-github-app-token@v2 + id: app-token + if: env.RELEASE_PLEASE_APP_ID != '' + with: + app-id: ${{ secrets.RELEASE_PLEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }} - uses: googleapis/release-please-action@v4 id: release with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} target-branch: production config-file: release-please-config.json manifest-file: .release-please-manifest.json @@ -38,15 +51,25 @@ jobs: release-please-beta: if: github.ref_name == 'dev' runs-on: ubuntu-latest + # See release-please-prod above for why this uses an App token with a + # GITHUB_TOKEN fallback. + env: + RELEASE_PLEASE_APP_ID: ${{ secrets.RELEASE_PLEASE_APP_ID }} outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} version: ${{ steps.release.outputs.version }} steps: + - uses: actions/create-github-app-token@v2 + id: app-token + if: env.RELEASE_PLEASE_APP_ID != '' + with: + app-id: ${{ secrets.RELEASE_PLEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }} - uses: googleapis/release-please-action@v4 id: release with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} target-branch: dev config-file: release-please-config-beta.json manifest-file: .release-please-manifest-beta.json diff --git a/CLA.md b/CLA.md new file mode 100644 index 0000000..1c8930d --- /dev/null +++ b/CLA.md @@ -0,0 +1,140 @@ +# Contributor License Agreement (CLA) + +> [!IMPORTANT] +> **This is a starting-point template, not final legal text.** It is adapted from +> the Apache Software Foundation Individual and Corporate CLAs. **Have it reviewed +> by legal counsel and replace the bracketed placeholders before relying on it.** +> Once finalised, this document is what the CLA Assistant bot links contributors +> to when they sign on a pull request. + +Thank you for your interest in contributing to software projects managed by +**[Legal Entity Name] ("devicecloud.dev", "we", "us")**. To clarify the +intellectual property licence granted with contributions from any person or +entity, we must have a Contributor License Agreement (CLA) on file that has been +signed by each contributor, indicating agreement to the licence terms below. + +This licence is for your protection as a contributor as well as the protection of +devicecloud.dev and its users; it does not change your rights to use your own +contributions for any other purpose. + +By signing via the CLA Assistant bot on a pull request, you accept and agree to +the applicable terms below for your present and future contributions submitted to +devicecloud.dev. + +--- + +## Individual Contributor License Agreement + +You accept and agree to the following terms and conditions for your present and +future Contributions submitted to devicecloud.dev. Except for the licence granted +herein to devicecloud.dev and recipients of software distributed by +devicecloud.dev, you reserve all right, title, and interest in and to your +Contributions. + +1. **Definitions.** "You" (or "Your") means the copyright owner or legal entity + authorised by the copyright owner that is making this Agreement. "Contribution" + means any original work of authorship, including any modifications or additions + to an existing work, that is intentionally submitted by You to devicecloud.dev + for inclusion in, or documentation of, any of the products owned or managed by + devicecloud.dev (the "Work"). "Submitted" means any form of electronic, verbal, + or written communication sent to devicecloud.dev or its representatives, + including but not limited to communication on electronic mailing lists, source + code control systems, and issue tracking systems that are managed by, or on + behalf of, devicecloud.dev for the purpose of discussing and improving the + Work, but excluding communication that is conspicuously marked or otherwise + designated in writing by You as "Not a Contribution." + +2. **Grant of Copyright Licence.** Subject to the terms and conditions of this + Agreement, You hereby grant to devicecloud.dev and to recipients of software + distributed by devicecloud.dev a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable copyright licence to reproduce, prepare + derivative works of, publicly display, publicly perform, sublicense, and + distribute Your Contributions and such derivative works. + +3. **Grant of Patent Licence.** Subject to the terms and conditions of this + Agreement, You hereby grant to devicecloud.dev and to recipients of software + distributed by devicecloud.dev a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable (except as stated in this section) patent + licence to make, have made, use, offer to sell, sell, import, and otherwise + transfer the Work, where such licence applies only to those patent claims + licensable by You that are necessarily infringed by Your Contribution(s) alone + or by combination of Your Contribution(s) with the Work to which such + Contribution(s) was submitted. If any entity institutes patent litigation + against You or any other entity (including a cross-claim or counterclaim in a + lawsuit) alleging that Your Contribution, or the Work to which You have + contributed, constitutes direct or contributory patent infringement, then any + patent licences granted to that entity under this Agreement for that + Contribution or Work shall terminate as of the date such litigation is filed. + +4. **Representations.** You represent that You are legally entitled to grant the + above licence. If Your employer(s) has rights to intellectual property that You + create that includes Your Contributions, You represent that You have received + permission to make Contributions on behalf of that employer, that Your employer + has waived such rights for Your Contributions to devicecloud.dev, or that Your + employer has executed a separate Corporate CLA with devicecloud.dev. + +5. **Original Work.** You represent that each of Your Contributions is Your + original creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete details of any + third-party licence or other restriction (including, but not limited to, + related patents and trademarks) of which You are personally aware and which are + associated with any part of Your Contributions. + +6. **No Warranty.** You are not expected to provide support for Your + Contributions, except to the extent You desire to provide support. You may + provide support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your Contributions on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions of TITLE, + NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. **Third-Party Works.** Should You wish to submit work that is not Your original + creation, You may submit it to devicecloud.dev separately from any + Contribution, identifying the complete details of its source and of any + licence or other restriction (including, but not limited to, related patents, + trademarks, and licence agreements) of which You are personally aware, and + conspicuously marking the work as "Submitted on behalf of a third-party: + [named here]". + +8. **Notification.** You agree to notify devicecloud.dev of any facts or + circumstances of which You become aware that would make these representations + inaccurate in any respect. + +--- + +## Corporate Contributor License Agreement + +This version is for a corporation (or other legal entity) that wishes to authorise +employees to submit Contributions. It covers the same copyright and patent grants, +representations, and disclaimers as the Individual CLA above, made on behalf of the +entity, plus a schedule of authorised contributors. + +1. The definitions, copyright licence, patent licence, "no warranty", and + third-party works provisions in sections 1–3 and 6–7 of the Individual CLA + above apply equally to this Corporate CLA, with "You" referring to the + **Corporation** identified below. + +2. **Authorisation.** The Corporation represents that each employee designated on + **Schedule A** is authorised to submit Contributions on behalf of the + Corporation. The Corporation agrees to maintain Schedule A and to notify + devicecloud.dev when an individual's authorisation to submit Contributions on + behalf of the Corporation is terminated. + +3. **Representations.** The Corporation represents that each Contribution is an + original creation (per section 5 of the Individual CLA) and that it is legally + entitled to grant the above licences. The Corporation agrees to notify + devicecloud.dev of any facts or circumstances of which it becomes aware that + would make these representations inaccurate. + +**Schedule A — Designated Employees** + +| Full name | GitHub username | Email | +| --- | --- | --- | +| | | | + +**Corporation details** + +- Corporation name: ______________________________ +- Corporation address: ___________________________ +- Authorised signatory (name & title): ___________ +- Signature / date: ______________________________ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..fd6b573 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +**conduct@devicecloud.dev**. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1761173 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,147 @@ +# Contributing to the devicecloud.dev CLI + +Thanks for your interest in improving `@devicecloud.dev/dcd`! This guide covers +everything you need to land a change: local setup, our commit/PR conventions, and +how releases work. + +By participating you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md). + +## Contributor License Agreement (CLA) + +Before your first contribution can be merged, you must sign our Contributor +License Agreement. When you open your first pull request, the **CLA Assistant** +bot will comment with a link and instructions — signing takes under a minute and +is a one-time step. PRs cannot be merged until the CLA check is green. + +- Individuals: sign the [Individual CLA](CLA.md#individual-contributor-license-agreement). +- Contributing on behalf of an employer? Have an authorised signatory complete the + [Corporate CLA](CLA.md#corporate-contributor-license-agreement). + +## Getting started + +You need **Node.js 22+** and **[pnpm](https://pnpm.io)** (`packageManager` pins +the exact version; [Corepack](https://nodejs.org/api/corepack.html) will pick it +up automatically). + +```sh-session +$ git clone https://github.com/devicecloud-dev/dcd-cli.git +$ cd dcd-cli +$ pnpm install # installs deps, builds, and sets up git hooks +$ pnpm dcd # run the CLI from source +``` + +Useful scripts: + +| Command | What it does | +| --- | --- | +| `pnpm lint` | ESLint over `src/` and `test/` | +| `pnpm typecheck` | Strict `tsc --noEmit` over `src/` and `test/` | +| `pnpm build` | Compile to `dist/` | +| `pnpm test` | Build + boot the mock API + run integration/unit tests | + +**Before pushing, make sure `pnpm lint`, `pnpm typecheck`, and `pnpm build` +pass.** These run for every PR (including from forks) and are required to merge. + +### About the test suite + +`pnpm test` boots a **mock API that lives in a private repository**, so the full +integration suite only runs on branches inside this repo. **On pull requests from +forks the integration tests are automatically skipped** — you'll see a CI notice +saying so. That's expected: lint, typecheck, and build still run and gate your +PR, and a maintainer runs the full suite before merge. You don't need backend +access to contribute. + +### Secret scanning + +A [gitleaks](https://github.com/gitleaks/gitleaks) scan runs as a pre-commit hook +and in CI (sharing the allowlist in `.gitleaks.toml`). Installing the binary +locally (`brew install gitleaks`) catches secrets before you commit; without it +the hook skips with a warning and CI remains the backstop. **Never commit real +credentials.** + +## Branching & pull requests + +1. Branch off **`dev`** (the default branch). Name it descriptively, e.g. + `fix/upload-retry` or `feat/json-output`. +2. Open your pull request **against `dev`**. (The `production` branch is the + stable release track and is maintainer-only — don't target it.) +3. Keep PRs focused. Smaller, single-purpose PRs are reviewed and merged faster. +4. Fill in the PR template, including the checklist. +5. PRs are merged via **squash merge**, so your PR ends up as a single commit on + `dev` whose message is your **PR title** — which is why the title must follow + the Conventional Commits format below. + +## Commit & PR title conventions + +We use [Conventional Commits](https://www.conventionalcommits.org). Because we +squash-merge, **only your PR title needs to follow the format** — individual +commit messages on your branch are squashed away, so commit however you like +while developing. A CI check (`PR Title`) validates the title and must pass to +merge. + +Format: + +``` +(): +``` + +Allowed types and how they affect the next release: + +| Type | Use for | Changelog | Version bump | +| --- | --- | --- | --- | +| `feat` | A new feature | **Features** | minor | +| `fix` | A bug fix | **Bug Fixes** | patch | +| `perf` | A performance improvement | **Performance** | patch | +| `deps` | Dependency updates | **Dependencies** | patch | +| `revert` | Reverting a previous change | **Reverts** | patch | +| `refactor` | Code change that neither fixes a bug nor adds a feature | **Code Refactoring** | patch | +| `docs` | Documentation only | hidden | none | +| `chore` | Tooling/maintenance | hidden | none | +| `test` | Adding or fixing tests | hidden | none | +| `ci` | CI configuration | hidden | none | +| `build` | Build system | hidden | none | +| `style` | Formatting, whitespace | hidden | none | + +**Breaking changes:** append `!` after the type (e.g. `feat!: drop Node 20`) or +add a `BREAKING CHANGE:` footer in the PR description. While the CLI is pre-1.0, +`feat` bumps the minor version and breaking changes bump the minor too. + +Examples: + +``` +feat(cloud): add --json output for run results +fix: retry binary upload on transient 5xx +docs: clarify dcd login flow in README +deps: bump @modelcontextprotocol/sdk to 1.x +``` + +## Code style + +- TypeScript, strict mode. Run `pnpm lint` and `pnpm typecheck` before pushing. +- Formatting is handled by Prettier (config in `.prettierrc`); an `.editorconfig` + keeps editors consistent. +- All human-facing CLI output goes through the rendering layer described in + [`STYLE_GUIDE.md`](STYLE_GUIDE.md) — please read it before adding output. Don't + hand-roll layouts or call `console.log` directly. + +## How releases work + +You don't need to do anything for releases — **do not bump the version in +`package.json` or edit `CHANGELOG.md`** in your PR. + +Releases are automated by [release-please](https://github.com/googleapis/release-please): + +- Merges to `dev` accumulate into a **beta** release (published to npm under the + `beta` tag). +- Maintainers promote `dev` → `production` for **stable** releases (npm `latest`). + +release-please reads the Conventional Commit titles of merged PRs to compute the +next version and generate the changelog — which is exactly why the PR title +convention matters. + +## Questions + +- General questions and help: [Discord](https://discord.gg/gm3mJwcNw8). +- Security vulnerabilities: **do not** open an issue — see [SECURITY.md](SECURITY.md). + +Thanks for contributing! 🎉 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e983d13 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 devicecloud.dev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a656931..b1acb0c 100644 --- a/README.md +++ b/README.md @@ -92,3 +92,20 @@ A [gitleaks](https://github.com/gitleaks/gitleaks) scan runs in two places, both - **CI** — the `secret-scan` job scans the full history on every push and pull request, and is the enforced backstop regardless of local setup. +## Contributing + +Contributions are welcome! Read **[CONTRIBUTING.md](CONTRIBUTING.md)** for local +setup, our commit/PR conventions (Conventional Commit PR titles, squash-merge), +and how releases work. All contributors sign our +[Contributor License Agreement](CLA.md) — the bot prompts you on your first PR — +and follow our [Code of Conduct](CODE_OF_CONDUCT.md). + +Found a security issue? Please **don't** open a public issue — see +[SECURITY.md](SECURITY.md). + + +## License + +[MIT](LICENSE) © devicecloud.dev + + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..047affc --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,55 @@ +# Security Policy + +We take the security of the devicecloud.dev CLI (`@devicecloud.dev/dcd`) +seriously. Thank you for helping keep our users safe. + +## Supported Versions + +Security fixes are released against the latest published major version on npm. +Always upgrade to the newest release before reporting: + +```sh-session +$ npm install -g @devicecloud.dev/dcd@latest # npm install +$ dcd upgrade # binary install +``` + +| Version | Supported | +| -------------- | ------------------ | +| Latest `5.x` | :white_check_mark: | +| Older majors | :x: | + +## Reporting a Vulnerability + +**Please do not open a public GitHub issue, pull request, or Discord message for +security vulnerabilities.** Public reports put users at risk before a fix is +available. + +Instead, email **security@devicecloud.dev** with: + +- A description of the vulnerability and its impact. +- Steps to reproduce (a proof of concept is ideal). +- The CLI version (`dcd --version`), OS, and Node.js version where applicable. +- Any suggested remediation, if you have one. + +### What to expect + +- **Acknowledgement** within 3 business days. +- An initial assessment and severity triage within 7 business days. +- Coordinated disclosure: we will work with you on a fix and a disclosure + timeline, and credit you in the release notes if you wish. + +Please give us a reasonable opportunity to remediate before any public +disclosure. + +## Scope + +This policy covers the code in this repository — the `dcd` CLI and the `dcd-mcp` +server. Vulnerabilities in the devicecloud.dev backend or web console should also +be sent to **security@devicecloud.dev** and will be routed to the right team. + +## Secrets + +This repository is scanned for committed secrets by [gitleaks](https://github.com/gitleaks/gitleaks) +on every push and pull request, and via a local pre-commit hook. If you believe +a secret has been committed, email **security@devicecloud.dev** immediately +rather than opening an issue. From 129f802729c79e436bb7dc2fd04b12cf742ed663 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:23:08 +0100 Subject: [PATCH 07/78] chore: drop CODEOWNERS Low value for a small maintainer team where anyone can review anything; the branch ruleset's approval requirement covers review without it. --- .github/CODEOWNERS | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 1867eb6..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,16 +0,0 @@ -# Code owners for dcd-cli. -# Listed owners are requested for review automatically and — when the branch -# ruleset has "Require review from Code Owners" enabled — must approve before a -# PR can merge. -# -# NOTE: replace @devicecloud-dev/cli-maintainers with the real maintainer team -# slug (or individual @handles) before enabling Code Owner review in the ruleset. - -* @devicecloud-dev/cli-maintainers - -# Release pipeline and CI are sensitive — keep them owned by maintainers. -/.github/ @devicecloud-dev/cli-maintainers -/release-please-config.json @devicecloud-dev/cli-maintainers -/release-please-config-beta.json @devicecloud-dev/cli-maintainers -/.release-please-manifest.json @devicecloud-dev/cli-maintainers -/.release-please-manifest-beta.json @devicecloud-dev/cli-maintainers From dc872572846fbe0d9760902cda3380edee1ef2ff Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:34:26 +0100 Subject: [PATCH 08/78] fix(ci): keep dependabot and fork PRs green (#46) * fix(ci): keep dependabot and fork PRs green Dependabot/fork PRs run without repo secrets, so three jobs failed on them: - lint-and-test: HAS_PRIVATE_ACCESS was true for dependabot (same-repo head), so it tried to clone the private mock-api with an empty DCD_SSH_DEPLOY_KEY. Now excludes dependabot[bot], same as forks (skips mock-api + integration). - claude-code-review: skips dependabot/fork PRs (no CLAUDE_CODE_OAUTH_TOKEN). - cla: skips its action step until PERSONAL_ACCESS_TOKEN is configured so the check is green instead of 'Branch cla-signatures not found'; also fixes two invalid input names (custom-*-prompt -> custom-*-prcomment). * ci: group all github-actions bumps into one weekly PR Wildcard pattern so major action bumps join the group too, instead of one PR per action. --- .github/dependabot.yml | 7 ++++--- .github/workflows/cla.yml | 11 +++++++++-- .github/workflows/claude-code-review.yml | 10 ++++------ .github/workflows/cli-ci.yml | 6 +++++- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e792454..e93cbd3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,7 +28,8 @@ updates: commit-message: prefix: ci groups: + # One PR per week for ALL action bumps (including majors). Actions are + # low-risk and quick to eyeball together; no need for a PR each. actions: - update-types: - - minor - - patch + patterns: + - "*" diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index bcab57c..215c76b 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -30,10 +30,17 @@ permissions: jobs: cla: runs-on: ubuntu-latest + # Empty until the PERSONAL_ACCESS_TOKEN secret is configured (see SETUP above). + # While empty, the action step below is skipped so this check passes (green) + # instead of failing on every PR with "Branch cla-signatures not found". It + # auto-activates once the secret + cla-signatures branch exist. + env: + HAS_CLA_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN != '' }} # Only act on the signature comment or on PR events (not every comment). if: (github.event.issue.pull_request && contains(github.event.comment.body, 'I have read the CLA Document and I hereby sign the CLA')) || github.event_name == 'pull_request_target' steps: - uses: contributor-assistant/github-action@v2.6.1 + if: env.HAS_CLA_TOKEN == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} @@ -44,6 +51,6 @@ jobs: # PR target branches the CLA applies to. allowlist: dependabot[bot],renovate[bot],*[bot] # Customise the bot's prompts if desired: - custom-notsigned-prompt: "Thanks for your contribution! Please sign our Contributor License Agreement before we can merge. Comment the line below to sign:" + custom-notsigned-prcomment: "Thanks for your contribution! Please sign our Contributor License Agreement before we can merge. Comment the line below to sign:" custom-pr-sign-comment: "I have read the CLA Document and I hereby sign the CLA" - custom-allsigned-prompt: "All contributors have signed the CLA. ✍️ ✅" + custom-allsigned-prcomment: "All contributors have signed the CLA. ✍️ ✅" diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index c396185..5474be8 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -12,12 +12,10 @@ on: jobs: claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - + # The review needs CLAUDE_CODE_OAUTH_TOKEN, which is NOT exposed to PRs that + # run without secrets — Dependabot PRs and PRs from forks. Skip them so the + # check doesn't fail with an empty token; same-repo PRs only. + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index d4bb529..9febaea 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -40,8 +40,12 @@ jobs: # SSH deploy key. GitHub does NOT expose secrets to pull_request workflows # triggered from forks, so that checkout (and the integration tests that need # it) can only run for same-repo events. Fork PRs still run lint/typecheck/build. + # + # Dependabot PRs branch from this repo (so the fork check passes) but ALSO run + # without secrets — treat them like forks and skip the private checkout, or + # the mock-api clone fails with an empty DCD_SSH_DEPLOY_KEY. env: - HAS_PRIVATE_ACCESS: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + HAS_PRIVATE_ACCESS: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' }} steps: - name: Checkout CLI From 07074d312be16e20d3053cd04314ac9919fc048c Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:17:08 +0100 Subject: [PATCH 09/78] ci: power CLA via the shared automation GitHub App (#49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: power CLA via the shared automation GitHub App Mint the CLA token from the same GitHub App release-please uses, instead of a personal PAT (no expiry, signature commits show as the bot). Rename the App secrets RELEASE_PLEASE_APP_* -> BOT_APP_* since one App now serves both workflows. CLA self-skips until BOT_APP_ID is set. Carries only the app-token delta — the dependabot/fork CI fixes and actions grouping already landed on dev via #46. * ci: allowlist internal maintainers (riglar, finalerock44) in CLA --- .github/workflows/cla.yml | 43 +++++++++++++++++----------- .github/workflows/release-please.yml | 21 +++++++------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 215c76b..395a4f1 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -7,14 +7,20 @@ name: CLA Assistant # service holds the data). Contributors sign by commenting the configured phrase # on their PR; the action records it and flips the check green. # -# SETUP REQUIRED before this can work: -# 1. Create a token with repo write access and add it as the `PERSONAL_ACCESS_TOKEN` -# secret (a fine-grained PAT or the release GitHub App token both work). The -# default GITHUB_TOKEN is also passed, but a PAT is needed to commit the -# signature file back to the repo. -# 2. Create the `cla-signatures` branch (e.g. an empty orphan branch) so the -# action has somewhere to write `signatures/version1/cla.json`. +# AUTH: mints a token from the shared automation GitHub App (the same App +# release-please uses), so signature commits show as the bot and there's no +# personal token to expire. +# +# SETUP REQUIRED before this enforces anything: +# 1. Create/install the automation GitHub App (Contents R/W, Pull requests R/W, +# Issues R/W) and add BOT_APP_ID + BOT_APP_PRIVATE_KEY repo secrets — the +# same secrets release-please uses. +# 2. Create the `cla-signatures` branch (empty orphan) so the action has +# somewhere to write `signatures/version1/cla.json`. # 3. Finalise CLA.md (legal review) — it's the document contributors agree to. +# +# Until the App secrets exist the CLA step self-skips, so the check is green +# (not failing) on every PR and auto-activates once they're set. on: issue_comment: types: [created] @@ -30,26 +36,31 @@ permissions: jobs: cla: runs-on: ubuntu-latest - # Empty until the PERSONAL_ACCESS_TOKEN secret is configured (see SETUP above). - # While empty, the action step below is skipped so this check passes (green) - # instead of failing on every PR with "Branch cla-signatures not found". It - # auto-activates once the secret + cla-signatures branch exist. + # Empty until the automation App secrets are configured (see SETUP above). + # While empty, the steps below self-skip so this check passes (green) instead + # of failing on every PR with "Branch cla-signatures not found". env: - HAS_CLA_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN != '' }} + HAS_APP: ${{ secrets.BOT_APP_ID != '' }} # Only act on the signature comment or on PR events (not every comment). if: (github.event.issue.pull_request && contains(github.event.comment.body, 'I have read the CLA Document and I hereby sign the CLA')) || github.event_name == 'pull_request_target' steps: + - uses: actions/create-github-app-token@v2 + id: app-token + if: env.HAS_APP == 'true' + with: + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} - uses: contributor-assistant/github-action@v2.6.1 - if: env.HAS_CLA_TOKEN == 'true' + if: env.HAS_APP == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }} with: path-to-signatures: "signatures/version1/cla.json" path-to-document: "https://github.com/devicecloud-dev/dcd-cli/blob/dev/CLA.md" branch: "cla-signatures" - # PR target branches the CLA applies to. - allowlist: dependabot[bot],renovate[bot],*[bot] + # Internal maintainers (covered by employment/CCLA) + bots skip the prompt. + allowlist: riglar,finalerock44,dependabot[bot],renovate[bot],*[bot] # Customise the bot's prompts if desired: custom-notsigned-prcomment: "Thanks for your contribution! Please sign our Contributor License Agreement before we can merge. Comment the line below to sign:" custom-pr-sign-comment: "I have read the CLA Document and I hereby sign the CLA" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index f28914a..0745137 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -22,13 +22,14 @@ jobs: release-please-prod: if: github.ref_name == 'production' runs-on: ubuntu-latest - # Empty until the release GitHub App secrets are configured. We use an App - # token (not GITHUB_TOKEN) so the Release PR triggers CI / PR-title / CLA + # Empty until the automation GitHub App secrets are configured (the same App + # powers the CLA workflow). We use an App token (not GITHUB_TOKEN) so the + # Release PR triggers CI / PR-title / CLA # checks — PRs opened by GITHUB_TOKEN do not, which would deadlock branch # protection. Falls back to GITHUB_TOKEN (today's behaviour) until the App # is set up, so this is safe to merge before then. env: - RELEASE_PLEASE_APP_ID: ${{ secrets.RELEASE_PLEASE_APP_ID }} + BOT_APP_ID: ${{ secrets.BOT_APP_ID }} outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} @@ -36,10 +37,10 @@ jobs: steps: - uses: actions/create-github-app-token@v2 id: app-token - if: env.RELEASE_PLEASE_APP_ID != '' + if: env.BOT_APP_ID != '' with: - app-id: ${{ secrets.RELEASE_PLEASE_APP_ID }} - private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} - uses: googleapis/release-please-action@v4 id: release with: @@ -54,7 +55,7 @@ jobs: # See release-please-prod above for why this uses an App token with a # GITHUB_TOKEN fallback. env: - RELEASE_PLEASE_APP_ID: ${{ secrets.RELEASE_PLEASE_APP_ID }} + BOT_APP_ID: ${{ secrets.BOT_APP_ID }} outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} @@ -62,10 +63,10 @@ jobs: steps: - uses: actions/create-github-app-token@v2 id: app-token - if: env.RELEASE_PLEASE_APP_ID != '' + if: env.BOT_APP_ID != '' with: - app-id: ${{ secrets.RELEASE_PLEASE_APP_ID }} - private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }} + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} - uses: googleapis/release-please-action@v4 id: release with: From d3b0accecd5b3283d7cf43cc34611fcf9194227c Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:47:23 +0100 Subject: [PATCH 10/78] docs: set legal entity to Moropo Ltd t/a DeviceCloud (#50) Fill the CLA party placeholder and the LICENSE/README copyright holder with the registered entity. CLA still pending legal review. --- CLA.md | 40 ++++++++++++++++++++-------------------- LICENSE | 2 +- README.md | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CLA.md b/CLA.md index 1c8930d..81ea8e4 100644 --- a/CLA.md +++ b/CLA.md @@ -8,52 +8,52 @@ > to when they sign on a pull request. Thank you for your interest in contributing to software projects managed by -**[Legal Entity Name] ("devicecloud.dev", "we", "us")**. To clarify the +**Moropo Ltd t/a DeviceCloud ("DeviceCloud", "we", "us")**. To clarify the intellectual property licence granted with contributions from any person or entity, we must have a Contributor License Agreement (CLA) on file that has been signed by each contributor, indicating agreement to the licence terms below. This licence is for your protection as a contributor as well as the protection of -devicecloud.dev and its users; it does not change your rights to use your own +DeviceCloud and its users; it does not change your rights to use your own contributions for any other purpose. By signing via the CLA Assistant bot on a pull request, you accept and agree to the applicable terms below for your present and future contributions submitted to -devicecloud.dev. +DeviceCloud. --- ## Individual Contributor License Agreement You accept and agree to the following terms and conditions for your present and -future Contributions submitted to devicecloud.dev. Except for the licence granted -herein to devicecloud.dev and recipients of software distributed by -devicecloud.dev, you reserve all right, title, and interest in and to your +future Contributions submitted to DeviceCloud. Except for the licence granted +herein to DeviceCloud and recipients of software distributed by +DeviceCloud, you reserve all right, title, and interest in and to your Contributions. 1. **Definitions.** "You" (or "Your") means the copyright owner or legal entity authorised by the copyright owner that is making this Agreement. "Contribution" means any original work of authorship, including any modifications or additions - to an existing work, that is intentionally submitted by You to devicecloud.dev + to an existing work, that is intentionally submitted by You to DeviceCloud for inclusion in, or documentation of, any of the products owned or managed by - devicecloud.dev (the "Work"). "Submitted" means any form of electronic, verbal, - or written communication sent to devicecloud.dev or its representatives, + DeviceCloud (the "Work"). "Submitted" means any form of electronic, verbal, + or written communication sent to DeviceCloud or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on - behalf of, devicecloud.dev for the purpose of discussing and improving the + behalf of, DeviceCloud for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." 2. **Grant of Copyright Licence.** Subject to the terms and conditions of this - Agreement, You hereby grant to devicecloud.dev and to recipients of software - distributed by devicecloud.dev a perpetual, worldwide, non-exclusive, + Agreement, You hereby grant to DeviceCloud and to recipients of software + distributed by DeviceCloud a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright licence to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. 3. **Grant of Patent Licence.** Subject to the terms and conditions of this - Agreement, You hereby grant to devicecloud.dev and to recipients of software - distributed by devicecloud.dev a perpetual, worldwide, non-exclusive, + Agreement, You hereby grant to DeviceCloud and to recipients of software + distributed by DeviceCloud a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent licence to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such licence applies only to those patent claims @@ -70,8 +70,8 @@ Contributions. above licence. If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You represent that You have received permission to make Contributions on behalf of that employer, that Your employer - has waived such rights for Your Contributions to devicecloud.dev, or that Your - employer has executed a separate Corporate CLA with devicecloud.dev. + has waived such rights for Your Contributions to DeviceCloud, or that Your + employer has executed a separate Corporate CLA with DeviceCloud. 5. **Original Work.** You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You @@ -89,14 +89,14 @@ Contributions. NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 7. **Third-Party Works.** Should You wish to submit work that is not Your original - creation, You may submit it to devicecloud.dev separately from any + creation, You may submit it to DeviceCloud separately from any Contribution, identifying the complete details of its source and of any licence or other restriction (including, but not limited to, related patents, trademarks, and licence agreements) of which You are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". -8. **Notification.** You agree to notify devicecloud.dev of any facts or +8. **Notification.** You agree to notify DeviceCloud of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect. @@ -117,13 +117,13 @@ entity, plus a schedule of authorised contributors. 2. **Authorisation.** The Corporation represents that each employee designated on **Schedule A** is authorised to submit Contributions on behalf of the Corporation. The Corporation agrees to maintain Schedule A and to notify - devicecloud.dev when an individual's authorisation to submit Contributions on + DeviceCloud when an individual's authorisation to submit Contributions on behalf of the Corporation is terminated. 3. **Representations.** The Corporation represents that each Contribution is an original creation (per section 5 of the Individual CLA) and that it is legally entitled to grant the above licences. The Corporation agrees to notify - devicecloud.dev of any facts or circumstances of which it becomes aware that + DeviceCloud of any facts or circumstances of which it becomes aware that would make these representations inaccurate. **Schedule A — Designated Employees** diff --git a/LICENSE b/LICENSE index e983d13..278d44f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 devicecloud.dev +Copyright (c) 2026 Moropo Ltd t/a DeviceCloud Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b1acb0c..f0da192 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,6 @@ Found a security issue? Please **don't** open a public issue — see ## License -[MIT](LICENSE) © devicecloud.dev +[MIT](LICENSE) © Moropo Ltd t/a DeviceCloud From 3fe1f2191c69261d5c5d48b897888e9e051dc7a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 17:14:13 +0100 Subject: [PATCH 11/78] ci: bump the actions group across 1 directory with 6 updates (#47) Bumps the actions group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/create-github-app-token](https://github.com/actions/create-github-app-token) | `2` | `3` | | [actions/checkout](https://github.com/actions/checkout) | `4` | `7` | | [pnpm/action-setup](https://github.com/pnpm/action-setup) | `4` | `6` | | [actions/setup-node](https://github.com/actions/setup-node) | `5` | `6` | | [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) | `5` | `6` | | [googleapis/release-please-action](https://github.com/googleapis/release-please-action) | `4` | `5` | Updates `actions/create-github-app-token` from 2 to 3 - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Changelog](https://github.com/actions/create-github-app-token/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/create-github-app-token/compare/v2...v3) Updates `actions/checkout` from 4 to 7 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v7) Updates `pnpm/action-setup` from 4 to 6 - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v4...v6) Updates `actions/setup-node` from 5 to 6 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v5...v6) Updates `amannn/action-semantic-pull-request` from 5 to 6 - [Release notes](https://github.com/amannn/action-semantic-pull-request/releases) - [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md) - [Commits](https://github.com/amannn/action-semantic-pull-request/compare/v5...v6) Updates `googleapis/release-please-action` from 4 to 5 - [Release notes](https://github.com/googleapis/release-please-action/releases) - [Changelog](https://github.com/googleapis/release-please-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/release-please-action/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/create-github-app-token dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: amannn/action-semantic-pull-request dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: googleapis/release-please-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: pnpm/action-setup dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: finalerock44 <77282157+finalerock44@users.noreply.github.com> --- .github/workflows/cla.yml | 2 +- .github/workflows/claude-code-review.yml | 2 +- .github/workflows/claude.yml | 2 +- .github/workflows/cli-ci.yml | 10 +++++----- .github/workflows/npm-publish.yml | 6 +++--- .github/workflows/pr-title-lint.yml | 2 +- .github/workflows/release-binaries.yml | 6 +++--- .github/workflows/release-please.yml | 8 ++++---- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 395a4f1..a0306ed 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -44,7 +44,7 @@ jobs: # Only act on the signature comment or on PR events (not every comment). if: (github.event.issue.pull_request && contains(github.event.comment.body, 'I have read the CLA Document and I hereby sign the CLA')) || github.event_name == 'pull_request_target' steps: - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@v3 id: app-token if: env.HAS_APP == 'true' with: diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 5474be8..3419419 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 1 diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 68c8f95..56235d1 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -26,7 +26,7 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 1 diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 9febaea..1cb3bc1 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout (full history) - uses: actions/checkout@v5 + uses: actions/checkout@v7 with: # Full history so gitleaks scans every commit, not just the tip. fetch-depth: 0 @@ -49,13 +49,13 @@ jobs: steps: - name: Checkout CLI - uses: actions/checkout@v5 + uses: actions/checkout@v7 with: path: cli - name: Checkout dcd (mock-api) if: env.HAS_PRIVATE_ACCESS == 'true' - uses: actions/checkout@v5 + uses: actions/checkout@v7 with: repository: moropo-com/dcd path: dcd @@ -68,13 +68,13 @@ jobs: /api/swagger.json - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: version: 10 run_install: false - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: '22' cache: 'pnpm' diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index dbee47d..8ff9b19 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -24,14 +24,14 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 # Setup .npmrc file to publish to npm - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: run_install: false - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version: '22.x' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml index c16aa6d..e3f8cb8 100644 --- a/.github/workflows/pr-title-lint.yml +++ b/.github/workflows/pr-title-lint.yml @@ -23,7 +23,7 @@ jobs: name: Validate PR title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v5 + - uses: amannn/action-semantic-pull-request@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 32467ed..ba1b227 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -24,14 +24,14 @@ jobs: permissions: contents: write # Required to upload release assets steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: run_install: false - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version: '22.x' cache: 'pnpm' diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 0745137..3913334 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -35,13 +35,13 @@ jobs: tag_name: ${{ steps.release.outputs.tag_name }} version: ${{ steps.release.outputs.version }} steps: - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@v3 id: app-token if: env.BOT_APP_ID != '' with: app-id: ${{ secrets.BOT_APP_ID }} private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} - - uses: googleapis/release-please-action@v4 + - uses: googleapis/release-please-action@v5 id: release with: token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} @@ -61,13 +61,13 @@ jobs: tag_name: ${{ steps.release.outputs.tag_name }} version: ${{ steps.release.outputs.version }} steps: - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@v3 id: app-token if: env.BOT_APP_ID != '' with: app-id: ${{ secrets.BOT_APP_ID }} private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} - - uses: googleapis/release-please-action@v4 + - uses: googleapis/release-please-action@v5 id: release with: token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} From d780e55093b314fe9855890db145188e2735beb3 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:00:13 +0100 Subject: [PATCH 12/78] =?UTF-8?q?fix:=20v5=20release=20blockers=20?= =?UTF-8?q?=E2=80=94=20installer,=20binary=20version,=20repeated=20flags,?= =?UTF-8?q?=E2=80=A6=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: v5 release blockers — installer, binary version, repeated flags, upgrade, CI output - install.ps1: fix PS 5.1 parse error (`$asset:` -> `${asset}`) that made `irm | iex` a no-op on stock Windows; decode the octet-stream SHA256SUMS (Byte[] under -UseBasicParsing) to text before splitting. - build/version: stamp the version into the bun-compiled binary via `bun --define __DCD_CLI_VERSION__` (the compiled binary can't read package.json), so `dcd --version` no longer reports 0.0.0. npm/tsx path still falls back to reading package.json. Adds src/global.d.ts. - cloud: collect repeated `-e/--env`, `-m/--metadata`, `--include-tags`, `--exclude-tags`, `--exclude-flows` from rawArgs (citty/parseArgs kept only the last occurrence, silently dropping earlier values); echo the collected values too. - upgrade: query the beta channel for prerelease installs and distinguish "no newer release on this channel" from a real network failure, replacing the misleading "Could not reach the update manifest" error during the beta. - progress/polling: make the realtime status indicator TTY-aware — in non-interactive/CI output, print one line per state change instead of flooding logs with a per-frame spinner (not suppressed by --quiet/--json-file). - methods: downgrade primary-Backblaze-upload failure warnings to debug-only; the Supabase fallback recovers and validateUploadResults raises the only user-facing error (when every strategy fails). - list/status: build console links from the env the CLI targets (resolveFrontendUrl) instead of the API's hardcoded-prod consoleUrl. - cloud: validate a local --app-file exists during --dry-run. --- install.ps1 | 12 +++++- scripts/build-binaries.mjs | 21 +++++++++- src/commands/cloud.ts | 50 +++++++++++++++++++----- src/commands/list.ts | 11 ++++-- src/commands/status.ts | 11 +++++- src/commands/upgrade.ts | 21 ++++++++-- src/global.d.ts | 10 +++++ src/methods.ts | 20 ++++------ src/services/results-polling.service.ts | 16 +++++++- src/services/version.service.ts | 49 ++++++++++++++++++++---- src/utils/cli.ts | 51 +++++++++++++++++++++++-- src/utils/progress.ts | 48 +++++++++++++++++++++-- 12 files changed, 271 insertions(+), 49 deletions(-) create mode 100644 src/global.d.ts diff --git a/install.ps1 b/install.ps1 index 07a70b7..dab6b1f 100644 --- a/install.ps1 +++ b/install.ps1 @@ -75,7 +75,15 @@ try { Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing # --- verify checksum --- - $sums = (Invoke-WebRequest -Uri $sumsUrl -UseBasicParsing).Content + # GitHub serves SHA256SUMS as application/octet-stream, so under + # -UseBasicParsing on Windows PowerShell 5.x .Content comes back as a + # Byte[] (not a string) and -split would never match. Decode to UTF-8 text. + $sumsResp = Invoke-WebRequest -Uri $sumsUrl -UseBasicParsing + $sums = if ($sumsResp.Content -is [byte[]]) { + [System.Text.Encoding]::UTF8.GetString($sumsResp.Content) + } else { + [string]$sumsResp.Content + } $expected = ($sums -split "`n" | Where-Object { $_ -match "^([a-f0-9]{64})\s+$([regex]::Escape($asset))\s*$" } | ForEach-Object { $matches[1] } | @@ -83,7 +91,7 @@ try { if (-not $expected) { throw "SHA256SUMS has no entry for $asset" } $actual = (Get-FileHash -Path $tmp -Algorithm SHA256).Hash.ToLower() if ($expected -ne $actual) { - throw "Checksum mismatch for $asset: expected $expected, got $actual" + throw "Checksum mismatch for ${asset}: expected $expected, got $actual" } # --- install --- diff --git a/scripts/build-binaries.mjs b/scripts/build-binaries.mjs index 008a52f..f9dae78 100644 --- a/scripts/build-binaries.mjs +++ b/scripts/build-binaries.mjs @@ -23,6 +23,13 @@ import { fileURLToPath } from 'node:url'; const repoRoot = dirname(dirname(fileURLToPath(import.meta.url))); const outDir = join(repoRoot, 'dist-bin'); +// The compiled binary can't read package.json off disk (it isn't bundled), so +// stamp the version in at compile time via `bun --define`. getCliVersion() +// prefers this constant and falls back to reading package.json on the npm path. +const { version } = JSON.parse( + readFileSync(join(repoRoot, 'package.json'), 'utf8'), +); + // Each entry maps a Bun cross-compile target to the GitHub Release asset name. // outName excludes `.exe` because Bun appends it automatically for windows targets. const targets = [ @@ -41,7 +48,19 @@ for (const { target, outName, asset } of targets) { console.log(`→ ${target}`); execFileSync( 'bun', - ['build', '--compile', `--target=${target}`, 'src/index.ts', '--outfile', out], + [ + 'build', + '--compile', + `--target=${target}`, + // bun wants the space-separated `--define KEY=value` form (the colon form + // `--define:KEY=value` silently no-ops). JSON.stringify supplies the + // surrounding quotes bun expects for a string-literal replacement. + '--define', + `__DCD_CLI_VERSION__=${JSON.stringify(version)}`, + 'src/index.ts', + '--outfile', + out, + ], { cwd: repoRoot, stdio: 'inherit' }, ); const produced = join(outDir, asset); diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index c7bcc00..e8358cf 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -1,5 +1,6 @@ /* eslint-disable complexity */ import { defineCommand } from 'citty'; +import { existsSync } from 'node:fs'; import * as path from 'node:path'; import { flags as allFlags } from '../constants.js'; @@ -36,6 +37,7 @@ import { isCI } from '../utils/ci.js'; import { CliError, coerceArray, + collectRepeatedFlag, getCliVersion, getUpgradeCommand, logger, @@ -109,7 +111,7 @@ export const cloudCommand = defineCommand({ }, }, // eslint-disable-next-line complexity - async run({ args }) { + async run({ args, rawArgs }) { const cliVersion = getCliVersion(); const deviceValidationService = new DeviceValidationService(); const moropoService = new MoropoService(); @@ -119,12 +121,16 @@ export const cloudCommand = defineCommand({ const versionService = new VersionService(); const versionCheck = async () => { - const latestVersion = await versionService.checkLatestCliVersion(); - if (latestVersion && versionService.isOutdated(cliVersion, latestVersion)) { + const result = await versionService.checkLatestCliVersion(cliVersion); + if ( + result.ok && + result.version && + versionService.isOutdated(cliVersion, result.version) + ) { out(ui.warn(colors.bold('Update available'))); out( ui.branch([ - `A new version of the DeviceCloud CLI is available: ${colors.highlight(latestVersion)}`, + `A new version of the DeviceCloud CLI is available: ${colors.highlight(result.version)}`, `${colors.dim('Run:')} ${colors.info(getUpgradeCommand())}`, ]), ); @@ -165,18 +171,23 @@ export const cloudCommand = defineCommand({ 'download-artifacts', ); const dryRun = Boolean(args['dry-run']); - const env = coerceArray(args.env as string | string[] | undefined, false); + // Repeatable flags are collected from rawArgs: citty/parseArgs only keeps + // the last occurrence, so reading args.* directly drops earlier values. + const env = coerceArray( + collectRepeatedFlag(rawArgs, ['--env', '-e']), + false, + ); const excludeFlows = coerceArray( - args['exclude-flows'] as string | string[] | undefined, + collectRepeatedFlag(rawArgs, ['--exclude-flows']), ); const excludeTags = coerceArray( - args['exclude-tags'] as string | string[] | undefined, + collectRepeatedFlag(rawArgs, ['--exclude-tags']), ); let flows = args.flows as string | undefined; const googlePlay = Boolean(args['google-play']); const ignoreShaCheck = Boolean(args['ignore-sha-check']); const includeTags = coerceArray( - args['include-tags'] as string | string[] | undefined, + collectRepeatedFlag(rawArgs, ['--include-tags']), ); const iOSDevice = validateEnum( args['ios-device'] as string | undefined, @@ -204,7 +215,7 @@ export const cloudCommand = defineCommand({ const jsonFileName = args['json-file-name'] as string | undefined; const maestroVersion = args['maestro-version'] as string | undefined; const metadata = coerceArray( - args.metadata as string | string[] | undefined, + collectRepeatedFlag(rawArgs, ['--metadata', '-m']), false, ); const mitmHost = args.mitmHost as string | undefined; @@ -437,6 +448,14 @@ export const cloudCommand = defineCommand({ out(`[DEBUG] Found .app bundle at: ${finalAppFile}`); } } + + // Validate the resolved local app file early — dry-run otherwise skips + // the upload that would surface a missing file, so a typo'd path would + // pass a dry-run and only fail on the real run. (URL/.tar.gz inputs are + // already resolved to existing temp paths by this point.) + if (!existsSync(finalAppFile)) { + throw new CliError(`App file does not exist: ${finalAppFile}`); + } } if (debug) { @@ -619,14 +638,27 @@ export const cloudCommand = defineCommand({ ]); // Only log canonical flag keys (skip citty-populated alias duplicates like apiURL/apiUrl). const canonicalFlagKeys = new Set(Object.keys(allFlags)); + // Repeatable flags are recovered from rawArgs (args.* only holds the last + // occurrence), so echo the fully-collected values rather than args.*. + const repeatableDisplay: Record = { + env, + metadata, + 'include-tags': includeTags, + 'exclude-tags': excludeTags, + 'exclude-flows': excludeFlows, + }; for (const [k, v] of Object.entries(args)) { if (!canonicalFlagKeys.has(k)) continue; + if (k in repeatableDisplay) continue; if (v === undefined || v === null || v === false) continue; const asString = String(v); if (asString.length > 0 && !sensitiveFlags.has(k)) { flagLogs.push(`${k}: ${asString}`); } } + for (const [k, values] of Object.entries(repeatableDisplay)) { + if (values.length > 0) flagLogs.push(`${k}: ${values.join(', ')}`); + } const overridesEntries = Object.entries(flowOverrides); const hasOverrides = overridesEntries.some( diff --git a/src/commands/list.ts b/src/commands/list.ts index 669acc0..c057b38 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -1,6 +1,7 @@ import { defineCommand } from 'citty'; import { apiFlags } from '../config/flags/api.flags.js'; +import { resolveFrontendUrl } from '../config/environments.js'; import { ApiGateway } from '../gateways/api-gateway.js'; import { resolveAuth } from '../utils/auth.js'; import { CliError, logger, parseIntFlag } from '../utils/cli.js'; @@ -41,8 +42,12 @@ function detectShellExpansion(name: string): void { } } -function displayResults(response: ListResponse): void { +function displayResults(response: ListResponse, apiUrl: string): void { const { uploads, total, limit, offset } = response; + // Build console links from the env the CLI is pointed at, rather than the + // API-supplied consoleUrl (which is hardcoded to prod) — so dev/staging users + // get links that actually resolve. + const frontendUrl = resolveFrontendUrl(apiUrl); if (uploads.length === 0) { logger.log(ui.info('No uploads found matching your criteria.')); @@ -70,7 +75,7 @@ function displayResults(response: ListResponse): void { ...ui.fields([ ['id', formatId(upload.id)], ['created', formattedDate], - ['console', formatUrl(upload.consoleUrl)], + ['console', formatUrl(`${frontendUrl}/results?upload=${upload.id}`)], ]), ]), ); @@ -169,7 +174,7 @@ export const listCommand = defineCommand({ return; } - displayResults(response); + displayResults(response, apiUrl); } catch (error) { throw new CliError( `Failed to list uploads: ${(error as Error).message}`, diff --git a/src/commands/status.ts b/src/commands/status.ts index 6deb425..9724897 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -1,6 +1,7 @@ import { defineCommand } from 'citty'; import { apiFlags } from '../config/flags/api.flags.js'; +import { resolveFrontendUrl } from '../config/environments.js'; import { ApiGateway } from '../gateways/api-gateway.js'; import { formatDurationSeconds } from '../methods.js'; import { resolveAuth } from '../utils/auth.js'; @@ -248,8 +249,14 @@ async function statusMain({ if (status.createdAt) { fields.push(['created', formatDateTime(status.createdAt)]); } - if (status.consoleUrl) { - fields.push(['console', formatUrl(status.consoleUrl)]); + // Prefer a console link built from the env the CLI is pointed at (the + // API-supplied consoleUrl is hardcoded to prod, so it misdirects + // dev/staging users); fall back to the API value if we have no uploadId. + const consoleUrl = status.uploadId + ? `${resolveFrontendUrl(apiUrl)}/results?upload=${status.uploadId}` + : status.consoleUrl; + if (consoleUrl) { + fields.push(['console', formatUrl(consoleUrl)]); } logger.log(ui.section('Upload Status')); diff --git a/src/commands/upgrade.ts b/src/commands/upgrade.ts index 0aa2232..719ab60 100644 --- a/src/commands/upgrade.ts +++ b/src/commands/upgrade.ts @@ -57,14 +57,24 @@ export const upgradeCommand = defineCommand({ const current = getCliVersion(); const versionService = new VersionService(); - const latest = await versionService.checkLatestCliVersion(); + const result = await versionService.checkLatestCliVersion(current); - if (!latest) { + if (!result.ok) { throw new CliError( - 'Could not reach the update manifest. Check your network connection and try again.', + `Could not reach the update manifest (${result.error}). Check your network connection and try again.`, ); } + // Reachable, but nothing published on this channel yet (e.g. a stable + // install while only betas exist). Not an error — just nothing to do. + if (result.version === null) { + logger.log( + ui.info(`No newer release available on the ${result.channel} channel.`), + ); + return; + } + + const latest = result.version; if (!versionService.isOutdated(current, latest)) { logger.log( ui.success(`Already on the latest version (${colors.highlight(current)})`), @@ -85,8 +95,11 @@ export const upgradeCommand = defineCommand({ if (process.platform === 'win32') { // Windows can't replace a running .exe; defer to a re-run of the installer. const base = process.env.DCD_DOWNLOAD_BASE ?? DEFAULT_DOWNLOAD_BASE; + // Prerelease users need the beta channel opt-in or the installer resolves + // the (currently non-existent) stable release. + const betaHint = result.channel === 'beta' ? '$env:DCD_BETA=1; ' : ''; throw new CliError( - `Automatic upgrade on Windows is not yet supported. Re-run the installer:\n irm ${base}/install.ps1 | iex`, + `Automatic upgrade on Windows is not yet supported. Re-run the installer:\n ${betaHint}irm ${base}/install.ps1 | iex`, ); } diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..bfa4354 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,10 @@ +/** + * Build-time constants injected by `bun --define` (see + * scripts/build-binaries.mjs). Declared as an ambient global — this file must + * stay free of top-level import/export or it stops being a global declaration. + * + * `__DCD_CLI_VERSION__` holds the package version stamped into the compiled + * standalone binary. On the npm/tsx path it is never defined; `getCliVersion()` + * guards every read with `typeof`. + */ +declare const __DCD_CLI_VERSION__: string | undefined; diff --git a/src/methods.ts b/src/methods.ts index 42ab30d..ebc7a0f 100644 --- a/src/methods.ts +++ b/src/methods.ts @@ -924,8 +924,9 @@ async function uploadToBackblaze( console.error(`[DEBUG] Backblaze upload failed with status ${response.status}: ${errorText}`); } - // Don't throw - we don't want Backblaze failures to block the primary upload - console.warn(`Warning: Backblaze upload failed with status ${response.status}`); + // Don't throw and don't warn — Backblaze is the primary attempt and the + // Supabase fallback usually recovers. A user-facing error is raised only + // if every strategy fails (see validateUploadResults). return false; } @@ -950,13 +951,10 @@ async function uploadToBackblaze( if (debug) { console.error('[DEBUG] Network error detected - could be DNS, connection, or SSL issue'); } - - console.warn('Warning: Backblaze upload failed due to network error'); - } else { - // Don't throw - we don't want Backblaze failures to block the primary upload - console.warn(`Warning: Backblaze upload failed: ${error instanceof Error ? error.message : String(error)}`); } + // Don't throw and don't warn — the Supabase fallback usually recovers, and + // validateUploadResults raises a user-facing error only if all fail. return false; } } @@ -1088,11 +1086,9 @@ function logBackblazeUploadError(error: unknown, debug: boolean): void { } } - if (error instanceof Error && error.message.includes('network error')) { - console.warn('Warning: Backblaze large file upload failed due to network error'); - } else { - console.warn(`Warning: Backblaze large file upload failed: ${error instanceof Error ? error.message : String(error)}`); - } + // No user-facing warning: Backblaze is the primary attempt and the Supabase + // fallback usually recovers; validateUploadResults raises the only + // user-facing error, and only when every strategy fails. } /** diff --git a/src/services/results-polling.service.ts b/src/services/results-polling.service.ts index 881af60..c1cf800 100644 --- a/src/services/results-polling.service.ts +++ b/src/services/results-polling.service.ts @@ -9,6 +9,7 @@ import { formatDurationSeconds } from '../methods.js'; import type { AuthContext } from '../types/domain/auth.types.js'; import { paths } from '../types/generated/schema.types.js'; import { checkInternetConnectivity } from '../utils/connectivity.js'; +import { isCI } from '../utils/ci.js'; import { ux } from '../utils/progress.js'; import { colors, formatTestSummary, statusPalette, table } from '../utils/styling.js'; import { type Field, ui } from '../utils/ui.js'; @@ -160,8 +161,16 @@ export class ResultsPollingService { let realtimeEnabled = false; let statusBody = ''; let nextPollAt: null | number = null; + // The animated footer/countdown only makes sense on a TTY. In CI/pipes it + // would flood logs (a fresh line per frame), so we drop it and let the + // progress adapter print one line per distinct status change instead. + const interactive = !json && !isCI(); const renderStatus = () => { if (json) return; + if (!interactive) { + ux.action.status = statusBody; + return; + } const footer = this.buildStatusFooter( realtimeEnabled, subscription?.isConnected() ?? false, @@ -201,8 +210,11 @@ export class ResultsPollingService { // Tick the live footer once a second so the countdown actually counts down // (the spinner's own frames don't recompute our message). Unref'd so it - // never keeps the process alive on its own. - const ticker: NodeJS.Timeout | null = json ? null : setInterval(renderStatus, 1000); + // never keeps the process alive on its own. Only on an interactive TTY — + // a 1s ticker in CI would reprint the status every second. + const ticker: NodeJS.Timeout | null = interactive + ? setInterval(renderStatus, 1000) + : null; ticker?.unref?.(); try { diff --git a/src/services/version.service.ts b/src/services/version.service.ts index 953a4b9..1ed8239 100644 --- a/src/services/version.service.ts +++ b/src/services/version.service.ts @@ -3,6 +3,18 @@ import { CompatibilityData } from '../utils/compatibility.js'; const DEFAULT_MANIFEST_URL = 'https://get.devicecloud.dev/latest.json'; const MANIFEST_TIMEOUT_MS = 3000; +export type ReleaseChannel = 'beta' | 'stable'; + +/** + * Outcome of a release-manifest lookup. `ok: true` means the manifest was + * reachable — `version` is the published version on the channel, or `null` when + * nothing is published there yet. `ok: false` means the lookup itself failed + * (network/timeout/non-2xx). + */ +export type LatestVersionResult = + | { ok: true; channel: ReleaseChannel; version: null | string } + | { ok: false; error: string }; + /** * Compare two semantic versions per SemVer 2.0.0 precedence rules. * Returns a negative number if `a < b`, positive if `a > b`, and 0 if equal. @@ -62,19 +74,42 @@ export class VersionService { /** * Fetch the latest published CLI version from the release manifest. * Works for both npm- and binary-installed users (no `npm` shell-out). - * Silently returns null on any failure — this check is informational only. + * + * The result is discriminated so callers can tell "reachable, but no release + * on this channel yet" (`ok: true, version: null`) apart from an actual + * network/manifest failure (`ok: false`) — the old single-`null` return + * conflated the two and produced a misleading "check your network" error + * during the beta. Prerelease installs (current version contains `-`) query + * the opt-in beta channel; everyone else gets the stable channel. */ - async checkLatestCliVersion(): Promise { - const url = process.env.DCD_MANIFEST_URL ?? DEFAULT_MANIFEST_URL; + async checkLatestCliVersion( + currentVersion?: string, + ): Promise { + const channel: ReleaseChannel = + currentVersion?.includes('-') ? 'beta' : 'stable'; + const base = process.env.DCD_MANIFEST_URL ?? DEFAULT_MANIFEST_URL; + const url = + channel === 'beta' + ? `${base}${base.includes('?') ? '&' : '?'}channel=beta` + : base; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), MANIFEST_TIMEOUT_MS); try { const res = await fetch(url, { signal: controller.signal }); - if (!res.ok) return null; + if (!res.ok) { + return { ok: false, error: `manifest responded with HTTP ${res.status}` }; + } const data = (await res.json()) as { version?: unknown }; - return typeof data.version === 'string' ? data.version : null; - } catch { - return null; + return { + ok: true, + channel, + version: typeof data.version === 'string' ? data.version : null, + }; + } catch (error) { + return { + ok: false, + error: error instanceof Error ? error.message : String(error), + }; } finally { clearTimeout(timer); } diff --git a/src/utils/cli.ts b/src/utils/cli.ts index 1a320f8..6b152cd 100644 --- a/src/utils/cli.ts +++ b/src/utils/cli.ts @@ -12,9 +12,19 @@ import { telemetry } from '../services/telemetry.service.js'; import { symbols } from './styling.js'; -// Resolve version at runtime — read the file rather than importing it, so -// package.json never gets pulled into the tsc program / dist rootDir. +// Resolve version at runtime. The bun-compiled binary can't read package.json +// off disk (it isn't bundled next to the embedded module), so the build stamps +// the version in via `bun --define __DCD_CLI_VERSION__` (see +// scripts/build-binaries.mjs). Prefer that constant; on the npm/tsx path the +// identifier was never defined, so `typeof` is 'undefined' (no ReferenceError) +// and we fall back to reading package.json. export function getCliVersion(): string { + if ( + typeof __DCD_CLI_VERSION__ === 'string' && + __DCD_CLI_VERSION__.length > 0 + ) { + return __DCD_CLI_VERSION__; + } try { const pkg = JSON.parse( readFileSync(new URL('../../package.json', import.meta.url), 'utf8'), @@ -114,8 +124,7 @@ export function validateEnum( /** * Coerce a flag value (possibly a single string, array, or undefined) into a * flat string array. Comma-separated values inside each entry are split out. - * Used for repeatable flags like --include-tags, --env, --metadata where citty - * surfaces a string (single use) or string[] (repeated). + * Pair with {@link collectRepeatedFlag} for repeatable flags. */ export function coerceArray( value: string | string[] | undefined, @@ -127,6 +136,40 @@ export function coerceArray( return arr.flatMap((v) => v.split(',')); } +/** + * Collect every occurrence of a repeatable flag from raw argv, in order. + * + * citty 0.2.2 delegates to Node's `parseArgs`, which — without `multiple: true` + * (unsupported by citty's ArgsDef) — keeps only the LAST value of a repeated + * `type: 'string'` flag. So `-e A=1 -e B=2` collapses to just `B=2`. We recover + * all occurrences by scanning rawArgs ourselves (same approach as + * `recoverFlagValue` in commands/live.ts). + * + * `names` lists every spelling of one logical flag, e.g. ['--env', '-e']. + * Handles both `--flag value` (consuming the next token, so values starting + * with `-` survive) and `--flag=value`. Feed the result through + * {@link coerceArray} for comma-splitting where appropriate. + */ +export function collectRepeatedFlag( + rawArgs: string[], + names: string[], +): string[] { + const out: string[] = []; + for (let i = 0; i < rawArgs.length; i++) { + const arg = rawArgs[i]; + const eqName = names.find((n) => arg.startsWith(`${n}=`)); + if (eqName) { + out.push(arg.slice(eqName.length + 1)); + continue; + } + if (names.includes(arg) && i + 1 < rawArgs.length) { + out.push(rawArgs[i + 1]); + i++; // consume the value so a leading-dash value isn't re-read as a flag + } + } + return out; +} + /** * Parse an integer flag. Returns undefined if the value is undefined/empty. * Throws CliError if the value is not a valid integer. diff --git a/src/utils/progress.ts b/src/utils/progress.ts index c4d33ed..fcf8d45 100644 --- a/src/utils/progress.ts +++ b/src/utils/progress.ts @@ -3,25 +3,53 @@ * drop-in API for existing services that used oclif's `ux.action` / `ux.info`. * * Keeps call sites unchanged while migrating away from @oclif/core. + * + * TTY-awareness: @clack/prompts' spinner animates on a timer and, when stdout + * isn't a TTY (CI, pipes, redirects), it can't rewrite a line in place — every + * frame becomes a fresh line, flooding logs with hundreds of duplicates. In + * non-interactive environments we skip the spinner entirely and instead print a + * plain line once per *distinct* status, so CI logs show real progress without + * the flood. */ import * as p from '@clack/prompts'; +import { isCI } from './ci.js'; + type ClackSpinner = ReturnType; class Action { private current: ClackSpinner | null = null; private _status = ''; + // Last line emitted in non-interactive mode, for de-duplication. + private _lastPrinted = ''; + + private interactive(): boolean { + return process.stdout.isTTY === true && !isCI(); + } start(title: string, initialStatus?: string, _opts?: unknown): void { + this._status = initialStatus ?? ''; + const line = initialStatus ? `${title} — ${initialStatus}` : title; + if (!this.interactive()) { + this.current = null; + this.print(line); + return; + } if (this.current) { this.current.stop(); } this.current = p.spinner(); - this._status = initialStatus ?? ''; - this.current.start(initialStatus ? `${title} — ${initialStatus}` : title); + this.current.start(line); } stop(message?: string): void { + if (!this.interactive()) { + if (message) this.print(message); + this.current = null; + this._status = ''; + this._lastPrinted = ''; + return; + } if (!this.current) { if (message) { // eslint-disable-next-line no-console @@ -36,7 +64,12 @@ class Action { set status(value: string) { this._status = value; - if (this.current && value) { + if (!value) return; + if (!this.interactive()) { + this.print(value); + return; + } + if (this.current) { this.current.message(value); } } @@ -44,6 +77,15 @@ class Action { get status(): string { return this._status; } + + // Emit a line only when it differs from the previous one, so repeated polls + // with no state change stay quiet. + private print(line: string): void { + if (line === this._lastPrinted) return; + this._lastPrinted = line; + // eslint-disable-next-line no-console + console.log(line); + } } export const ux = { From 7fd253a73cd5bcf2d32e1b0459b732d957c877fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:18:09 +0100 Subject: [PATCH 13/78] chore(dev): release 5.0.0-beta.2 (#36) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ package.json | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index 86cb1d6..26587a1 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.0.0-beta.1" + ".": "5.0.0-beta.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 25de72f..de0dc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [5.0.0-beta.2](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2026-06-24) + + +### Features + +* **cloud:** drop legacy Maestro removed-versions block; soft-warn on deprecated 1.39.5/1.41.0 ([62c7672](https://github.com/devicecloud-dev/dcd-cli/commit/62c767295cb99339cbc3326c6bf319caf637d649)) +* **cloud:** Maestro deprecation — drop legacy hard-block, soft-warn 1.39.5/1.41.0 ([ee995e7](https://github.com/devicecloud-dev/dcd-cli/commit/ee995e7ebefb7ddfd3678bea27adf4751a39e879)) +* **cloud:** warn on deprecated iOS 16 (removal 2026-08-23) ([d794695](https://github.com/devicecloud-dev/dcd-cli/commit/d794695529c9dce938d16199e336d6698e21bff9)) +* **cloud:** warn on deprecated iOS 16 (removal 2026-08-23) ([ea62f72](https://github.com/devicecloud-dev/dcd-cli/commit/ea62f724653b3e1173036c4abe66aa4e110c0a0e)) + + +### Bug Fixes + +* **ci:** keep dependabot and fork PRs green ([#46](https://github.com/devicecloud-dev/dcd-cli/issues/46)) ([dc87257](https://github.com/devicecloud-dev/dcd-cli/commit/dc872572846fbe0d9760902cda3380edee1ef2ff)) +* **installer:** make beta opt-in, add stable/beta channels ([ec16bcc](https://github.com/devicecloud-dev/dcd-cli/commit/ec16bccd044f892f7fd1997aac977c77aa14376d)) +* **installer:** make beta opt-in, default to stable channel ([88c3532](https://github.com/devicecloud-dev/dcd-cli/commit/88c3532f8a3c6de7210c2d66d819838ea4c99fad)) +* suppress refresh countdown in quiet mode ([10eade4](https://github.com/devicecloud-dev/dcd-cli/commit/10eade42c80f42df05b165e3f83e1190aeabfd80)) +* suppress refresh countdown in quiet mode ([d543981](https://github.com/devicecloud-dev/dcd-cli/commit/d543981b0e2dc274d664bf378da4154a77a6d2e8)) +* **upgrade:** compare prerelease versions per SemVer ([f77841f](https://github.com/devicecloud-dev/dcd-cli/commit/f77841fae397e3b8d88b7ba6876b98f65026f089)) +* **upgrade:** compare prerelease versions per SemVer ([6c533e7](https://github.com/devicecloud-dev/dcd-cli/commit/6c533e7ae9e55b64e5ffe63a9c9a6934e9250938)) +* v5 release blockers — installer, binary version, repeated flags, upgrade, CI output ([d780e55](https://github.com/devicecloud-dev/dcd-cli/commit/d780e55093b314fe9855890db145188e2735beb3)) +* v5 release blockers — installer, binary version, repeated flags,… ([#51](https://github.com/devicecloud-dev/dcd-cli/issues/51)) ([d780e55](https://github.com/devicecloud-dev/dcd-cli/commit/d780e55093b314fe9855890db145188e2735beb3)) + ## [5.0.0-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.0...v5.0.0-beta.1) (2026-06-23) diff --git a/package.json b/package.json index 32464d8..a5ee017 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.0.0-beta.1", + "version": "5.0.0-beta.2", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From bd6029847b9eccfe9078ae21b40ad548e4ef8985 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:35:04 +0100 Subject: [PATCH 14/78] fix: stop CLA locking release PRs (breaks release pipeline) (#52) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CLA Assistant action defaults lock-pullrequest-aftermerge=true, so merging a release-please PR locked it; release-please then failed trying to comment on the locked PR, killing the Release job before npm publish + binary upload ran (seen on v5.0.0-beta.2). Set lock-pullrequest-aftermerge=false. Also skip release-please PRs in claude-code-review (version bumps — nothing to review, and it must never block a release). --- .github/workflows/cla.yml | 5 +++++ .github/workflows/claude-code-review.yml | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index a0306ed..ba697bc 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -59,6 +59,11 @@ jobs: path-to-signatures: "signatures/version1/cla.json" path-to-document: "https://github.com/devicecloud-dev/dcd-cli/blob/dev/CLA.md" branch: "cla-signatures" + # Do NOT lock the PR on merge (the action's default is true). release-please + # comments on its release PR *after* merge; a locked conversation makes that + # comment fail and takes down the whole Release job (npm publish + binaries + # never run). Keeping this false is load-bearing for the release pipeline. + lock-pullrequest-aftermerge: false # Internal maintainers (covered by employment/CCLA) + bots skip the prompt. allowlist: riglar,finalerock44,dependabot[bot],renovate[bot],*[bot] # Customise the bot's prompts if desired: diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 3419419..1eac5d0 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -12,10 +12,11 @@ on: jobs: claude-review: - # The review needs CLAUDE_CODE_OAUTH_TOKEN, which is NOT exposed to PRs that - # run without secrets — Dependabot PRs and PRs from forks. Skip them so the - # check doesn't fail with an empty token; same-repo PRs only. - if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} + # Skip PRs we shouldn't (or can't) review: + # - Dependabot / forks: no CLAUDE_CODE_OAUTH_TOKEN, so the action would fail. + # - release-please release PRs: just version bumps + changelog — nothing to + # review, and it must never block a release. + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' && !startsWith(github.head_ref, 'release-please--') }} runs-on: ubuntu-latest permissions: contents: read From a02584fe574fd687539d0be424c658c743aaae8a Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:31:42 +0100 Subject: [PATCH 15/78] feat(live): add a beta warning to `dcd live start` (#54) Prints a beta notice (billed at $0.03/min, contact support to enroll) before starting a session. The API's new enrollment gate returns a 403 whose "contact support" message the CLI already surfaces verbatim on a non-enrolled org. --- src/commands/live.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/commands/live.ts b/src/commands/live.ts index 2e2e017..cf925a5 100644 --- a/src/commands/live.ts +++ b/src/commands/live.ts @@ -171,6 +171,14 @@ const startSub = defineCommand({ throw new CliError('--android-device and --android-api-level must be provided together.'); } + logger.log(ui.warn(colors.bold('Live is in beta'))); + logger.log( + ui.branch([ + 'Live device sessions are a beta feature, billed at $0.03/min.', + `${colors.dim('Not enrolled?')} Contact support to request access.`, + ]), + ); + logger.log(ui.running(`Starting ${platform} live session…`)); const session = await ApiGateway.startLiveSession(apiUrl, auth, { From 3eedde30551dfc74a39d9860ed41bbe98395b1c8 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:40:06 +0100 Subject: [PATCH 16/78] chore(dev): release 5.0.0-beta.3 (#53) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index 26587a1..a690fc9 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.0.0-beta.2" + ".": "5.0.0-beta.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index de0dc2d..f4548ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [5.0.0-beta.3](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.2...v5.0.0-beta.3) (2026-06-25) + + +### Features + +* **live:** add a beta warning to `dcd live start` ([#54](https://github.com/devicecloud-dev/dcd-cli/issues/54)) ([a02584f](https://github.com/devicecloud-dev/dcd-cli/commit/a02584fe574fd687539d0be424c658c743aaae8a)) + + +### Bug Fixes + +* stop CLA locking release PRs (breaks release pipeline) ([#52](https://github.com/devicecloud-dev/dcd-cli/issues/52)) ([bd60298](https://github.com/devicecloud-dev/dcd-cli/commit/bd6029847b9eccfe9078ae21b40ad548e4ef8985)) + ## [5.0.0-beta.2](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2026-06-24) diff --git a/package.json b/package.json index a5ee017..7dd3ebe 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From b72b83ac8a098f7795956d286b10f4a8bb51b6ec Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:49:42 +0100 Subject: [PATCH 17/78] chore: remove dead Maestro 1.39.5/1.41.0 deprecation warning (#57) Both versions are gone from the API gate, so resolveMaestroVersion rejects them before the soft-warn block runs; drop the now-unreachable notice and bump the integration test to a supported version. Also refresh CLAUDE.md. --- CLAUDE.md | 37 +++++++++++++++++++--- src/commands/cloud.ts | 14 -------- test/integration/cloud.integration.test.ts | 2 +- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 426a23a..ab99a23 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,11 +5,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Commands - `pnpm dcd ` — run the CLI from source via `tsx`. -- `pnpm build` — clean, compile TypeScript to `dist/`, and `chmod +x dist/index.js` so the published binary is directly executable. +- `pnpm build` — clean, compile TypeScript to `dist/`, and `chmod +x dist/index.js dist/mcp/index.js` so both published binaries are directly executable. +- `pnpm build:binaries` — `scripts/build-binaries.mjs` produces the bun-compiled, self-contained `dcd--` binaries published to GitHub Releases (the install `dcd upgrade` self-updates). The platform/arch keys must stay in sync with `ASSET_BY_PLATFORM` in `src/commands/upgrade.ts`. - `pnpm lint` — ESLint over `src/` and `test/`. -- `pnpm typecheck` — `tsc --noEmit` over `src/` and `test/` (strict mode; `pnpm build` only compiles `src/`). -- `pnpm test` — runs `scripts/test-runner.mjs`: builds the CLI, boots a mock API, then runs all `test/**/*.test.ts` via mocha + ts-node. The mock API lives in the **sibling `dcd/` repo** (`../dcd/mock-api`). Override its location with `MOCK_API_DIR=/path/to/mock-api`. -- Run a single test: `pnpm mocha test/integration/cloud.integration.test.ts --timeout 60000` (picks up `.mocharc.json` which wires tsx; requires the mock API already running on its expected port). +- `pnpm typecheck` — `tsc --noEmit -p tsconfig.test.json` over `src/` and `test/` (strict mode; `pnpm build` only compiles `src/`). Requires Node `>=22`. +- `pnpm test` — runs `scripts/test-runner.mjs`: builds the CLI, boots the mock API, then runs all `test/**/*.test.ts` via mocha. TypeScript is loaded by **tsx** (`.mocharc.json`'s `node-option: ["import=tsx"]`), *not* ts-node — Mocha 11 imports specs as ESM, which bypasses the `require: ts-node/register` hook. The mock API lives in the **sibling `dcd/` repo** (`../dcd/mock-api`, started via `npm run start:auth` on port 3001). Override its location with `MOCK_API_DIR=/path/to/mock-api`. The runner isolates `DCD_CONFIG_DIR` to a temp dir so tests never touch your real `dcd login` session. +- Tests split into `test/unit/*` (pure, no backend) and `test/integration/*` (drive the built CLI against the mock API). Run a single test: `pnpm mocha test/integration/cloud.integration.test.ts --timeout 60000` (picks up `.mocharc.json` which wires tsx; integration specs require the mock API already running on port 3001). ## Entry point @@ -19,7 +20,7 @@ The package ships a **second bin, `dcd-mcp`** (`bin.dcd-mcp` → `dist/mcp/index ## Architecture -Top-level `defineCommand` in `src/index.ts` wires ten subcommands (`cloud`, `upload`, `list`, `status`, `artifacts`, `live`, plus the auth-related `login`, `logout`, `whoami`, `switch-org`). `cloud` is the primary command and replicates `maestro cloud`. +Top-level `defineCommand` in `src/index.ts` wires eleven subcommands (`cloud`, `upload`, `list`, `status`, `artifacts`, `live`, `upgrade`, plus the auth-related `login`, `logout`, `whoami`, `switch-org`). `cloud` is the primary command and replicates `maestro cloud`; `upgrade` self-updates the standalone bun binary (no-op for npm installs, which it redirects to `npm install -g`). Note `src/index.ts` deliberately **reimplements** citty's `runMain` rather than calling it — see the Telemetry section for why. **Flag composition.** Flag definitions are split by domain in `src/config/flags/*.flags.ts` (api, binary, device, environment, execution, github, output) and re-exported as a single `flags` object from `src/constants.ts`. Commands that need the full cloud surface spread `...flags` into their citty `args`; subset commands import individual flag groups. @@ -45,3 +46,29 @@ Top-level `defineCommand` in `src/index.ts` wires ten subcommands (`cloud`, `upl **Telemetry.** `src/services/telemetry.service.ts` ships lifecycle (`command started` / `command completed` / `command failed`) and error events to the dcd API's `/cli/logs` proxy → Axiom `cli-dev` / `cli-prod`. Wired in at three points: `src/index.ts` replicates citty's `runMain` (which would otherwise swallow errors and exit 1) to record start/success/failure and honor `CliError.exitCode`; `src/utils/auth.ts` calls `telemetry.configure({ auth })` from `resolveAuth` so the token never has to be re-derived; `src/utils/cli.ts` `logger.error` calls `telemetry.flushSync()` (which shells out to `curl` because `process.exit` bypasses `beforeExit`) before exiting. Unauthenticated invocations (`--help`, `--version`, `dcd login` pre-success) buffer in memory and drop on exit — by design, since there's no identity to attach. Opt out per-invocation with `DCD_TELEMETRY_DISABLED=1`. **MCP server.** `src/mcp/` is a third front-end onto the same service layer (a sibling to `src/commands/`), shipped as the `dcd-mcp` bin over stdio transport (`@modelcontextprotocol/sdk`, `zod` schemas). `index.ts` boots the server; `server.ts` registers tools; `context.ts` resolves auth + API URL **lazily and once** (so `tools/list` works unauthenticated and auth errors surface as tool errors, not a boot crash) via the same `resolveAuth`/`resolveApiUrl` as the CLI — `DEVICE_CLOUD_API_KEY` env or stored `dcd login` session, with `DCD_API_URL` to override. **Critical invariant: stdout is the JSON-RPC channel** — tools must never call the `src/commands/*` layer or `utils/cli` `logger` (both write to stdout / can `process.exit`); they call services/gateways directly with `logStderr` and return data via `helpers.ts` `jsonResult`/`errorResult`. The `runTool` wrapper records `mcp tool …` telemetry and converts thrown errors to `isError` results. Tools: `dcd_list_devices`, `dcd_list_runs`, `dcd_get_status`, `dcd_download_artifacts` (read-only), and `dcd_run_cloud_test` (billable — gated out by `--read-only` / `DCD_MCP_READONLY=1`, annotated destructive, async-by-default). `dcd_run_cloud_test` reuses `computeCommonRoot`/`buildTestMetadataMap` from `src/services/flow-paths.ts` (extracted from `cloud.ts` so both build identical server-side paths). Registry manifest: `server.json` at repo root. + +## Contributing + +Full guide in `CONTRIBUTING.md`; the operationally important parts (the ones that gate a merge or affect a release): + +- **Branch off `dev`** (the default branch) and open PRs **against `dev`**. `production` is the maintainer-only stable track — never target it directly. +- PRs are **squash-merged**, so the **PR title becomes the commit** and must be a [Conventional Commit](https://www.conventionalcommits.org). The title — not the branch commits — is what release-please reads to compute the next version, so it matters even though individual commits are squashed away. A `PR Title` CI check enforces it. +- Type → bump (pre-1.0, so `feat` and breaking `!` both bump **minor**): `feat` minor; `fix`/`perf`/`deps`/`revert`/`refactor` patch; `docs`/`chore`/`test`/`ci`/`build`/`style` are hidden and bump nothing. Allowed scopes are free-form. +- **Never hand-edit `package.json` version, `CHANGELOG.md`, or the `.release-please-manifest*.json` files** — release-please owns all of them. `src/types/generated/schema.types.ts` is likewise generated (openapi-typescript). +- A first-time contributor must sign the CLA (the CLA Assistant bot comments on the first PR); the CLA check must be green to merge. +- **CI (`.github/workflows/cli-ci.yml`) runs on every PR** including forks: gitleaks secret scan, `pnpm lint`, `pnpm typecheck`, `pnpm build`, `pnpm audit --audit-level moderate`. The **integration tests need the private `moropo-com/dcd` mock-api** (cloned via the `DCD_SSH_DEPLOY_KEY` secret), and GitHub withholds secrets from fork and Dependabot PRs — so `pnpm test` is **skipped there** and a maintainer runs the full suite before merge. gitleaks also runs as a pre-commit hook (allowlist in `.gitleaks.toml`); without the binary installed the hook self-skips and CI is the backstop. + +## Releases + +Fully automated by [release-please](https://github.com/googleapis/release-please) — no manual version bumping. `.github/workflows/release-please.yml` drives **two parallel tracks off two separate config+manifest pairs**: + +| Push to | Track | Config / manifest | Version | npm tag | +| --- | --- | --- | --- | --- | +| `dev` | **beta** (prerelease) | `release-please-config-beta.json` / `.release-please-manifest-beta.json` | `X.Y.Z-beta.N` | `beta` | +| `production` | **stable** | `release-please-config.json` / `.release-please-manifest.json` | `X.Y.Z` | `latest` | + +The two manifests track their versions **independently** (e.g. beta `5.0.0-beta.3` while stable is `5.0.0`). On each qualifying push release-please opens/updates a **Release PR** on that branch; merging the Release PR creates the git tag + GitHub Release, and the same workflow run **chains** (as `needs:` jobs, because a `GITHUB_TOKEN`-created release won't fire `release: published`) into: +1. `npm-publish.yml` — publishes to npm. Guards: a prod publish may only run from `production` and its version must **not** carry `-beta`; a beta version **must** carry `-beta`. +2. `release-binaries.yml` — bun-compiles the standalone binaries (`node scripts/build-binaries.mjs`) and uploads them to the GitHub Release. `get.devicecloud.dev` serves them by reading the GitHub Releases API at runtime, so there's no separate manifest to deploy. + +**Promoting beta → stable** is a maintainer merging `dev` into `production` via a PR (also gated by `cli-ci.yml`) — that push to `production` is what triggers the stable Release PR. The current `release/promote-v5` branch is exactly such a promotion. Releases prefer an automation GitHub App token (`BOT_APP_ID`) so the Release PR triggers the CI / PR-title / CLA checks that branch protection requires, falling back to `GITHUB_TOKEN` until the App secrets are configured. diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index e8358cf..4b8f3d8 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -378,20 +378,6 @@ export const cloudCommand = defineCommand({ }, ); - // Soft deprecation notice for Maestro versions slated for removal on - // 26 June 2026. Non-fatal — these still run during the grace period. - const DEPRECATED_MAESTRO_VERSIONS = ['1.39.5', '1.41.0']; - if (DEPRECATED_MAESTRO_VERSIONS.includes(resolvedMaestroVersion)) { - warnOut(ui.warn(colors.bold(`Maestro ${resolvedMaestroVersion} is deprecated`))); - warnOut( - ui.branch([ - `Maestro ${resolvedMaestroVersion} will be removed on 26 June 2026; after that, tests pinned to it will fail.`, - 'Upgrade to Maestro 2.6.0 or above.', - `${colors.dim('See:')} ${colors.url('https://docs.devicecloud.dev/configuration/maestro-versions')}`, - ]), - ); - } - if (retry !== undefined && retry > 2) { out( ui.warn( diff --git a/test/integration/cloud.integration.test.ts b/test/integration/cloud.integration.test.ts index e87f06f..253057f 100644 --- a/test/integration/cloud.integration.test.ts +++ b/test/integration/cloud.integration.test.ts @@ -208,7 +208,7 @@ appId: com.example.app }; it('should support custom Maestro versions', async () => { - const command = `${CLI} cloud ${androidAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --maestro-version 1.39.5 --name test-maestro-version --async`; + const command = `${CLI} cloud ${androidAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --maestro-version 2.2.0 --name test-maestro-version --async`; const { stdout } = await exec(command, { timeout: 15_000 }); expectAsyncSubmission(stdout); From 10dfdbf9a5d0ebf12568e90a1fad623213175368 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:16:36 +0100 Subject: [PATCH 18/78] feat: render DB-driven notices and forward CLI/CI identity (#58) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: render DB-driven notices and forward CLI/CI identity The CLI now renders deprecation/warn/info/marketing notices the API returns with the compatibility data, honouring --json. Removes the hardcoded iOS-16 warning (now a seeded notice gated on the selected iOS version). - notices.service: Notice type, match-DSL evaluator, level-aware renderer - ci.ts: detectCiContext() resolves provider + wrapper version (DCD_CI_*) - compatibility.ts: carries notices; forwards x-dcd-cli-version + x-dcd-ci-* headers - version.service: export compareSemver for reuse * test(upload): expect success when --ignore-sha-check bypasses dedup The dcd swagger fix (getBinaryUploadUrl now returns a valid uploads/ staging path) makes the TUS fallback upload succeed against dev storage, so this test no longer fails — invert it to assert the command succeeds and returns a binary id. --- src/commands/cloud.ts | 39 +++--- src/services/notices.service.ts | 137 ++++++++++++++++++++ src/services/version.service.ts | 2 +- src/utils/ci.ts | 36 +++++ src/utils/compatibility.ts | 29 ++++- test/integration/upload.integration.test.ts | 18 +-- 6 files changed, 231 insertions(+), 30 deletions(-) create mode 100644 src/services/notices.service.ts diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 4b8f3d8..5f8a75c 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -33,7 +33,7 @@ import { EiOSVersions, } from '../types/domain/device.types.js'; import { resolveAuth } from '../utils/auth.js'; -import { isCI } from '../utils/ci.js'; +import { detectCiContext, isCI } from '../utils/ci.js'; import { CliError, coerceArray, @@ -48,6 +48,7 @@ import { CompatibilityData, fetchCompatibilityData, } from '../utils/compatibility.js'; +import { renderNotices } from '../services/notices.service.js'; import { resolveApiUrl } from '../utils/config-store.js'; import { downloadExpoUrl, extractTarGz, findAppBundle, isUrl } from '../utils/expo.js'; import { @@ -346,9 +347,14 @@ export const cloudCommand = defineCommand({ ); } + const ciContext = detectCiContext(); let compatibilityData: CompatibilityData; try { - compatibilityData = await fetchCompatibilityData(apiUrl, auth); + compatibilityData = await fetchCompatibilityData(apiUrl, auth, { + cliVersion, + ciProvider: ciContext.provider, + ciWrapperVersion: ciContext.wrapperVersion, + }); if (debug) { out('[DEBUG] Successfully fetched compatibility data from API'); } @@ -472,20 +478,21 @@ export const cloudCommand = defineCommand({ logger: (m: string) => out(m), }); - // iOS 16 deprecation notice (soft warning during the grace period; - // removed on 23 August 2026). Only fires on an explicit --ios-version 16 — - // when omitted the API defaults to iOS 17, so no false warning. - const DEPRECATED_IOS_VERSIONS = ['16']; - if (iOSVersion && DEPRECATED_IOS_VERSIONS.includes(iOSVersion)) { - warnOut(ui.warn(colors.bold('iOS 16 is deprecated'))); - warnOut( - ui.branch([ - 'iOS 16 will be removed on 23 August 2026; after that, tests targeting it will fail.', - 'Switch to iOS 17 or newer — iPhone 14 also supports 17 and 18.', - `${colors.dim('See:')} ${colors.url('https://docs.devicecloud.dev/getting-started/devices-configuration')}`, - ]), - ); - } + // Render DB-driven notices (deprecation/warn/info/marketing) the API + // returned with the compatibility data. Replaces the previously hardcoded + // iOS-16 deprecation warning — that is now a seeded notice gated on the + // selected iOS version below. Honours --json via out/warnOut. + renderNotices( + compatibilityData.notices, + { + ios_version: iOSVersion, + android_api_level: androidApiLevel, + cli_version: cliVersion, + ci_provider: ciContext.provider, + ci_wrapper_version: ciContext.wrapperVersion, + }, + { out, warnOut }, + ); deviceValidationService.validateAndroidDevice( androidApiLevel, diff --git a/src/services/notices.service.ts b/src/services/notices.service.ts new file mode 100644 index 0000000..ea3c5f5 --- /dev/null +++ b/src/services/notices.service.ts @@ -0,0 +1,137 @@ +import { ui } from '../utils/ui.js'; +import { colors } from '../utils/styling.js'; +import { compareSemver } from './version.service.js'; + +export type NoticeLevel = 'deprecation' | 'warn' | 'info' | 'marketing'; + +export interface NoticeMatchRule { + field: string; + op: 'present' | 'absent' | 'equals' | 'in' | 'not_in' | 'lt' | 'gt'; + value?: unknown; +} + +export interface NoticeMatch { + rules: NoticeMatchRule[]; +} + +/** + * The client-facing notice shape returned by the API (embedded in the + * compatibility response and from GET /notices). `match` is an optional display + * gate the CLI evaluates locally against its own context (e.g. the selected iOS + * version) — the API can't know those at fetch time. + */ +export interface Notice { + id: string; + slug: string | null; + level: NoticeLevel; + title: string; + body: string; + learnMoreUrl: string | null; + dismissible: boolean; + match: NoticeMatch | null; +} + +/** Flat key/value context the `match` DSL is evaluated against. */ +export type NoticeContext = Record; + +function isPresent(raw: unknown): boolean { + return raw !== null && raw !== undefined && raw !== false && raw !== ''; +} + +function ruleMatches(rule: NoticeMatchRule, ctx: NoticeContext): boolean { + const raw = ctx[rule.field]; + switch (rule.op) { + case 'present': + return isPresent(raw); + case 'absent': + return !isPresent(raw); + case 'equals': + return isPresent(raw) && String(raw) === String(rule.value); + case 'in': + return ( + isPresent(raw) && + Array.isArray(rule.value) && + rule.value.map(String).includes(String(raw)) + ); + case 'not_in': + return ( + isPresent(raw) && + Array.isArray(rule.value) && + !rule.value.map(String).includes(String(raw)) + ); + case 'lt': + return ( + isPresent(raw) && + rule.value != null && + compareSemver(String(raw), String(rule.value)) < 0 + ); + case 'gt': + return ( + isPresent(raw) && + rule.value != null && + compareSemver(String(raw), String(rule.value)) > 0 + ); + default: + return false; + } +} + +/** A null / empty match imposes no constraint; otherwise every rule must match (AND). */ +export function matchesRules( + match: NoticeMatch | null | undefined, + ctx: NoticeContext, +): boolean { + if (!match || !Array.isArray(match.rules) || match.rules.length === 0) { + return true; + } + return match.rules.every((rule) => ruleMatches(rule, ctx)); +} + +export interface RenderNoticesOptions { + out: (message: string) => void; + warnOut: (message: string) => void; +} + +/** Render a single notice with styling appropriate to its level. */ +function renderNotice(notice: Notice, opts: RenderNoticesOptions): void { + const rows = [notice.body]; + if (notice.learnMoreUrl) { + rows.push(`${colors.dim('See:')} ${colors.url(notice.learnMoreUrl)}`); + } + + switch (notice.level) { + case 'deprecation': + case 'warn': + opts.warnOut(ui.warn(colors.bold(notice.title))); + opts.warnOut(ui.branch(rows)); + break; + case 'marketing': + opts.out(ui.section(notice.title)); + opts.out(ui.branch(rows)); + break; + case 'info': + default: + opts.out(ui.info(colors.bold(notice.title))); + opts.out(ui.branch(rows)); + break; + } +} + +/** + * Filter notices by their local-context `match` gate and render those that pass. + * Returns the visible notices so a `--json` caller can include them in its + * payload instead of printing. When `--json` is active, callers pass no-op + * out/warnOut so nothing is printed but the list is still returned. + */ +export function renderNotices( + notices: Notice[] | undefined, + ctx: NoticeContext, + opts: RenderNoticesOptions, +): Notice[] { + if (!notices || notices.length === 0) return []; + const visible = notices.filter((n) => matchesRules(n.match, ctx)); + for (const notice of visible) { + renderNotice(notice, opts); + } + return visible; +} diff --git a/src/services/version.service.ts b/src/services/version.service.ts index 1ed8239..2340377 100644 --- a/src/services/version.service.ts +++ b/src/services/version.service.ts @@ -27,7 +27,7 @@ export type LatestVersionResult = * lexically (ASCII), and numeric always sorts below alphanumeric. A longer * set of identifiers wins when all preceding ones are equal. */ -function compareSemver(a: string, b: string): number { +export function compareSemver(a: string, b: string): number { const split = (v: string): { release: number[]; pre: string[] } => { const [core, ...preParts] = v.trim().replace(/^v/, '').split('-'); const nums = core.split('.').map((n) => Number(n) || 0); diff --git a/src/utils/ci.ts b/src/utils/ci.ts index 196b2dc..c5a8153 100644 --- a/src/utils/ci.ts +++ b/src/utils/ci.ts @@ -40,3 +40,39 @@ export function isCI(): boolean { return !process.stdout.isTTY; } + +/** Which DCD CI integration (if any) is wrapping this CLI invocation, and its version. */ +export interface CiContext { + /** e.g. 'github' | 'bitrise' | 'bitbucket' | 'eas' | 'gitlab' | 'circleci'. */ + provider: string | null; + /** The wrapper's own version, if it forwarded one (DCD_CI_WRAPPER_VERSION). */ + wrapperVersion: string | null; +} + +/** Infer the CI provider from the env vars each platform sets natively. */ +function inferProvider(): string | null { + const env = process.env; + if (env.GITHUB_ACTIONS) return 'github'; + if (env.BITRISE_IO || env.BITRISE_BUILD_NUMBER) return 'bitrise'; + if (env.BITBUCKET_BUILD_NUMBER) return 'bitbucket'; + if (env.EAS_BUILD || env.EAS_BUILD_RUNNER || env.EAS_BUILD_ID) return 'eas'; + if (env.GITLAB_CI) return 'gitlab'; + if (env.CIRCLECI) return 'circleci'; + return null; +} + +/** + * Resolve the CI integration context the CLI forwards to the notices API so + * notices can target a specific integration/version (e.g. "Bitbucket Pipe < + * 1.1.0"). The DCD CI wrappers set `DCD_CI_PROVIDER` / `DCD_CI_WRAPPER_VERSION` + * explicitly (preferred — carries the wrapper version); otherwise the provider + * is inferred from native env vars and the version is unknown. + */ +export function detectCiContext(): CiContext { + const forwardedProvider = process.env.DCD_CI_PROVIDER?.trim(); + const forwardedVersion = process.env.DCD_CI_WRAPPER_VERSION?.trim(); + return { + provider: forwardedProvider || inferProvider(), + wrapperVersion: forwardedVersion || null, + }; +} diff --git a/src/utils/compatibility.ts b/src/utils/compatibility.ts index a48324a..af1934d 100644 --- a/src/utils/compatibility.ts +++ b/src/utils/compatibility.ts @@ -1,4 +1,5 @@ import type { AuthContext } from '../types/domain/auth.types.js'; +import type { Notice } from '../services/notices.service.js'; export interface CompatibilityData { android: Record; @@ -9,20 +10,46 @@ export interface CompatibilityData { latestVersion: string; supportedVersions: string[]; }; + /** Active CLI notices, piggybacked onto the compatibility response by the API. */ + notices?: Notice[]; +} + +/** Identity the CLI forwards so the API can target notices by version / CI. */ +export interface ClientContext { + cliVersion?: string; + ciProvider?: string | null; + ciWrapperVersion?: string | null; } let cachedCompatibilityData: CompatibilityData | null = null; -export async function fetchCompatibilityData(apiUrl: string, auth: AuthContext): Promise { +export async function fetchCompatibilityData( + apiUrl: string, + auth: AuthContext, + clientContext?: ClientContext, +): Promise { if (cachedCompatibilityData) { return cachedCompatibilityData; } + // Forward CLI / CI identity so the API can version-filter and target notices. + const noticeHeaders: Record = {}; + if (clientContext?.cliVersion) { + noticeHeaders['x-dcd-cli-version'] = clientContext.cliVersion; + } + if (clientContext?.ciProvider) { + noticeHeaders['x-dcd-ci-provider'] = clientContext.ciProvider; + } + if (clientContext?.ciWrapperVersion) { + noticeHeaders['x-dcd-ci-wrapper-version'] = clientContext.ciWrapperVersion; + } + try { const response = await fetch(`${apiUrl}/results/compatibility/data`, { headers: { 'Content-Type': 'application/json', ...auth.headers, + ...noticeHeaders, }, method: 'GET', }); diff --git a/test/integration/upload.integration.test.ts b/test/integration/upload.integration.test.ts index ceadc8a..b41dc8e 100644 --- a/test/integration/upload.integration.test.ts +++ b/test/integration/upload.integration.test.ts @@ -188,20 +188,14 @@ describe('Upload Command Integration Tests', () => { expect(stdout).to.include('skipping upload'); }); - it('should attempt a real upload when the SHA check is bypassed', async () => { + it('should perform a real upload when the SHA check is bypassed', async () => { const command = `${CLI} upload ${androidAppFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ignore-sha-check --json`; - // Bypassing dedup makes the CLI upload to the storage URLs from the - // mock's example response, which point at real (unwritable) hosts — - // so this deterministically fails after attempting every upload path. - // It still verifies --ignore-sha-check skips the dedup short-circuit. - const { code, stdout } = await runExpectingFailure(command, { - timeout: 60_000, - }); - expect(code).to.equal(1); - const result = JSON.parse(stdout); - expect(result).to.have.property('status', 'FAILED'); - expect(result.error).to.include('All uploads failed'); + // --ignore-sha-check skips the dedup short-circuit and performs a real + // upload. The mock returns a valid `uploads/` staging path, so the TUS + // fallback upload succeeds and the command returns the new binary id. + const { stdout } = await exec(command, { timeout: 60_000 }); + expectUploadJson(stdout); }); }); }); From 58c3b1b7a96dc30bd938cb741f7241fe3ba56f0d Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:19:25 +0100 Subject: [PATCH 19/78] chore(dev): release 5.0.0-beta.4 (#59) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index a690fc9..25dd525 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.0.0-beta.3" + ".": "5.0.0-beta.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f4548ab..8b85d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [5.0.0-beta.4](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.3...v5.0.0-beta.4) (2026-06-26) + + +### Features + +* render DB-driven notices and forward CLI/CI identity ([#58](https://github.com/devicecloud-dev/dcd-cli/issues/58)) ([10dfdbf](https://github.com/devicecloud-dev/dcd-cli/commit/10dfdbf9a5d0ebf12568e90a1fad623213175368)) + ## [5.0.0-beta.3](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.2...v5.0.0-beta.3) (2026-06-25) diff --git a/package.json b/package.json index 7dd3ebe..a655cce 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From c99f040baaf069de948c8c982b0dbbeb43218783 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:21:19 +0100 Subject: [PATCH 20/78] fix: notices render polish (#60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(notices): single ⚠ symbol and distinct deprecation styling Notice rendering routed warn/deprecation through warnOut (logger.warn), which prepends its own ⚠ on top of the one ui.warn adds — producing a doubled ⚠ ⚠ and a stray ⚠ on each branch row. Render through the gated out channel instead and add the level symbol explicitly, so it shows a single symbol. Deprecation now uses a red ⚠ to read as more serious than a yellow warn (they were identical). * ci: point mock-api checkout at devicecloud-dev/dcd; docs: update CLAUDE.md Update the cli-ci mock-api checkout from moropo-com/dcd to devicecloud-dev/dcd (org rename) so it no longer relies on the redirect, and refresh CLAUDE.md. --- .github/workflows/cli-ci.yml | 6 +++--- CLAUDE.md | 2 +- src/commands/cloud.ts | 2 +- src/services/notices.service.ts | 21 +++++++++++++++------ 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 1cb3bc1..961eb4a 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -36,7 +36,7 @@ jobs: lint-and-test: runs-on: ubuntu-latest - # The mock-api lives in the private moropo-com/dcd repo, checked out via an + # The mock-api lives in the private devicecloud-dev/dcd repo, checked out via an # SSH deploy key. GitHub does NOT expose secrets to pull_request workflows # triggered from forks, so that checkout (and the integration tests that need # it) can only run for same-repo events. Fork PRs still run lint/typecheck/build. @@ -57,7 +57,7 @@ jobs: if: env.HAS_PRIVATE_ACCESS == 'true' uses: actions/checkout@v7 with: - repository: moropo-com/dcd + repository: devicecloud-dev/dcd path: dcd ssh-key: ${{ secrets.DCD_SSH_DEPLOY_KEY }} # api/swagger.json is a file, which cone-mode sparse checkout rejects @@ -106,7 +106,7 @@ jobs: - name: Skip integration tests (fork PR — no mock-api access) if: env.HAS_PRIVATE_ACCESS != 'true' - run: echo "::notice::Integration tests skipped — the mock-api (private moropo-com/dcd) is not accessible from fork PRs. Lint, typecheck, and build still ran." + run: echo "::notice::Integration tests skipped — the mock-api (private devicecloud-dev/dcd) is not accessible from fork PRs. Lint, typecheck, and build still ran." - name: Build CLI working-directory: ./cli diff --git a/CLAUDE.md b/CLAUDE.md index ab99a23..985e4a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,7 +56,7 @@ Full guide in `CONTRIBUTING.md`; the operationally important parts (the ones tha - Type → bump (pre-1.0, so `feat` and breaking `!` both bump **minor**): `feat` minor; `fix`/`perf`/`deps`/`revert`/`refactor` patch; `docs`/`chore`/`test`/`ci`/`build`/`style` are hidden and bump nothing. Allowed scopes are free-form. - **Never hand-edit `package.json` version, `CHANGELOG.md`, or the `.release-please-manifest*.json` files** — release-please owns all of them. `src/types/generated/schema.types.ts` is likewise generated (openapi-typescript). - A first-time contributor must sign the CLA (the CLA Assistant bot comments on the first PR); the CLA check must be green to merge. -- **CI (`.github/workflows/cli-ci.yml`) runs on every PR** including forks: gitleaks secret scan, `pnpm lint`, `pnpm typecheck`, `pnpm build`, `pnpm audit --audit-level moderate`. The **integration tests need the private `moropo-com/dcd` mock-api** (cloned via the `DCD_SSH_DEPLOY_KEY` secret), and GitHub withholds secrets from fork and Dependabot PRs — so `pnpm test` is **skipped there** and a maintainer runs the full suite before merge. gitleaks also runs as a pre-commit hook (allowlist in `.gitleaks.toml`); without the binary installed the hook self-skips and CI is the backstop. +- **CI (`.github/workflows/cli-ci.yml`) runs on every PR** including forks: gitleaks secret scan, `pnpm lint`, `pnpm typecheck`, `pnpm build`, `pnpm audit --audit-level moderate`. The **integration tests need the private `devicecloud-dev/dcd` mock-api** (cloned via the `DCD_SSH_DEPLOY_KEY` secret), and GitHub withholds secrets from fork and Dependabot PRs — so `pnpm test` is **skipped there** and a maintainer runs the full suite before merge. gitleaks also runs as a pre-commit hook (allowlist in `.gitleaks.toml`); without the binary installed the hook self-skips and CI is the backstop. ## Releases diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 5f8a75c..db1c0fb 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -491,7 +491,7 @@ export const cloudCommand = defineCommand({ ci_provider: ciContext.provider, ci_wrapper_version: ciContext.wrapperVersion, }, - { out, warnOut }, + { out }, ); deviceValidationService.validateAndroidDevice( diff --git a/src/services/notices.service.ts b/src/services/notices.service.ts index ea3c5f5..55296aa 100644 --- a/src/services/notices.service.ts +++ b/src/services/notices.service.ts @@ -1,5 +1,5 @@ import { ui } from '../utils/ui.js'; -import { colors } from '../utils/styling.js'; +import { colors, symbols } from '../utils/styling.js'; import { compareSemver } from './version.service.js'; export type NoticeLevel = 'deprecation' | 'warn' | 'info' | 'marketing'; @@ -88,8 +88,13 @@ export function matchesRules( } export interface RenderNoticesOptions { + /** + * Emit a line of human output. Notices route through the same gated `out` as + * the rest of the CLI (suppressed under `--json`); we don't use the `warn` + * channel because its logger prepends its own `⚠`, which would double up with + * the symbol the `ui.*` helpers already add. + */ out: (message: string) => void; - warnOut: (message: string) => void; } /** Render a single notice with styling appropriate to its level. */ @@ -101,9 +106,13 @@ function renderNotice(notice: Notice, opts: RenderNoticesOptions): void { switch (notice.level) { case 'deprecation': + // Red ⚠ so a deprecation reads as more serious than a plain (yellow) warn. + opts.out(`${colors.error('⚠')} ${colors.bold(notice.title)}`); + opts.out(ui.branch(rows)); + break; case 'warn': - opts.warnOut(ui.warn(colors.bold(notice.title))); - opts.warnOut(ui.branch(rows)); + opts.out(`${symbols.warning} ${colors.bold(notice.title)}`); + opts.out(ui.branch(rows)); break; case 'marketing': opts.out(ui.section(notice.title)); @@ -120,8 +129,8 @@ function renderNotice(notice: Notice, opts: RenderNoticesOptions): void { /** * Filter notices by their local-context `match` gate and render those that pass. * Returns the visible notices so a `--json` caller can include them in its - * payload instead of printing. When `--json` is active, callers pass no-op - * out/warnOut so nothing is printed but the list is still returned. + * payload instead of printing. `opts.out` is the caller's `--json`-gated + * emitter, so under `--json` nothing prints but the list is still returned. */ export function renderNotices( notices: Notice[] | undefined, From 5adec1855f523ee2396377d44416d09df0ef49e5 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:31:26 +0100 Subject: [PATCH 21/78] chore: release 5.0.1-beta.1 (#62) Release-As: 5.0.1-beta.1 From ee23356905808a893d8c5e4d4bfdfc60ed496ec4 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:33:30 +0100 Subject: [PATCH 22/78] chore(dev): release 5.0.1-beta.1 (#61) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index 25dd525..c8330f9 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.0.0-beta.4" + ".": "5.0.1-beta.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b85d74..b7ceb74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [5.0.1-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.4...v5.0.1-beta.1) (2026-06-26) + + +### Bug Fixes + +* notices render polish ([#60](https://github.com/devicecloud-dev/dcd-cli/issues/60)) ([c99f040](https://github.com/devicecloud-dev/dcd-cli/commit/c99f040baaf069de948c8c982b0dbbeb43218783)) + + +### Miscellaneous + +* release 5.0.1-beta.1 ([#62](https://github.com/devicecloud-dev/dcd-cli/issues/62)) ([5adec18](https://github.com/devicecloud-dev/dcd-cli/commit/5adec1855f523ee2396377d44416d09df0ef49e5)) + ## [5.0.0-beta.4](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.3...v5.0.0-beta.4) (2026-06-26) diff --git a/package.json b/package.json index a655cce..46400d9 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.0.0-beta.4", + "version": "5.0.1-beta.1", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From 1d331b4e5e1e02cb86615df6e0315a5171e012ec Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:35:00 +0100 Subject: [PATCH 23/78] refactor: route notice rendering through ui (add ui.deprecation) (#66) Notice warn/deprecation rendering hand-concatenated symbols/colors in the service layer, violating the STYLE_GUIDE rule that all human-facing output goes through ui.ts. Add a ui.deprecation() helper (red warning glyph) and a matching symbols.deprecation, and route warn through ui.warn. Output is unchanged. --- src/services/notices.service.ts | 7 +++---- src/utils/styling.ts | 1 + src/utils/ui.ts | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/services/notices.service.ts b/src/services/notices.service.ts index 55296aa..9a8070d 100644 --- a/src/services/notices.service.ts +++ b/src/services/notices.service.ts @@ -1,5 +1,5 @@ import { ui } from '../utils/ui.js'; -import { colors, symbols } from '../utils/styling.js'; +import { colors } from '../utils/styling.js'; import { compareSemver } from './version.service.js'; export type NoticeLevel = 'deprecation' | 'warn' | 'info' | 'marketing'; @@ -106,12 +106,11 @@ function renderNotice(notice: Notice, opts: RenderNoticesOptions): void { switch (notice.level) { case 'deprecation': - // Red ⚠ so a deprecation reads as more serious than a plain (yellow) warn. - opts.out(`${colors.error('⚠')} ${colors.bold(notice.title)}`); + opts.out(ui.deprecation(colors.bold(notice.title))); opts.out(ui.branch(rows)); break; case 'warn': - opts.out(`${symbols.warning} ${colors.bold(notice.title)}`); + opts.out(ui.warn(colors.bold(notice.title))); opts.out(ui.branch(rows)); break; case 'marketing': diff --git a/src/utils/styling.ts b/src/utils/styling.ts index 78a3625..53d1e7b 100644 --- a/src/utils/styling.ts +++ b/src/utils/styling.ts @@ -16,6 +16,7 @@ export const stripAnsi = (s: string): string => s.replace(/\u001B\[[0-9;]*m/g, ' */ export const symbols = { cancelled: chalk.gray('⊘'), + deprecation: chalk.red('⚠'), error: chalk.red('✗'), info: chalk.blue('ℹ'), pending: chalk.yellow('⏸'), diff --git a/src/utils/ui.ts b/src/utils/ui.ts index 3977616..7b2b141 100644 --- a/src/utils/ui.ts +++ b/src/utils/ui.ts @@ -70,6 +70,11 @@ export const ui = { }); }, + /** `⚠ message` in red — a deprecation; reads as more serious than a (yellow) {@link warn}. */ + deprecation(message: string): string { + return `${symbols.deprecation} ${message}`; + }, + /** `ℹ message` — neutral, standalone information. */ info(message: string): string { return `${symbols.info} ${message}`; From 851f05178c0cffcb63114ce6e9e9ffc80303e84a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:02:47 +0100 Subject: [PATCH 24/78] chore: bump eslint-plugin-unicorn from 68.0.0 to 69.0.0 (#70) Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 68.0.0 to 69.0.0. - [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases) - [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v68.0.0...v69.0.0) --- updated-dependencies: - dependency-name: eslint-plugin-unicorn dependency-version: 69.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 46400d9..095e426 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-unicorn": "^68.0.0", + "eslint-plugin-unicorn": "^69.0.0", "husky": "^9.1.7", "mocha": "^11.7.6", "prettier": "^3.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5db7755..338dc9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,8 +109,8 @@ importers: specifier: ^2.32.0 version: 2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0) eslint-plugin-unicorn: - specifier: ^68.0.0 - version: 68.0.0(eslint@10.5.0) + specifier: ^69.0.0 + version: 69.0.0(eslint@10.5.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -615,8 +615,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.38: - resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==} + baseline-browser-mapping@2.10.40: + resolution: {integrity: sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==} engines: {node: '>=6.0.0'} hasBin: true @@ -658,8 +658,8 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - builtin-modules@5.2.0: - resolution: {integrity: sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ==} + builtin-modules@5.3.0: + resolution: {integrity: sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ==} engines: {node: '>=18.20'} bytes@3.1.2: @@ -840,8 +840,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.376: - resolution: {integrity: sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==} + electron-to-chromium@1.5.381: + resolution: {integrity: sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -944,8 +944,8 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-unicorn@68.0.0: - resolution: {integrity: sha512-mHYWvX948Q4H3bGc39bsNMxD/leOuNI+Iws9NVsoSz5VA7EGP86wnz7mZ/SPSvRhefT8L4hd8DHfDuGC+lIoCQ==} + eslint-plugin-unicorn@69.0.0: + resolution: {integrity: sha512-ZN/KtHr9hQ6AOByANSNJpsDbo/+Nn+EyQ6blK4w+dcmS/xpYkqLLfrUc+NA/wOK6vF5uEUvhn8my5B/3sruB9g==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -1154,8 +1154,8 @@ packages: 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 hasBin: true - globals@17.6.0: - resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} engines: {node: '>=18'} globalthis@1.0.4: @@ -1559,8 +1559,8 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} - node-releases@2.0.48: - resolution: {integrity: sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==} + node-releases@2.0.50: + resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} engines: {node: '>=18'} node-stream-zip@1.15.0: @@ -2591,7 +2591,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.38: {} + baseline-browser-mapping@2.10.40: {} big-integer@1.6.52: {} @@ -2630,17 +2630,17 @@ snapshots: browserslist@4.28.4: dependencies: - baseline-browser-mapping: 2.10.38 + baseline-browser-mapping: 2.10.40 caniuse-lite: 1.0.30001799 - electron-to-chromium: 1.5.376 - node-releases: 2.0.48 + electron-to-chromium: 1.5.381 + node-releases: 2.0.50 update-browserslist-db: 1.2.3(browserslist@4.28.4) buffer-crc32@1.0.0: {} buffer-from@1.1.2: {} - builtin-modules@5.2.0: {} + builtin-modules@5.3.0: {} bytes@3.1.2: {} @@ -2804,7 +2804,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.376: {} + electron-to-chromium@1.5.381: {} emoji-regex@8.0.0: {} @@ -2993,7 +2993,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-unicorn@68.0.0(eslint@10.5.0): + eslint-plugin-unicorn@69.0.0(eslint@10.5.0): dependencies: '@babel/helper-validator-identifier': 7.29.7 '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0) @@ -3004,7 +3004,7 @@ snapshots: detect-indent: 7.0.2 eslint: 10.5.0 find-up-simple: 1.0.1 - globals: 17.6.0 + globals: 17.7.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 @@ -3283,7 +3283,7 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - globals@17.6.0: {} + globals@17.7.0: {} globalthis@1.0.4: dependencies: @@ -3383,7 +3383,7 @@ snapshots: is-builtin-module@5.0.0: dependencies: - builtin-modules: 5.2.0 + builtin-modules: 5.3.0 is-callable@1.2.7: {} @@ -3656,7 +3656,7 @@ snapshots: node-forge@1.4.0: {} - node-releases@2.0.48: {} + node-releases@2.0.50: {} node-stream-zip@1.15.0: {} From cfd9fe23fcee003535f73ab72754eb5aeba5cec5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:08:34 +0100 Subject: [PATCH 25/78] deps: bump the minor-and-patch group across 1 directory with 9 updates (#71) --- updated-dependencies: - dependency-name: "@clack/prompts" dependency-version: 1.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: "@supabase/supabase-js" dependency-version: 2.110.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: "@types/node" dependency-version: 26.1.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: eslint dependency-version: 10.6.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: js-yaml dependency-version: 5.2.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: prettier dependency-version: 3.9.4 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: tar dependency-version: 7.5.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: tsx dependency-version: 4.23.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: typescript-eslint dependency-version: 8.62.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 330 ++++++++++++++++++++++++------------------------- 1 file changed, 165 insertions(+), 165 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 338dc9e..43ccd66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,13 +37,13 @@ importers: dependencies: '@clack/prompts': specifier: ^1.6.0 - version: 1.6.0 + version: 1.7.0 '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.4.3) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.108.2 + version: 2.110.2 bplist-parser: specifier: ^0.3.2 version: 0.3.2 @@ -55,7 +55,7 @@ importers: version: 0.2.2 js-yaml: specifier: ^5.0.0 - version: 5.0.0 + version: 5.2.1 node-apk: specifier: ^1.2.1 version: 1.2.1 @@ -67,7 +67,7 @@ importers: version: 5.0.0 tar: specifier: ^7.5.16 - version: 7.5.16 + version: 7.5.19 tus-js-client: specifier: ^4.3.1 version: 4.3.1 @@ -80,7 +80,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.5.0) + version: 10.0.1(eslint@10.6.0) '@types/chai': specifier: ^5.2.3 version: 5.2.3 @@ -92,7 +92,7 @@ importers: version: 10.0.10 '@types/node': specifier: ^26.0.0 - version: 26.0.0 + version: 26.1.1 '@types/yazl': specifier: ^3.3.1 version: 3.3.1 @@ -101,16 +101,16 @@ importers: version: 6.2.2 eslint: specifier: ^10.5.0 - version: 10.5.0 + version: 10.6.0 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.5.0) + version: 10.1.8(eslint@10.6.0) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0) + version: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0) eslint-plugin-unicorn: specifier: ^69.0.0 - version: 69.0.0(eslint@10.5.0) + version: 69.0.0(eslint@10.6.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -119,19 +119,19 @@ importers: version: 11.7.6 prettier: specifier: ^3.8.4 - version: 3.8.4 + version: 3.9.5 shx: specifier: ^0.4.0 version: 0.4.0 tsx: specifier: ^4.22.4 - version: 4.22.4 + version: 4.23.0 typescript: specifier: ^6.0.3 version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.61.1(eslint@10.5.0)(typescript@6.0.3) + version: 8.63.0(eslint@10.6.0)(typescript@6.0.3) packages: @@ -139,12 +139,12 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@clack/core@1.4.2': - resolution: {integrity: sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==} + '@clack/core@1.4.3': + resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} engines: {node: '>= 20.12.0'} - '@clack/prompts@1.6.0': - resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==} + '@clack/prompts@1.7.0': + resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} engines: {node: '>= 20.12.0'} '@esbuild/aix-ppc64@0.28.1': @@ -405,32 +405,32 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@supabase/auth-js@2.108.2': - resolution: {integrity: sha512-tNaQmBgodDZwgB40mRwVbxFy8IDYwjdpcZ0BYrWiwlULCSQoJj4QoG4zgJT7QRPXcqipefNOzvO/qAu4dF98ag==} - engines: {node: '>=20.0.0'} + '@supabase/auth-js@2.110.2': + resolution: {integrity: sha512-Qj7a6EDP+AMMQFWqGv+qFa8r6re//dk+qQI5bA0KK+PZmnI3JPu97TDeNt6SMiQ2FkklP79hP2yDFYSnA989OA==} + engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.108.2': - resolution: {integrity: sha512-RNUX8EiBy3iLwAX19jtRzLyePnl11/fHcgwDHLnpKcDSXt/5qBnh3LUwAtIjT21Q66QsmNUR2esrHziLCpNubw==} - engines: {node: '>=20.0.0'} + '@supabase/functions-js@2.110.2': + resolution: {integrity: sha512-ZjjqrXpxM9/rE+eAtZxiK45EWy9EBoJQ322Q5Y75LccYQNh212neHTgXP/o4MIzmH0LNXT8UzvTZtQOfOzyoeQ==} + engines: {node: '>=22.0.0'} '@supabase/phoenix@0.4.4': resolution: {integrity: sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==} - '@supabase/postgrest-js@2.108.2': - resolution: {integrity: sha512-GQ28/Y8hk3CFmkb3kXH1h/AQx6JIYSQfO0CJMRVBcEKZoNy6C45cXAZ4fcJvRC5Id0cs6xnkUV0+c0rIocigsw==} - engines: {node: '>=20.0.0'} + '@supabase/postgrest-js@2.110.2': + resolution: {integrity: sha512-++LBmcIMwCtgO4tISQUmo9+2xkRwHQqS8ZKMCnhXLe9P8k8YQRXuMoh/RiSzQSoev8gqet0W7yOboW0cUxnt0Q==} + engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.108.2': - resolution: {integrity: sha512-aAGxCSUemZvQIibnCdvNvgaKib28I4rfrNjKbQ9cG1uBLwUsI7hVpGXgEbypCCDhLjQlDTAiJlu7rgljYUT73g==} - engines: {node: '>=20.0.0'} + '@supabase/realtime-js@2.110.2': + resolution: {integrity: sha512-z3jTOTPgyn6E3r6dVOOQ10He4yAMB2czjFw7xVdX3s16MHElna5rY1gVaePs0NIo6xvtMYbtmOXlFaFt/ePLpg==} + engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.108.2': - resolution: {integrity: sha512-TVZPQxXGxY2+A6yTtm77zUHsh70lBhYUEaJL8RQC+BghcX/ygiMG/rmXrNVBce30/WAeNPa8FiG8HbqlGeV05g==} - engines: {node: '>=20.0.0'} + '@supabase/storage-js@2.110.2': + resolution: {integrity: sha512-EhsRSwSnmQefKJsAxoRUZ0hvHr92ECM8DDGAKR5z0HdoJx4heI60PjHUTruVNZxKX6XeobLGDyLud020Bw1iwg==} + engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.108.2': - resolution: {integrity: sha512-hFhnPveb5JQg4a0QYicM0swT253YHMdfeRAl2BKHOlI5VAzuHxUGSr8RbwNLYNPauWOgQMS1H8sz8bvYlgwUfQ==} - engines: {node: '>=20.0.0'} + '@supabase/supabase-js@2.110.2': + resolution: {integrity: sha512-r9q9w4ZQ6mOjh36aqUNFSisBF611vzpO8JphBESr2Q1SWvmGFQeI7Jq7Y+PaNMZ6Zszz+S2yTlJStCpnaMSnQg==} + engines: {node: '>=22.0.0'} '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -456,69 +456,69 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@26.0.0': - resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} - '@typescript-eslint/eslint-plugin@8.61.1': - resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==} + '@typescript-eslint/eslint-plugin@8.63.0': + resolution: {integrity: sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.61.1 + '@typescript-eslint/parser': ^8.63.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.61.1': - resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==} + '@typescript-eslint/parser@8.63.0': + resolution: {integrity: sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.61.1': - resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==} + '@typescript-eslint/project-service@8.63.0': + resolution: {integrity: sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.61.1': - resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==} + '@typescript-eslint/scope-manager@8.63.0': + resolution: {integrity: sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.61.1': - resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==} + '@typescript-eslint/tsconfig-utils@8.63.0': + resolution: {integrity: sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.61.1': - resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==} + '@typescript-eslint/type-utils@8.63.0': + resolution: {integrity: sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.61.1': - resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==} + '@typescript-eslint/types@8.63.0': + resolution: {integrity: sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.61.1': - resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==} + '@typescript-eslint/typescript-estree@8.63.0': + resolution: {integrity: sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.61.1': - resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==} + '@typescript-eslint/utils@8.63.0': + resolution: {integrity: sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.61.1': - resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==} + '@typescript-eslint/visitor-keys@8.63.0': + resolution: {integrity: sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.10': @@ -962,8 +962,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.5.0: - resolution: {integrity: sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==} + eslint@10.6.0: + resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1225,8 +1225,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} engines: {node: '>= 4'} imurmurhash@0.1.4: @@ -1406,8 +1406,8 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-yaml@5.0.0: - resolution: {integrity: sha512-GSvaPUbk1U+FMZ7rJzF+F8e5YVtu7KnD40et/5rBXXRBv2jCO9L3qCewvIDDdudC0QycTFlf6EAA+h3kxBsuUw==} + js-yaml@5.2.1: + resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==} hasBin: true jsesc@3.1.0: @@ -1666,8 +1666,8 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} pkce-challenge@5.0.1: @@ -1690,8 +1690,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.4: - resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + prettier@3.9.5: + resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} engines: {node: '>=14'} hasBin: true @@ -1954,8 +1954,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tar@7.5.16: - resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==} + tar@7.5.19: + resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -1982,8 +1982,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + tsx@4.23.0: + resolution: {integrity: sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==} engines: {node: '>=18.0.0'} hasBin: true @@ -2015,8 +2015,8 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.61.1: - resolution: {integrity: sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==} + typescript-eslint@8.63.0: + resolution: {integrity: sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2140,14 +2140,14 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} - '@clack/core@1.4.2': + '@clack/core@1.4.3': dependencies: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@clack/prompts@1.6.0': + '@clack/prompts@1.7.0': dependencies: - '@clack/core': 1.4.2 + '@clack/core': 1.4.3 fast-string-width: 3.0.2 fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 @@ -2230,9 +2230,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.5.0)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0)': dependencies: - eslint: 10.5.0 + eslint: 10.6.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2253,9 +2253,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.5.0)': + '@eslint/js@10.0.1(eslint@10.6.0)': optionalDependencies: - eslint: 10.5.0 + eslint: 10.6.0 '@eslint/object-schema@3.0.5': {} @@ -2336,37 +2336,37 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@supabase/auth-js@2.108.2': + '@supabase/auth-js@2.110.2': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.108.2': + '@supabase/functions-js@2.110.2': dependencies: tslib: 2.8.1 '@supabase/phoenix@0.4.4': {} - '@supabase/postgrest-js@2.108.2': + '@supabase/postgrest-js@2.110.2': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.108.2': + '@supabase/realtime-js@2.110.2': dependencies: '@supabase/phoenix': 0.4.4 tslib: 2.8.1 - '@supabase/storage-js@2.108.2': + '@supabase/storage-js@2.110.2': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.108.2': + '@supabase/supabase-js@2.110.2': dependencies: - '@supabase/auth-js': 2.108.2 - '@supabase/functions-js': 2.108.2 - '@supabase/postgrest-js': 2.108.2 - '@supabase/realtime-js': 2.108.2 - '@supabase/storage-js': 2.108.2 + '@supabase/auth-js': 2.110.2 + '@supabase/functions-js': 2.110.2 + '@supabase/postgrest-js': 2.110.2 + '@supabase/realtime-js': 2.110.2 + '@supabase/storage-js': 2.110.2 '@types/chai@5.2.3': dependencies: @@ -2387,80 +2387,80 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@26.0.0': + '@types/node@26.1.1': dependencies: undici-types: 8.3.0 '@types/yazl@3.3.1': dependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 - '@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/type-utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.1 - eslint: 10.5.0 - ignore: 7.0.5 + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/type-utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.63.0 + eslint: 10.6.0 + ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.63.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.5.0 + eslint: 10.6.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.61.1(typescript@6.0.3)': + '@typescript-eslint/project-service@8.63.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) + '@typescript-eslint/types': 8.63.0 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.61.1': + '@typescript-eslint/scope-manager@8.63.0': dependencies: - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/visitor-keys': 8.63.0 - '@typescript-eslint/tsconfig-utils@8.61.1(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.63.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.63.0(eslint@10.6.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.5.0 + eslint: 10.6.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.61.1': {} + '@typescript-eslint/types@8.63.0': {} - '@typescript-eslint/typescript-estree@8.61.1(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.63.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.61.1(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/project-service': 8.63.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/visitor-keys': 8.63.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.3 semver: 7.8.5 @@ -2470,20 +2470,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.63.0(eslint@10.6.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0) - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - eslint: 10.5.0 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + eslint: 10.6.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.61.1': + '@typescript-eslint/visitor-keys@8.63.0': dependencies: - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/types': 8.63.0 eslint-visitor-keys: 5.0.1 '@xmldom/xmldom@0.9.10': {} @@ -2942,9 +2942,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.5.0): + eslint-config-prettier@10.1.8(eslint@10.6.0): dependencies: - eslint: 10.5.0 + eslint: 10.6.0 eslint-import-resolver-node@0.3.10: dependencies: @@ -2954,17 +2954,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.6.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - eslint: 10.5.0 + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + eslint: 10.6.0 eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -2973,9 +2973,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.5.0 + eslint: 10.6.0 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0) + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.6.0) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -2987,22 +2987,22 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-unicorn@69.0.0(eslint@10.5.0): + eslint-plugin-unicorn@69.0.0(eslint@10.6.0): dependencies: '@babel/helper-validator-identifier': 7.29.7 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) browserslist: 4.28.4 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.49.0 detect-indent: 7.0.2 - eslint: 10.5.0 + eslint: 10.6.0 find-up-simple: 1.0.1 globals: 17.7.0 indent-string: 5.0.0 @@ -3024,9 +3024,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.5.0: + eslint@10.6.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -3163,9 +3163,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.4): + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.5 file-entry-cache@8.0.0: dependencies: @@ -3338,7 +3338,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} + ignore@7.0.6: {} imurmurhash@0.1.4: {} @@ -3502,7 +3502,7 @@ snapshots: js-base64@3.7.8: {} - js-yaml@5.0.0: + js-yaml@5.2.1: dependencies: argparse: 2.0.1 @@ -3622,7 +3622,7 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 5.0.0 + js-yaml: 5.2.1 log-symbols: 4.1.0 minimatch: 9.0.7 ms: 2.1.3 @@ -3762,7 +3762,7 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} pkce-challenge@5.0.1: {} @@ -3777,7 +3777,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.8.4: {} + prettier@3.9.5: {} proper-lockfile@4.1.2: dependencies: @@ -4089,7 +4089,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tar@7.5.16: + tar@7.5.19: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -4099,8 +4099,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 to-regex-range@5.0.1: dependencies: @@ -4121,7 +4121,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.22.4: + tsx@4.23.0: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -4180,13 +4180,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.61.1(eslint@10.5.0)(typescript@6.0.3): + typescript-eslint@8.63.0(eslint@10.6.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - eslint: 10.5.0 + '@typescript-eslint/eslint-plugin': 8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + eslint: 10.6.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color From 7fd7748632fdce2a0f417ee9af73243de5d4b1a8 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Fri, 10 Jul 2026 15:55:15 +0100 Subject: [PATCH 26/78] fix: recover cleanly when the stored session is dead (#72) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to devicecloud-dev/dcd#1084 (which fixes the root cause of "Invalid Refresh Token: Already Used" by minting the CLI its own session at login). This fixes the recovery path for sessions that are already broken: - CliAuthGateway.refresh now throws a typed SessionRefreshError whose `definitive` flag distinguishes a GoTrue 4xx token rejection from transient failures (network, 5xx). - On a definitive rejection, resolveAuth drops the dead session from the config (keeping env/api_url/org), so subsequent commands report a clean "Not authenticated" instead of failing the same refresh every time. - `dcd login` no longer dead-ends users whose session has expired: the "Already logged in… Sign out and log in again?" confirm is skipped for expired sessions (it proceeds straight to sign-in), and defaults to Yes for live ones — running `dcd login` deliberately almost always means "log me in". Also updates the login-flow comments and CLAUDE.md for the new handoff contract (access token as identity proof; api mints a dedicated session). Co-authored-by: Claude Fable 5 --- CLAUDE.md | 2 +- src/commands/login.ts | 43 ++++++++++++++++++++---------- src/gateways/cli-auth-gateway.ts | 30 ++++++++++++++++++--- src/utils/auth.ts | 37 +++++++++++++++++++++----- test/unit/auth.test.ts | 45 +++++++++++++++++++++++++++++++- 5 files changed, 132 insertions(+), 25 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 985e4a3..9b32b1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,7 @@ Top-level `defineCommand` in `src/index.ts` wires eleven subcommands (`cloud`, ` **Auth.** Every command calls `resolveAuth({ apiKeyFlag })` (`src/utils/auth.ts`) once and threads the returned `AuthContext` into gateways/services. `ApiGateway` and `fetchCompatibilityData` spread `auth.headers` into fetch headers — they no longer accept a raw api key. Precedence: `--api-key` flag > `DEVICE_CLOUD_API_KEY` env > stored session from `dcd login`. `resolveAuth` refreshes expiring Supabase sessions via `CliAuthGateway.refresh` and rewrites the config atomically. -**Config store.** `dcd login` writes `$XDG_CONFIG_HOME/dcd/config.json` (fallback `~/.dcd/config.json`, 0600). Shape: `{ version, env, api_url, supabase_url, session: { access_token, refresh_token, expires_at, user_email, user_id }, current_org_id, current_org_name }`. `DCD_CONFIG_DIR` overrides the directory (used by tests). The login command itself (`src/commands/login.ts`) uses PKCE (S256) with a server rendezvous — no loopback server: it mints `state`, `code_verifier`, and `code_challenge`, opens `/cli-login?state=...&code_challenge=...`, then polls the dcd API's `POST /cli-login/claim` with `{state, code_verifier}` while the frontend POSTs the session to `POST /cli-login/handoff`; the API verifies `sha256(verifier) === challenge` and returns the session. After claiming, the CLI fetches `/me/orgs` and prompts for an org (the same picker `dcd switch-org` uses). The frontend lives in `../dcd/frontend/app/features/cli-login/CliLoginScreen.tsx`. +**Config store.** `dcd login` writes `$XDG_CONFIG_HOME/dcd/config.json` (fallback `~/.dcd/config.json`, 0600). Shape: `{ version, env, api_url, supabase_url, session: { access_token, refresh_token, expires_at, user_email, user_id }, current_org_id, current_org_name }`. `DCD_CONFIG_DIR` overrides the directory (used by tests). The login command itself (`src/commands/login.ts`) uses PKCE (S256) with a server rendezvous — no loopback server: it mints `state`, `code_verifier`, and `code_challenge`, opens `/cli-login?state=...&code_challenge=...`, then polls the dcd API's `POST /cli-login/claim` with `{state, code_verifier}` while the frontend POSTs proof of identity (the browser's access token) to `POST /cli-login/handoff`; the API verifies the token, **mints a dedicated Supabase session for the CLI** (its own refresh-token family — sharing the browser's tokens caused "Invalid Refresh Token: Already Used" whenever either client rotated them), stores it keyed by state, then on claim verifies `sha256(verifier) === challenge` and returns it. After claiming, the CLI fetches `/me/orgs` and prompts for an org (the same picker `dcd switch-org` uses). The frontend lives in `../dcd/frontend/app/features/cli-login/CliLoginScreen.tsx`. **Cross-repo auth surface.** The dcd API's `ApiKeyGuard` accepts either `x-app-api-key` (existing) or `Authorization: Bearer ` + `x-dcd-org: `. For Bearer it verifies the JWT, checks `user_org_profile` membership, and injects the org's api_key back into the request headers so existing `@Headers(APP_API_KEY_HEADER)` controller code keeps working unchanged. `dcd switch-org` calls `GET /me/orgs`, a JWT-only endpoint at `../dcd/api/src/apps/me/me.controller.ts`. diff --git a/src/commands/login.ts b/src/commands/login.ts index b451813..ba6e9dc 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -10,8 +10,11 @@ * 2. CLI opens /cli-login?state=...&code_challenge=... in the browser. * 3. User signs in (OTP or SSO) and explicitly authorizes the handoff on * the frontend. - * 4. Frontend POSTs {state, code_challenge, session...} to the dcd api at - * POST /cli-login/handoff. The api stores a short-TTL row keyed by state. + * 4. Frontend POSTs {state, code_challenge, access_token} to the dcd api at + * POST /cli-login/handoff — the access token is proof of identity only. + * The api verifies it, mints a *dedicated* Supabase session for the CLI + * (its own refresh-token family, so browser token rotation can't + * invalidate it), and stores it in a short-TTL row keyed by state. * 5. Meanwhile, the CLI polls POST /cli-login/claim with {state, code_verifier}. * Once the api has the row, it verifies sha256(verifier) === challenge, * deletes the row, and returns the session. @@ -81,20 +84,32 @@ export const loginCommand = defineCommand({ // If there's an existing stored session, make the user confirm before we // overwrite it. Silent clobber is fine for power users but surprising if - // someone runs `dcd login` by mistake while already authenticated. + // someone runs `dcd login` by mistake while already authenticated. An + // already-expired session gets no confirm: the user is here because a + // command told them to re-login, and "Already logged in… keep session?" + // would dead-end them on a session that no longer works. const existing = readConfig(); if (existing?.session) { - const currentOrg = existing.current_org_name ?? existing.current_org_id; - const ok = await p.confirm({ - message: - `Already logged in as ${existing.session.user_email}` + - (currentOrg ? ` (org ${currentOrg})` : '') + - `. Sign out and log in again?`, - initialValue: false, - }); - if (p.isCancel(ok) || !ok) { - logger.log(ui.info('Keeping existing session.')); - return; + const now = Math.floor(Date.now() / 1000); + if (existing.session.expires_at <= now) { + logger.log( + ui.info( + `Your session for ${existing.session.user_email} has expired — signing in again.`, + ), + ); + } else { + const currentOrg = existing.current_org_name ?? existing.current_org_id; + const ok = await p.confirm({ + message: + `Already logged in as ${existing.session.user_email}` + + (currentOrg ? ` (org ${currentOrg})` : '') + + `. Sign out and log in again?`, + initialValue: true, + }); + if (p.isCancel(ok) || !ok) { + logger.log(ui.info('Keeping existing session.')); + return; + } } } diff --git a/src/gateways/cli-auth-gateway.ts b/src/gateways/cli-auth-gateway.ts index 247609a..35b10cf 100644 --- a/src/gateways/cli-auth-gateway.ts +++ b/src/gateways/cli-auth-gateway.ts @@ -3,12 +3,28 @@ * and sign-out (best-effort revocation on the Supabase side). * * Does not talk to the dcd API — the dcd-side session exchange lives in the - * login command's loopback flow, where the frontend POSTs ciphertext back. + * login command's PKCE rendezvous flow (see src/commands/login.ts). */ -import { createClient } from '@supabase/supabase-js'; +import { createClient, isAuthApiError } from '@supabase/supabase-js'; import type { StoredSession } from '../utils/config-store.js'; +/** + * Thrown when a session refresh fails. `definitive` distinguishes "Supabase + * rejected this refresh token" (revoked, already used, malformed — re-login + * is the only fix) from transient failures (network, GoTrue 5xx) where the + * stored session may still be good on the next attempt. + */ +export class SessionRefreshError extends Error { + constructor( + message: string, + readonly definitive: boolean, + ) { + super(message); + this.name = 'SessionRefreshError'; + } +} + export interface RefreshedSession { access_token: string; refresh_token: string; @@ -35,9 +51,17 @@ export const CliAuthGateway = { refresh_token: session.refresh_token, }); if (error || !data.session || !data.user) { - throw new Error( + // 4xx from GoTrue means the token itself was rejected; anything else + // (fetch failure, 5xx) could succeed on retry with the same token. + const definitive = + error != null && + isAuthApiError(error) && + error.status >= 400 && + error.status < 500; + throw new SessionRefreshError( `Failed to refresh session: ${error?.message ?? 'no session returned'}. ` + `Run \`dcd login\` again.`, + definitive, ); } const s = data.session; diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 62a376f..21aecac 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -10,7 +10,10 @@ import { closeSync, openSync, rmSync, statSync } from 'node:fs'; import { ENVIRONMENTS } from '../config/environments.js'; -import { CliAuthGateway } from '../gateways/cli-auth-gateway.js'; +import { + CliAuthGateway, + SessionRefreshError, +} from '../gateways/cli-auth-gateway.js'; import { telemetry } from '../services/telemetry.service.js'; import type { AuthContext } from '../types/domain/auth.types.js'; @@ -124,11 +127,19 @@ async function refreshSessionWithLock( } const { anonKey } = ENVIRONMENTS[current.env].supabase; - const refreshed = await CliAuthGateway.refresh( - current.supabase_url, - anonKey, - session, - ); + let refreshed; + try { + refreshed = await CliAuthGateway.refresh( + current.supabase_url, + anonKey, + session, + ); + } catch (error) { + if (error instanceof SessionRefreshError && error.definitive) { + dropStoredSession(); + } + throw error; + } // Re-read again and merge only `session` so a concurrent `switch-org` // write (org fields) isn't reverted by our pre-refresh snapshot. const merged: StoredConfig = { ...(readConfig() ?? current), session: refreshed }; @@ -139,6 +150,20 @@ async function refreshSessionWithLock( } } +/** + * Remove only the (definitively dead) session from the stored config, keeping + * env/api_url/org so a re-login lands back in the same environment. With the + * dead session gone, subsequent commands report "Not authenticated" instead + * of failing the same refresh, and `dcd login` skips its "already logged in" + * confirm. Exported for tests. + */ +export function dropStoredSession(): void { + const config = readConfig(); + if (!config?.session) return; + delete config.session; + writeConfig(config); +} + async function acquireRefreshLock(lockPath: string): Promise { const deadline = Date.now() + LOCK_WAIT_MS; for (;;) { diff --git a/test/unit/auth.test.ts b/test/unit/auth.test.ts index 6140715..89ed4dd 100644 --- a/test/unit/auth.test.ts +++ b/test/unit/auth.test.ts @@ -3,7 +3,8 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { resolveAuth } from '../../src/utils/auth.js'; +import { SessionRefreshError } from '../../src/gateways/cli-auth-gateway.js'; +import { dropStoredSession, resolveAuth } from '../../src/utils/auth.js'; import { clearConfig, configFileMode, @@ -181,6 +182,48 @@ describe('resolveAuth precedence', () => { }); }); + it('surfaces definitive vs transient refresh failures via SessionRefreshError', () => { + const dead = new SessionRefreshError('Invalid Refresh Token: Already Used', true); + const blip = new SessionRefreshError('fetch failed', false); + expect(dead.definitive).to.equal(true); + expect(blip.definitive).to.equal(false); + expect(dead).to.be.instanceOf(Error); + }); + + it('dropStoredSession removes only the session, keeping env/org fields', async () => { + await withTempConfigDir(() => { + writeConfig({ + version: 1, + env: 'dev', + api_url: 'https://api.dev.devicecloud.dev', + supabase_url: 'https://lbmsowehtjwnqlurpemb.supabase.co', + session: { + access_token: 'a', + refresh_token: 'consumed-by-another-client', + expires_at: Math.floor(Date.now() / 1000) - 60, + user_email: 'u@example.com', + user_id: 'u1', + }, + current_org_id: '42', + current_org_name: 'Acme', + }); + + dropStoredSession(); + + const after = readConfig(); + expect(after).to.not.equal(null); + expect(after!.session).to.equal(undefined); + expect(after!.env).to.equal('dev'); + expect(after!.api_url).to.equal('https://api.dev.devicecloud.dev'); + expect(after!.current_org_id).to.equal('42'); + expect(after!.current_org_name).to.equal('Acme'); + + // Idempotent when there's no session to drop. + dropStoredSession(); + expect(readConfig()!.session).to.equal(undefined); + }); + }); + it('uses a non-expired stored session as a last resort', async () => { await withTempConfigDir(async () => { writeConfig({ From b78a1dc92f61428e6c42a914c0779886d3713e79 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:33:42 +0100 Subject: [PATCH 27/78] =?UTF-8?q?feat(cloud):=20device=20matrix=20via=20re?= =?UTF-8?q?peated=20--ios-config/--android-config=E2=80=A6=20(#75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(cloud): device matrix via repeated --ios-config/--android-config (#1105) One upload targets N device configs. Each --ios-config : / --android-config :[:play] names exactly one validated cell — no cross-product. A device matrix is single-platform; mixing platforms is rejected before any upload. - parseDeviceMatrix() (unit-tested): parsing, :play Google Play cell, mixed- platform + malformed rejection. - Each cell validated against the compatibility matrix up front; fails fast naming the bad cell before the flow zip is uploaded. - Serialized as the deviceMatrix field; single-device submissions stay byte- identical. targetPlatform now derives from the matrix too. - Pre-submit cost preview via POST /uploads/estimateMatrix (cells + est. cost + per-column breakdown); tolerant of older APIs that lack the endpoint (404). - --json tests[].device (structured {name, osVersion, googlePlay}) on both the sync and async paths, so a flow run on two devices is disambiguated. Note: generated schema.types.ts is intentionally not regenerated here (it lags dev's swagger); regenerate from dev after the API lands. Runtime reads config/simulator_name structurally. * fix(cloud): tolerate 405 from estimateMatrix preview (#1105) The cost preview is best-effort, but the gateway only treated 404 as 'endpoint absent' and threw on 405. An API that predates the endpoint (incl. the CI Prism mock running against dev's swagger before this lands) 405s with NO_METHOD_MATCHED, which blocked the submit. Treat 404 and 405 alike — return null and proceed without a preview. A 400 (invalid config) still surfaces. * test: run integration CLI via execFile argv, not a shell (#1105) CodeQL flags every `exec(\)` in the integration suite as js/shell-command-injection-from-environment — the built CLI's absolute path (path.resolve) flows through a template string into a shell. There is no real injection (test harness, local fixture paths), but the whole class is avoidable. Route the shared exec helper through execFile(process.execPath, argv) — no shell — by tokenising the `${CLI} ` command. This is CodeQL's recommended remediation (argument list, not a shell string) and clears the entire class across all five integration files at once, with no call-site changes (signature-compatible; runExpectingFailure still reads stdout/stderr from the rejection). Verified the CLI runs and non-zero exits still surface stderr. --- src/commands/cloud.ts | 76 ++++++++++++++++++++++ src/config/flags/device.flags.ts | 8 +++ src/gateways/api-gateway.ts | 46 +++++++++++++ src/services/results-polling.service.ts | 42 ++++++++++++ src/services/test-submission.service.ts | 21 +++++- src/types/domain/device.types.ts | 14 ++++ src/utils/device-matrix.ts | 68 +++++++++++++++++++ test/integration/cloud.integration.test.ts | 26 ++++++++ test/integration/helpers.ts | 43 +++++++++++- test/unit/device-matrix.test.ts | 61 +++++++++++++++++ 10 files changed, 402 insertions(+), 3 deletions(-) create mode 100644 src/utils/device-matrix.ts create mode 100644 test/unit/device-matrix.test.ts diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index db1c0fb..081ea17 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -20,6 +20,7 @@ import { import { MoropoService } from '../services/moropo.service.js'; import { ReportDownloadService } from '../services/report-download.service.js'; import { + deviceFromResultRow, ResultsPollingService, RunFailedError, } from '../services/results-polling.service.js'; @@ -31,8 +32,10 @@ import { EAndroidDevices, EiOSDevices, EiOSVersions, + isIosMatrixConfig, } from '../types/domain/device.types.js'; import { resolveAuth } from '../utils/auth.js'; +import { matrixIsIos, parseDeviceMatrix } from '../utils/device-matrix.js'; import { detectCiContext, isCI } from '../utils/ci.js'; import { CliError, @@ -211,6 +214,10 @@ export const cloudCommand = defineCommand({ 'android-device', ); const androidNoSnapshot = Boolean(args['android-no-snapshot']); + // Repeatable device-matrix flags: one validated cell each, no cross-product. + const iosConfigFlags = collectRepeatedFlag(rawArgs, ['--ios-config']); + const androidConfigFlags = collectRepeatedFlag(rawArgs, ['--android-config']); + const deviceMatrix = parseDeviceMatrix(iosConfigFlags, androidConfigFlags); const json = Boolean(args.json); const jsonFileFlag = Boolean(args['json-file']); const jsonFileName = args['json-file-name'] as string | undefined; @@ -502,6 +509,28 @@ export const cloudCommand = defineCommand({ { debug, logger: (m: string) => out(m) }, ); + // Validate every device-matrix cell up front — each on its own, against + // the same compatibility matrix — so an unsupported cell fails fast, + // naming it, before anything is uploaded. + for (const cfg of deviceMatrix) { + if (isIosMatrixConfig(cfg)) { + deviceValidationService.validateiOSDevice( + cfg.iOSVersion, + cfg.iOSDevice, + compatibilityData, + { debug, logger: (m: string) => out(m) }, + ); + } else { + deviceValidationService.validateAndroidDevice( + cfg.androidApiLevel, + cfg.androidDevice, + cfg.googlePlay ?? googlePlay, + compatibilityData, + { debug, logger: (m: string) => out(m) }, + ); + } + } + if (maestroChromeOnboarding && !androidApiLevel && !androidDevice) { warnOut( 'The --maestro-chrome-onboarding flag only applies to Android tests and will be ignored for iOS tests.', @@ -639,6 +668,8 @@ export const cloudCommand = defineCommand({ 'include-tags': includeTags, 'exclude-tags': excludeTags, 'exclude-flows': excludeFlows, + 'ios-config': iosConfigFlags, + 'android-config': androidConfigFlags, }; for (const [k, v] of Object.entries(args)) { if (!canonicalFlagKeys.has(k)) continue; @@ -762,6 +793,7 @@ export const cloudCommand = defineCommand({ continueOnFailure, debug, deviceLocale, + deviceMatrix, env, executionPlan, flowFile, @@ -784,6 +816,49 @@ export const cloudCommand = defineCommand({ disableAnimations, }); + // Device-matrix cost preview: the server prices the exact fan-out (quote + // == charge) so the user sees the cell count and estimated cost before the + // flow zip is uploaded. An unsupported cell fails fast here. Skipped when + // there is no matrix, and tolerant of older APIs that lack the endpoint. + if (deviceMatrix.length > 0) { + const estimate = await ApiGateway.estimateMatrix(apiUrl, auth, fields); + if (estimate) { + const osPrefix = matrixIsIos(deviceMatrix) ? 'iOS' : 'API'; + const rows = ui.fields([ + ['cells', colors.highlight(String(estimate.cellCount))], + ['est. cost', colors.highlight(`$${estimate.totalCost.toFixed(2)}`)], + ]); + for (const col of estimate.columns) { + const label = [ + col.deviceName, + col.osVersion && `${osPrefix} ${col.osVersion}`, + col.googlePlay && 'Play', + ] + .filter(Boolean) + .join(' · '); + rows.push( + ...ui.fields([ + [ + label, + colors.dim( + `${col.flowCount} flow${col.flowCount === 1 ? '' : 's'} · $${col.cost.toFixed(2)}`, + ), + ], + ]), + ); + } + if (estimate.excludedFlows.length > 0) { + rows.push( + colors.dim( + `${estimate.excludedFlows.length} flow${estimate.excludedFlows.length === 1 ? '' : 's'} target their own device (excluded from the matrix)`, + ), + ); + } + out(ui.section('Device matrix')); + out(ui.branch(rows)); + } + } + // New path: upload the zip directly to storage, then submit a JSON test // referencing it. Older API deployments lack these endpoints — a real API // 404s (route undefined), some proxies 405 (path/method not allowed); in @@ -865,6 +940,7 @@ export const cloudCommand = defineCommand({ consoleUrl: url, status: 'PENDING', tests: results.map((r) => ({ + device: deviceFromResultRow(r), fileName: r.test_file_name, flowName: testMetadataMap[r.test_file_name]?.flowName || diff --git a/src/config/flags/device.flags.ts b/src/config/flags/device.flags.ts index 37135d4..1fa81df 100644 --- a/src/config/flags/device.flags.ts +++ b/src/config/flags/device.flags.ts @@ -42,6 +42,14 @@ export const deviceFlags = { type: 'string', description: `[iOS only] iOS version to run your flow against (options: ${iosVersions})`, }, + 'ios-config': { + type: 'string', + description: `[iOS only] Device-matrix cell as :, e.g. iphone-16:18. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --android-config.`, + }, + 'android-config': { + type: 'string', + description: `[Android only] Device-matrix cell as : (append :play for Google Play), e.g. pixel-7:34 or pixel-7:34:play. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --ios-config.`, + }, orientation: { type: 'string', description: diff --git a/src/gateways/api-gateway.ts b/src/gateways/api-gateway.ts index 7ba37b4..48eeb18 100644 --- a/src/gateways/api-gateway.ts +++ b/src/gateways/api-gateway.ts @@ -588,6 +588,52 @@ export const ApiGateway = { } }, + /** + * Dry-run cost + cell-count estimate for a (possibly device-matrix) + * submission. Runs the same resolve → validate → fan-out → price core as the + * submit path server-side, without persisting. The preview is best-effort: + * an API that predates this endpoint 404s (route undefined) or 405s (route + * matched a sibling path, no POST) — in either case return null so the caller + * proceeds without a preview. A 400 (an invalid config) still surfaces as a + * normal API error so the CLI can fail fast before uploading the flow zip. + */ + async estimateMatrix(baseUrl: string, auth: AuthContext, body: Record) { + try { + const res = await fetch(`${baseUrl}/uploads/estimateMatrix`, { + body: JSON.stringify(body), + headers: { + 'content-type': 'application/json', + ...auth.headers, + }, + method: 'POST', + }); + if (res.status === 404 || res.status === 405) { + return null; + } + if (!res.ok) { + await this.handleApiError(res, 'Failed to estimate device matrix'); + } + return await parseJsonResponse<{ + cellCount: number; + totalCost: number; + excludedFlows: string[]; + columns: Array<{ + deviceName: string; + osVersion: string; + googlePlay: boolean; + flowCount: number; + cost: number; + }>; + }>(res, 'Failed to estimate device matrix'); + } catch (error) { + if (error instanceof TypeError && error.message === 'fetch failed') { + throw this.enhanceFetchError(error, `${baseUrl}/uploads/estimateMatrix`); + } + + throw error; + } + }, + /** * Generic report download method that handles both junit and allure reports diff --git a/src/services/results-polling.service.ts b/src/services/results-polling.service.ts index c1cf800..774fc66 100644 --- a/src/services/results-polling.service.ts +++ b/src/services/results-polling.service.ts @@ -47,10 +47,46 @@ export interface TestMetadata { tags: string[]; } +/** + * The device a result ran on. Additive: single-device runs are unchanged, and + * a device matrix disambiguates two `tests[]` entries that share a `name` (the + * same flow on two devices) by their device. + */ +export interface TestDevice { + googlePlay?: boolean; + name?: string; + osVersion?: string; +} + +/** + * Structured device for a result row. Prefers the friendly deviceName/osVersion + * the per-flow targeting / matrix fan-out stamps onto each result's config + * (#1097), falling back to the raw simulator_name for older rows. Returns + * undefined when neither is present, so single-device runs that predate the + * field simply omit `device`. Shared by the sync polling path and the async + * (--async --json) path so both emit an identical device shape. + */ +export function deviceFromResultRow(r: { + config?: unknown; + simulator_name?: string | null; +}): TestDevice | undefined { + const config = (r.config ?? {}) as { deviceName?: string; osVersion?: string }; + const sim = r.simulator_name ?? undefined; + const name = config.deviceName ?? sim; + if (!name && !config.osVersion) return undefined; + return { + name, + osVersion: config.osVersion, + googlePlay: sim ? /(_PLAY|-play)$/.test(sim) : undefined, + }; +} + export interface PollingResult { consoleUrl: string; status: 'FAILED' | 'PASSED'; tests: Array<{ + /** Device this result ran on (present when the API reports it). */ + device?: TestDevice; durationSeconds: null | number; failReason?: string; /** File path of the test (same as name, for clarity) */ @@ -311,6 +347,12 @@ export class ResultsPollingService { ? 'PASSED' : 'FAILED', tests: resultsWithoutEarlierTries.map((r) => ({ + // r carries config/simulator_name at runtime; the committed generated + // types lag the API (regenerated wholesale from dev's swagger), so read + // them through the helper's structural type. + device: deviceFromResultRow( + r as { config?: unknown; simulator_name?: string | null }, + ), durationSeconds: r.duration_seconds ?? null, failReason: r.status === 'FAILED' ? r.fail_reason || 'No reason provided' : undefined, diff --git a/src/services/test-submission.service.ts b/src/services/test-submission.service.ts index 5d4aea6..bbff215 100644 --- a/src/services/test-submission.service.ts +++ b/src/services/test-submission.service.ts @@ -2,6 +2,7 @@ import { createHash } from 'node:crypto'; import * as path from 'node:path'; import { compressFilesFromRelativePath } from '../methods.js'; +import { DeviceMatrixConfig } from '../types/domain/device.types.js'; import { toPortableRelativePath } from '../utils/paths.js'; import { IExecutionPlan } from './execution-plan.service.js'; @@ -15,6 +16,7 @@ export interface TestSubmissionConfig { continueOnFailure?: boolean; debug?: boolean; deviceLocale?: string; + deviceMatrix?: DeviceMatrixConfig[]; disableAnimations?: boolean; env?: string[]; executionPlan: IExecutionPlan; @@ -85,6 +87,7 @@ export class TestSubmissionService { maestroChromeOnboarding, raw, disableAnimations, + deviceMatrix, debug = false, logger, } = config; @@ -183,7 +186,23 @@ export class TestSubmissionService { // Note: googlePlay is now included in configPayload below instead of as a separate field // to work around a FormData parsing issue in the API - const targetPlatform = iOSDevice || iOSVersion ? 'ios' : 'android'; + // Explicit device matrix (one upload, N cells). Only sent when present, so + // single-device submissions stay byte-identical. + if (deviceMatrix && deviceMatrix.length > 0) { + fields.deviceMatrix = JSON.stringify(deviceMatrix); + } + + // Platform used only to pick which workspace-config disableAnimations flag + // applies. A device matrix is single-platform; its first cell decides. Fall + // back to the scalar iOS flags for single-device submissions. + const matrixPlatform = + deviceMatrix && deviceMatrix.length > 0 + ? 'iOSDevice' in deviceMatrix[0] + ? 'ios' + : 'android' + : undefined; + const targetPlatform = + matrixPlatform ?? (iOSDevice || iOSVersion ? 'ios' : 'android'); const configYamlDisableAnimations = targetPlatform === 'ios' ? Boolean(workspaceConfig?.platform?.ios?.disableAnimations) diff --git a/src/types/domain/device.types.ts b/src/types/domain/device.types.ts index 064c23a..45e2632 100644 --- a/src/types/domain/device.types.ts +++ b/src/types/domain/device.types.ts @@ -40,3 +40,17 @@ export enum EAndroidApiLevels { 'thirtyTwo' = '32', 'twentyNine' = '29', } + +/** + * One explicit device-matrix cell. iOS entries carry {iOSDevice, iOSVersion}; + * Android entries carry {androidDevice, androidApiLevel} plus an optional Play + * channel. Sent to the API as the `deviceMatrix` array; every non-targeted flow + * runs once per cell. There is no cross-product — each entry is one cell. + */ +export type DeviceMatrixConfig = + | { iOSDevice: string; iOSVersion: string } + | { androidApiLevel: string; androidDevice: string; googlePlay?: boolean }; + +export const isIosMatrixConfig = ( + c: DeviceMatrixConfig, +): c is { iOSDevice: string; iOSVersion: string } => 'iOSDevice' in c; diff --git a/src/utils/device-matrix.ts b/src/utils/device-matrix.ts new file mode 100644 index 0000000..18e26cc --- /dev/null +++ b/src/utils/device-matrix.ts @@ -0,0 +1,68 @@ +import { + DeviceMatrixConfig, + isIosMatrixConfig, +} from '../types/domain/device.types.js'; +import { CliError } from './cli.js'; + +/** + * Parse repeated `--ios-config :` and + * `--android-config :[:play]` flags into an explicit device + * matrix. Each entry is one validated cell — there is NO cross-product, because + * the compatibility matrix is ragged and a cross-product would invent cells the + * user never asked for. + * + * A device matrix is single-platform (one upload, one binary), so mixing iOS + * and Android configs is rejected here before anything is uploaded. + * + * @throws CliError on malformed syntax or a mixed-platform matrix. + */ +export function parseDeviceMatrix( + iosConfigs: string[], + androidConfigs: string[], +): DeviceMatrixConfig[] { + if (iosConfigs.length > 0 && androidConfigs.length > 0) { + throw new CliError( + 'A device matrix cannot mix platforms: use either --ios-config or --android-config, not both. One upload runs one binary.', + ); + } + + const configs: DeviceMatrixConfig[] = []; + + for (const raw of iosConfigs) { + const parts = raw.split(':'); + if (parts.length !== 2 || !parts[0] || !parts[1]) { + throw new CliError( + `Invalid --ios-config "${raw}". Expected :, e.g. iphone-16:18.`, + ); + } + configs.push({ iOSDevice: parts[0], iOSVersion: parts[1] }); + } + + for (const raw of androidConfigs) { + const parts = raw.split(':'); + // : with an optional trailing :play for a Play cell. + if ( + parts.length < 2 || + parts.length > 3 || + !parts[0] || + !parts[1] || + (parts.length === 3 && parts[2] !== 'play') + ) { + throw new CliError( + `Invalid --android-config "${raw}". Expected : or ::play, e.g. pixel-7:34 or pixel-7:34:play.`, + ); + } + configs.push({ + androidDevice: parts[0], + androidApiLevel: parts[1], + ...(parts.length === 3 ? { googlePlay: true } : {}), + }); + } + + return configs; +} + +/** True when the matrix targets iOS (used to pick the validation lookup). */ +export function matrixIsIos(configs: DeviceMatrixConfig[]): boolean { + return configs.length > 0 && isIosMatrixConfig(configs[0]); +} diff --git a/test/integration/cloud.integration.test.ts b/test/integration/cloud.integration.test.ts index 253057f..ab1de5d 100644 --- a/test/integration/cloud.integration.test.ts +++ b/test/integration/cloud.integration.test.ts @@ -163,6 +163,32 @@ appId: com.example.app }); }); + // #1105 device matrix: repeated --ios-config / --android-config cells. + describe('device matrix', () => { + it('accepts a repeated --ios-config matrix and still yields one upload', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16:18 --ios-config iphone-16-pro:26 --async --json`; + + const { stdout } = await exec(command, { timeout: 30_000 }); + // Still one upload with one uploadId — the matrix is a property of the + // upload, not N uploads. + expectAsyncRunJson(stdout); + }); + + it('rejects mixing --ios-config and --android-config before any upload', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16:18 --android-config pixel-7:34`; + + const { output } = await runExpectingFailure(command); + expect(output.toLowerCase()).to.include('cannot mix platforms'); + }); + + it('rejects a malformed --ios-config, naming the value', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16`; + + const { output } = await runExpectingFailure(command); + expect(output).to.include('iphone-16'); + }); + }); + describe('device configuration options', () => { // Async non-JSON runs always reach submission against the mock API. // `.include` keeps these robust to incidental extra lines (e.g. the diff --git a/test/integration/helpers.ts b/test/integration/helpers.ts index d60eeb1..0c998f9 100644 --- a/test/integration/helpers.ts +++ b/test/integration/helpers.ts @@ -7,15 +7,54 @@ * assert the success path unconditionally: a dead or missing mock API must * fail the suite, never soften it. */ -import { exec as execCallback } from 'node:child_process'; +import { execFile as execFileCallback } from 'node:child_process'; import * as path from 'node:path'; import { promisify } from 'node:util'; -export const exec = promisify(execCallback); +const execFileAsync = promisify(execFileCallback); /** Absolute path to the built CLI so tests can run with any cwd. */ export const CLI = path.resolve('dist/index.js'); +export interface ExecResult { + stdout: string; + stderr: string; +} + +/** + * Split a `${CLI} …` command line into argv, honouring single/double quotes so + * a quoted multi-word value stays one token. These test commands contain no + * other shell metacharacters, so this is exact for our use. + */ +function tokenize(command: string): string[] { + const tokens: string[] = []; + const pattern = /"([^"]*)"|'([^']*)'|(\S+)/g; + let match: RegExpExecArray | null; + while ((match = pattern.exec(command)) !== null) { + tokens.push(match[1] ?? match[2] ?? match[3]); + } + return tokens; +} + +/** + * Run a test command **without a shell**. Every integration command is + * `${CLI} ` — an absolute script path plus arguments — so we tokenise it + * and invoke the current Node binary directly with the script and args as argv + * (`execFile`, never `exec`). Passing an argument list instead of a shell string + * is CodeQL's recommended fix for `js/shell-command-injection-from-environment`: + * with no shell there is nothing for the (uncontrolled, but trusted) absolute + * paths to inject into. Signature-compatible with the previous + * `promisify(child_process.exec)` so no call site changes. + */ +export async function exec( + command: string, + opts: { cwd?: string; env?: NodeJS.ProcessEnv; timeout?: number } = {}, +): Promise { + const argv = tokenize(command); + const { stdout, stderr } = await execFileAsync(process.execPath, argv, opts); + return { stdout: String(stdout), stderr: String(stderr) }; +} + export const MOCK_API_URL = process.env.MOCK_API_URL ?? 'http://localhost:3001'; /** One of the keys accepted by the mock API's auth shim. */ diff --git a/test/unit/device-matrix.test.ts b/test/unit/device-matrix.test.ts new file mode 100644 index 0000000..7e8c497 --- /dev/null +++ b/test/unit/device-matrix.test.ts @@ -0,0 +1,61 @@ +import { expect } from 'chai'; + +import { CliError } from '../../src/utils/cli.js'; +import { + matrixIsIos, + parseDeviceMatrix, +} from '../../src/utils/device-matrix.js'; + +/** + * The device matrix is the load-bearing part of #1105: each --ios-config / + * --android-config names exactly one cell, there is no cross-product, and a + * matrix is single-platform. These are pure and worth pinning precisely. + */ +describe('parseDeviceMatrix', () => { + it('returns an empty matrix when no config flags are passed', () => { + expect(parseDeviceMatrix([], [])).to.deep.equal([]); + expect(matrixIsIos([])).to.equal(false); + }); + + it('parses each --ios-config as exactly one cell (no cross-product)', () => { + const matrix = parseDeviceMatrix( + ['iphone-16:18', 'iphone-16-pro:26'], + [], + ); + expect(matrix).to.deep.equal([ + { iOSDevice: 'iphone-16', iOSVersion: '18' }, + { iOSDevice: 'iphone-16-pro', iOSVersion: '26' }, + ]); + expect(matrixIsIos(matrix)).to.equal(true); + }); + + it('parses --android-config, with :play marking a Google Play cell', () => { + expect(parseDeviceMatrix([], ['pixel-7:34', 'pixel-7:34:play'])).to.deep.equal([ + { androidDevice: 'pixel-7', androidApiLevel: '34' }, + { androidDevice: 'pixel-7', androidApiLevel: '34', googlePlay: true }, + ]); + }); + + it('rejects a mixed-platform matrix', () => { + expect(() => + parseDeviceMatrix(['iphone-16:18'], ['pixel-7:34']), + ).to.throw(CliError, /cannot mix platforms/i); + }); + + it('rejects malformed iOS syntax, naming the offending value', () => { + expect(() => parseDeviceMatrix(['iphone-16'], [])).to.throw( + CliError, + /iphone-16/, + ); + expect(() => parseDeviceMatrix(['iphone-16:'], [])).to.throw(CliError); + expect(() => parseDeviceMatrix([':18'], [])).to.throw(CliError); + }); + + it('rejects malformed Android syntax and a bad third segment', () => { + expect(() => parseDeviceMatrix([], ['pixel-7'])).to.throw(CliError); + expect(() => parseDeviceMatrix([], ['pixel-7:34:store'])).to.throw( + CliError, + /pixel-7:34:store/, + ); + }); +}); From bc9c61f2fc20074980798ce155d14b0571ddbecb Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:50:12 +0100 Subject: [PATCH 28/78] chore: release 5.2.0-beta.1 (#76) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beta of the upload-level device matrix (#75). It is a `feat`, so minor. The beta manifest had drifted behind stable (5.0.1-beta.1 while latest is 5.1.0), so release-please computed 5.1.0-beta.1 — semver BELOW the published 5.1.0, which would have pointed the `beta` dist-tag at something older than `latest`. 5.2.0 sits above stable 5.1.0 and clears the pending 5.1.1 (#74), so `beta` stays ahead of `latest` either way. Release-As: 5.2.0-beta.1 From 4ef0292876b7d220b6484e37e6e66c510c3f0955 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:52:32 +0100 Subject: [PATCH 29/78] chore(dev): release 5.2.0-beta.1 (#67) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 27 +++++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index c8330f9..e6f5dcc 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.0.1-beta.1" + ".": "5.2.0-beta.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b7ceb74..9a7acbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [5.2.0-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.1-beta.1...v5.2.0-beta.1) (2026-07-13) + + +### Features + +* **cloud:** device matrix via repeated --ios-config/--android-config… ([#75](https://github.com/devicecloud-dev/dcd-cli/issues/75)) ([b78a1dc](https://github.com/devicecloud-dev/dcd-cli/commit/b78a1dc92f61428e6c42a914c0779886d3713e79)) + + +### Bug Fixes + +* recover cleanly when the stored session is dead ([#72](https://github.com/devicecloud-dev/dcd-cli/issues/72)) ([7fd7748](https://github.com/devicecloud-dev/dcd-cli/commit/7fd7748632fdce2a0f417ee9af73243de5d4b1a8)) + + +### Dependencies + +* bump the minor-and-patch group across 1 directory with 9 updates ([#71](https://github.com/devicecloud-dev/dcd-cli/issues/71)) ([cfd9fe2](https://github.com/devicecloud-dev/dcd-cli/commit/cfd9fe23fcee003535f73ab72754eb5aeba5cec5)) + + +### Code Refactoring + +* route notice rendering through ui (add ui.deprecation) ([#66](https://github.com/devicecloud-dev/dcd-cli/issues/66)) ([1d331b4](https://github.com/devicecloud-dev/dcd-cli/commit/1d331b4e5e1e02cb86615df6e0315a5171e012ec)) + + +### Miscellaneous + +* release 5.2.0-beta.1 ([#76](https://github.com/devicecloud-dev/dcd-cli/issues/76)) ([bc9c61f](https://github.com/devicecloud-dev/dcd-cli/commit/bc9c61f2fc20074980798ce155d14b0571ddbecb)) + ## [5.0.1-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.0-beta.4...v5.0.1-beta.1) (2026-06-26) diff --git a/package.json b/package.json index 095e426..93aed9a 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.0.1-beta.1", + "version": "5.2.0-beta.1", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From fc3ea3f9fa41f51a640827dbe845deca7b355924 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:37:41 +0100 Subject: [PATCH 30/78] refactor(cloud): rename --ios-config/--android-config to --ios-device-matrix/--android-device-matrix (#77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames the device-matrix flags to match the API's `deviceMatrix` field and to say what they actually are (#1105). Clean break — no alias, no migration guard. The old names only ever existed in 5.2.0-beta.1, an unconsumed beta, so there is nobody to migrate. Deliberately NOT marked breaking. A `!` bumps the MAJOR on this 5.x repo (`bump-minor-pre-major` only applies below 1.0.0), which would ship a bogus 6.0.0 for renaming flags that nobody uses. --- src/commands/cloud.ts | 10 +++++----- src/config/flags/device.flags.ts | 8 ++++---- src/utils/device-matrix.ts | 10 +++++----- test/integration/cloud.integration.test.ts | 14 +++++++------- test/unit/device-matrix.test.ts | 8 ++++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 081ea17..8eb2106 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -215,9 +215,9 @@ export const cloudCommand = defineCommand({ ); const androidNoSnapshot = Boolean(args['android-no-snapshot']); // Repeatable device-matrix flags: one validated cell each, no cross-product. - const iosConfigFlags = collectRepeatedFlag(rawArgs, ['--ios-config']); - const androidConfigFlags = collectRepeatedFlag(rawArgs, ['--android-config']); - const deviceMatrix = parseDeviceMatrix(iosConfigFlags, androidConfigFlags); + const iosMatrixFlags = collectRepeatedFlag(rawArgs, ['--ios-device-matrix']); + const androidMatrixFlags = collectRepeatedFlag(rawArgs, ['--android-device-matrix']); + const deviceMatrix = parseDeviceMatrix(iosMatrixFlags, androidMatrixFlags); const json = Boolean(args.json); const jsonFileFlag = Boolean(args['json-file']); const jsonFileName = args['json-file-name'] as string | undefined; @@ -668,8 +668,8 @@ export const cloudCommand = defineCommand({ 'include-tags': includeTags, 'exclude-tags': excludeTags, 'exclude-flows': excludeFlows, - 'ios-config': iosConfigFlags, - 'android-config': androidConfigFlags, + 'ios-device-matrix': iosMatrixFlags, + 'android-device-matrix': androidMatrixFlags, }; for (const [k, v] of Object.entries(args)) { if (!canonicalFlagKeys.has(k)) continue; diff --git a/src/config/flags/device.flags.ts b/src/config/flags/device.flags.ts index 1fa81df..62df3de 100644 --- a/src/config/flags/device.flags.ts +++ b/src/config/flags/device.flags.ts @@ -42,13 +42,13 @@ export const deviceFlags = { type: 'string', description: `[iOS only] iOS version to run your flow against (options: ${iosVersions})`, }, - 'ios-config': { + 'ios-device-matrix': { type: 'string', - description: `[iOS only] Device-matrix cell as :, e.g. iphone-16:18. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --android-config.`, + description: `[iOS only] Device-matrix cell as :, e.g. iphone-16:18. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --android-device-matrix.`, }, - 'android-config': { + 'android-device-matrix': { type: 'string', - description: `[Android only] Device-matrix cell as : (append :play for Google Play), e.g. pixel-7:34 or pixel-7:34:play. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --ios-config.`, + description: `[Android only] Device-matrix cell as : (append :play for Google Play), e.g. pixel-7:34 or pixel-7:34:play. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --ios-device-matrix.`, }, orientation: { type: 'string', diff --git a/src/utils/device-matrix.ts b/src/utils/device-matrix.ts index 18e26cc..8e9e5f9 100644 --- a/src/utils/device-matrix.ts +++ b/src/utils/device-matrix.ts @@ -5,8 +5,8 @@ import { import { CliError } from './cli.js'; /** - * Parse repeated `--ios-config :` and - * `--android-config :[:play]` flags into an explicit device + * Parse repeated `--ios-device-matrix :` and + * `--android-device-matrix :[:play]` flags into an explicit device * matrix. Each entry is one validated cell — there is NO cross-product, because * the compatibility matrix is ragged and a cross-product would invent cells the * user never asked for. @@ -22,7 +22,7 @@ export function parseDeviceMatrix( ): DeviceMatrixConfig[] { if (iosConfigs.length > 0 && androidConfigs.length > 0) { throw new CliError( - 'A device matrix cannot mix platforms: use either --ios-config or --android-config, not both. One upload runs one binary.', + 'A device matrix cannot mix platforms: use either --ios-device-matrix or --android-device-matrix, not both. One upload runs one binary.', ); } @@ -32,7 +32,7 @@ export function parseDeviceMatrix( const parts = raw.split(':'); if (parts.length !== 2 || !parts[0] || !parts[1]) { throw new CliError( - `Invalid --ios-config "${raw}". Expected :, e.g. iphone-16:18.`, + `Invalid --ios-device-matrix "${raw}". Expected :, e.g. iphone-16:18.`, ); } configs.push({ iOSDevice: parts[0], iOSVersion: parts[1] }); @@ -49,7 +49,7 @@ export function parseDeviceMatrix( (parts.length === 3 && parts[2] !== 'play') ) { throw new CliError( - `Invalid --android-config "${raw}". Expected : or ::play, e.g. pixel-7:34 or pixel-7:34:play.`, + `Invalid --android-device-matrix "${raw}". Expected : or ::play, e.g. pixel-7:34 or pixel-7:34:play.`, ); } configs.push({ diff --git a/test/integration/cloud.integration.test.ts b/test/integration/cloud.integration.test.ts index ab1de5d..7d052af 100644 --- a/test/integration/cloud.integration.test.ts +++ b/test/integration/cloud.integration.test.ts @@ -163,10 +163,10 @@ appId: com.example.app }); }); - // #1105 device matrix: repeated --ios-config / --android-config cells. + // #1105 device matrix: repeated --ios-device-matrix / --android-device-matrix cells. describe('device matrix', () => { - it('accepts a repeated --ios-config matrix and still yields one upload', async () => { - const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16:18 --ios-config iphone-16-pro:26 --async --json`; + it('accepts a repeated --ios-device-matrix matrix and still yields one upload', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-device-matrix iphone-16:18 --ios-device-matrix iphone-16-pro:26 --async --json`; const { stdout } = await exec(command, { timeout: 30_000 }); // Still one upload with one uploadId — the matrix is a property of the @@ -174,15 +174,15 @@ appId: com.example.app expectAsyncRunJson(stdout); }); - it('rejects mixing --ios-config and --android-config before any upload', async () => { - const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16:18 --android-config pixel-7:34`; + it('rejects mixing --ios-device-matrix and --android-device-matrix before any upload', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-device-matrix iphone-16:18 --android-device-matrix pixel-7:34`; const { output } = await runExpectingFailure(command); expect(output.toLowerCase()).to.include('cannot mix platforms'); }); - it('rejects a malformed --ios-config, naming the value', async () => { - const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16`; + it('rejects a malformed --ios-device-matrix, naming the value', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-device-matrix iphone-16`; const { output } = await runExpectingFailure(command); expect(output).to.include('iphone-16'); diff --git a/test/unit/device-matrix.test.ts b/test/unit/device-matrix.test.ts index 7e8c497..8a4cb11 100644 --- a/test/unit/device-matrix.test.ts +++ b/test/unit/device-matrix.test.ts @@ -7,8 +7,8 @@ import { } from '../../src/utils/device-matrix.js'; /** - * The device matrix is the load-bearing part of #1105: each --ios-config / - * --android-config names exactly one cell, there is no cross-product, and a + * The device matrix is the load-bearing part of #1105: each --ios-device-matrix / + * --android-device-matrix names exactly one cell, there is no cross-product, and a * matrix is single-platform. These are pure and worth pinning precisely. */ describe('parseDeviceMatrix', () => { @@ -17,7 +17,7 @@ describe('parseDeviceMatrix', () => { expect(matrixIsIos([])).to.equal(false); }); - it('parses each --ios-config as exactly one cell (no cross-product)', () => { + it('parses each --ios-device-matrix as exactly one cell (no cross-product)', () => { const matrix = parseDeviceMatrix( ['iphone-16:18', 'iphone-16-pro:26'], [], @@ -29,7 +29,7 @@ describe('parseDeviceMatrix', () => { expect(matrixIsIos(matrix)).to.equal(true); }); - it('parses --android-config, with :play marking a Google Play cell', () => { + it('parses --android-device-matrix, with :play marking a Google Play cell', () => { expect(parseDeviceMatrix([], ['pixel-7:34', 'pixel-7:34:play'])).to.deep.equal([ { androidDevice: 'pixel-7', androidApiLevel: '34' }, { androidDevice: 'pixel-7', androidApiLevel: '34', googlePlay: true }, From 8b6fde1faea1c35baadbbf4ced346107e6eec6de Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:57:52 +0100 Subject: [PATCH 31/78] =?UTF-8?q?docs:=20correct=20the=20release=20bump=20?= =?UTF-8?q?table=20=E2=80=94=20a=20breaking=20`!`=20bumps=20the=20MAJOR=20?= =?UTF-8?q?(#79)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The table claimed `feat` and a breaking `!` both bump the minor 'pre-1.0'. That is wrong for this repo: `bump-minor-pre-major` only applies below 1.0.0, and we are on 5.x, so a `!` bumps the MAJOR. Acting on the old wording is exactly how a `refactor(cloud)!:` PR title produced a 6.0.0-beta.1 release PR for a flag rename in an unconsumed beta. No Release-As footer: with the `!` removed from dev, release-please already computes 5.2.0-beta.2 on its own. Forcing it here would only risk that footer leaking into the production promotion. --- CLAUDE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9b32b1f..2cd76a9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,7 +53,8 @@ Full guide in `CONTRIBUTING.md`; the operationally important parts (the ones tha - **Branch off `dev`** (the default branch) and open PRs **against `dev`**. `production` is the maintainer-only stable track — never target it directly. - PRs are **squash-merged**, so the **PR title becomes the commit** and must be a [Conventional Commit](https://www.conventionalcommits.org). The title — not the branch commits — is what release-please reads to compute the next version, so it matters even though individual commits are squashed away. A `PR Title` CI check enforces it. -- Type → bump (pre-1.0, so `feat` and breaking `!` both bump **minor**): `feat` minor; `fix`/`perf`/`deps`/`revert`/`refactor` patch; `docs`/`chore`/`test`/`ci`/`build`/`style` are hidden and bump nothing. Allowed scopes are free-form. +- Type → bump: `feat` **minor**; `fix`/`perf`/`deps`/`revert`/`refactor` **patch**; `docs`/`chore`/`test`/`ci`/`build`/`style` are hidden and bump nothing. Allowed scopes are free-form. +- ⚠️ **A `!` (or `BREAKING CHANGE:` footer) bumps the MAJOR — do not use it casually.** The configs set `bump-minor-pre-major: true`, but that only applies **below 1.0.0**; we are on 5.x, so it is inert and a breaking marker means exactly what semver says. A `refactor(cloud)!:` PR title once produced a `6.0.0-beta.1` release PR for what was only a flag rename in an unconsumed beta. Because PRs are squash-merged, **the PR title IS the commit** — the `!` lands even if no branch commit carried it. - **Never hand-edit `package.json` version, `CHANGELOG.md`, or the `.release-please-manifest*.json` files** — release-please owns all of them. `src/types/generated/schema.types.ts` is likewise generated (openapi-typescript). - A first-time contributor must sign the CLA (the CLA Assistant bot comments on the first PR); the CLA check must be green to merge. - **CI (`.github/workflows/cli-ci.yml`) runs on every PR** including forks: gitleaks secret scan, `pnpm lint`, `pnpm typecheck`, `pnpm build`, `pnpm audit --audit-level moderate`. The **integration tests need the private `devicecloud-dev/dcd` mock-api** (cloned via the `DCD_SSH_DEPLOY_KEY` secret), and GitHub withholds secrets from fork and Dependabot PRs — so `pnpm test` is **skipped there** and a maintainer runs the full suite before merge. gitleaks also runs as a pre-commit hook (allowlist in `.gitleaks.toml`); without the binary installed the hook self-skips and CI is the backstop. From db71189e893b5a024d994b5e83b65554b7f77bd5 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:03:21 +0100 Subject: [PATCH 32/78] chore(dev): release 5.2.0-beta.2 (#78) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index e6f5dcc..134036a 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.2.0-beta.1" + ".": "5.2.0-beta.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a7acbb..0970d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [5.2.0-beta.2](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.1...v5.2.0-beta.2) (2026-07-13) + + +### Code Refactoring + +* **cloud:** rename --ios-config/--android-config to --ios-device-matrix/--android-device-matrix ([#77](https://github.com/devicecloud-dev/dcd-cli/issues/77)) ([fc3ea3f](https://github.com/devicecloud-dev/dcd-cli/commit/fc3ea3f9fa41f51a640827dbe845deca7b355924)) + ## [5.2.0-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.0.1-beta.1...v5.2.0-beta.1) (2026-07-13) diff --git a/package.json b/package.json index 93aed9a..a5a063e 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.2.0-beta.1", + "version": "5.2.0-beta.2", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From 5560158389440735354be963098a2704a8610040 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:42:45 +0100 Subject: [PATCH 33/78] fix(cloud): refuse a device matrix on an API that cannot honour it (#80) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An API predating #1105 does not merely lack the feature — it accepts the submission and SILENTLY STRIPS the unknown deviceMatrix field, because the API's global ValidationPipe runs whitelist:true / forbidNonWhitelisted:false. Every flow then runs on a single default device and the run exits 0. The user asked for N devices, tested one, and is told nothing: the exact silent under-testing the device matrix exists to prevent, and the worst outcome the feature can produce. The estimate endpoint is only called when a matrix was requested, so a null estimate (gateway maps 404/405 -> null) is a definitive 'API too old' signal. Fail loudly there instead of proceeding. This also removes the ordering footgun for the prod rollout structurally rather than by remembering it: if the CLI ever reaches an API without the matrix (stale deploy, rollback, lagging self-host), it now refuses instead of quietly under-testing. Verified against a local Prism mock served from PRODUCTION's swagger (no estimateMatrix): matrix run refuses with exit 1; a legacy single-device run on the same API still succeeds with exit 0. --- src/commands/cloud.ts | 10 +++++++++- src/utils/device-matrix.ts | 28 +++++++++++++++++++++++++++ test/unit/device-matrix.test.ts | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 8eb2106..bedb952 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -35,7 +35,11 @@ import { isIosMatrixConfig, } from '../types/domain/device.types.js'; import { resolveAuth } from '../utils/auth.js'; -import { matrixIsIos, parseDeviceMatrix } from '../utils/device-matrix.js'; +import { + assertMatrixSupported, + matrixIsIos, + parseDeviceMatrix, +} from '../utils/device-matrix.js'; import { detectCiContext, isCI } from '../utils/ci.js'; import { CliError, @@ -822,6 +826,10 @@ export const cloudCommand = defineCommand({ // there is no matrix, and tolerant of older APIs that lack the endpoint. if (deviceMatrix.length > 0) { const estimate = await ApiGateway.estimateMatrix(apiUrl, auth, fields); + // A null estimate means the API predates the matrix (404/405). It would + // silently strip deviceMatrix and run one device — refuse rather than + // hand back a green single-device run the user reads as a matrix. + assertMatrixSupported(deviceMatrix, estimate); if (estimate) { const osPrefix = matrixIsIos(deviceMatrix) ? 'iOS' : 'API'; const rows = ui.fields([ diff --git a/src/utils/device-matrix.ts b/src/utils/device-matrix.ts index 8e9e5f9..7f171ae 100644 --- a/src/utils/device-matrix.ts +++ b/src/utils/device-matrix.ts @@ -4,6 +4,34 @@ import { } from '../types/domain/device.types.js'; import { CliError } from './cli.js'; +/** + * Refuse to submit a device matrix to an API that cannot honour it. + * + * The estimate endpoint is only called when a matrix was actually requested, so + * a null estimate (the gateway maps 404/405 to null) means the API predates the + * feature. That API would **silently strip** the unknown `deviceMatrix` field — + * its ValidationPipe runs `whitelist: true, forbidNonWhitelisted: false` — and + * run every flow on a single default device, exiting 0. The user would believe + * they had tested N devices when they tested one: the exact silent + * under-testing the device matrix exists to prevent. Fail loudly instead. + * + * @throws CliError when a matrix was requested but the API does not support it. + */ +export function assertMatrixSupported( + deviceMatrix: DeviceMatrixConfig[], + estimate: unknown | null, +): void { + if (deviceMatrix.length === 0 || estimate) return; + + throw new CliError( + 'This DeviceCloud API does not support device matrices, so ' + + '--ios-device-matrix / --android-device-matrix cannot be honoured. ' + + 'Submitting anyway would silently run every flow on a single default ' + + 'device and report success. Upgrade the API, or drop the matrix flags ' + + 'and use --ios-device / --android-device for a single-device run.', + ); +} + /** * Parse repeated `--ios-device-matrix :` and * `--android-device-matrix :[:play]` flags into an explicit device diff --git a/test/unit/device-matrix.test.ts b/test/unit/device-matrix.test.ts index 8a4cb11..20476a1 100644 --- a/test/unit/device-matrix.test.ts +++ b/test/unit/device-matrix.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { CliError } from '../../src/utils/cli.js'; import { + assertMatrixSupported, matrixIsIos, parseDeviceMatrix, } from '../../src/utils/device-matrix.js'; @@ -59,3 +60,36 @@ describe('parseDeviceMatrix', () => { ); }); }); + +/** + * An API that predates the matrix silently STRIPS the unknown deviceMatrix + * field (its ValidationPipe is whitelist:true / forbidNonWhitelisted:false) and + * runs every flow on one default device, exiting 0. Submitting into that is the + * worst outcome the feature can produce, so it must be refused, not tolerated. + */ +describe('assertMatrixSupported', () => { + const matrix = [{ iOSDevice: 'iphone-16', iOSVersion: '18' }]; + + it('throws when a matrix was requested but the API has no estimate endpoint', () => { + expect(() => assertMatrixSupported(matrix, null)).to.throw( + CliError, + /does not support device matrices/i, + ); + }); + + it('explains that submitting anyway would silently run a single device', () => { + expect(() => assertMatrixSupported(matrix, null)).to.throw( + /silently run every flow on a single default device/i, + ); + }); + + it('passes when the API returned an estimate', () => { + expect(() => + assertMatrixSupported(matrix, { cellCount: 2, totalCost: 0.16 }), + ).to.not.throw(); + }); + + it('is a no-op when no matrix was requested (legacy single-device runs)', () => { + expect(() => assertMatrixSupported([], null)).to.not.throw(); + }); +}); From 1e7a1eb40b2cc8eec5c0dbfc6c517a696c459033 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:45:53 +0100 Subject: [PATCH 34/78] chore(dev): release 5.2.0-beta.3 (#81) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index 134036a..b3b3a94 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.2.0-beta.2" + ".": "5.2.0-beta.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0970d79..6965596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [5.2.0-beta.3](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.2...v5.2.0-beta.3) (2026-07-13) + + +### Bug Fixes + +* **cloud:** refuse a device matrix on an API that cannot honour it ([#80](https://github.com/devicecloud-dev/dcd-cli/issues/80)) ([5560158](https://github.com/devicecloud-dev/dcd-cli/commit/5560158389440735354be963098a2704a8610040)) + ## [5.2.0-beta.2](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.1...v5.2.0-beta.2) (2026-07-13) diff --git a/package.json b/package.json index a5a063e..47e3b7c 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.2.0-beta.2", + "version": "5.2.0-beta.3", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From cc5ee4d4595fd4b3e6abd9e3ad0d50b2a7db84ca Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Thu, 23 Jul 2026 14:20:44 +0100 Subject: [PATCH 35/78] fix(deps): resolve pnpm audit failures in transitive dependencies (#89) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(deps): resolve pnpm audit failures in transitive dependencies - bump hono 4.12.26 -> 4.12.31 and fast-uri 3.1.2 -> 3.1.4 (lockfile only) - update brace-expansion overrides: the pins added for the previous advisory (1.1.13 / 5.0.6) are exactly the versions flagged by GHSA-3jxr-9vmj-r5cp; now pin 1.1.16 / 5.0.7 - ignore GHSA-frvp-7c67-39w9 (@hono/node-server serve-static path traversal on Windows): the fix is a major (2.0.5) outside the MCP SDK's ^1.19.9 range, and dcd-mcp is stdio-only and never serves static files. Remove the exemption when the SDK adopts 2.x. Co-Authored-By: Claude Fable 5 * ci: temporarily show full claude-review output to surface the hidden error Debug commit — will be reverted once the failure cause is captured. Co-Authored-By: Claude Fable 5 * Revert "ci: temporarily show full claude-review output to surface the hidden error" This reverts commit c61643dd628143698e817eb344aa32b96fb1ee30. --------- Co-authored-by: Claude Fable 5 --- package.json | 9 +++++++-- pnpm-lock.yaml | 46 +++++++++++++++++++++++----------------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 47e3b7c..8d24f9c 100644 --- a/package.json +++ b/package.json @@ -103,13 +103,18 @@ "ajv@<7.0.0": "6.14.0", "diff@>=4.0.0 <6.0.0": "4.0.4", "diff@>=6.0.0": "8.0.3", - "brace-expansion@<1.1.13": "1.1.13", + "brace-expansion@<1.1.16": "1.1.16", "brace-expansion@>=2.0.0 <2.0.3": "2.0.3", - "brace-expansion@>=4.0.0 <5.0.6": "5.0.6", + "brace-expansion@>=3.0.0 <5.0.7": "5.0.7", "ws@>=8.0.0 <8.21.0": "8.21.0", "esbuild@<0.28.1": ">=0.28.1", "micromatch>picomatch": "^2.3.2", "tinyglobby>picomatch": "^4.0.4" + }, + "auditConfig": { + "ignoreGhsas": [ + "GHSA-frvp-7c67-39w9" + ] } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43ccd66..7904438 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,9 @@ overrides: ajv@<7.0.0: 6.14.0 diff@>=4.0.0 <6.0.0: 4.0.4 diff@>=6.0.0: 8.0.3 - brace-expansion@<1.1.13: 1.1.13 + brace-expansion@<1.1.16: 1.1.16 brace-expansion@>=2.0.0 <2.0.3: 2.0.3 - brace-expansion@>=4.0.0 <5.0.6: 5.0.6 + brace-expansion@>=3.0.0 <5.0.7: 5.0.7 ws@>=8.0.0 <8.21.0: 8.21.0 esbuild@<0.28.1: '>=0.28.1' micromatch>picomatch: ^2.3.2 @@ -632,11 +632,11 @@ packages: resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} engines: {node: '>= 5.10.0'} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -1037,8 +1037,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-uri@3.1.4: + resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -1200,8 +1200,8 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hono@4.12.26: - resolution: {integrity: sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==} + hono@4.12.31: + resolution: {integrity: sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==} engines: {node: '>=16.9.0'} http-errors@2.0.1: @@ -2264,9 +2264,9 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@hono/node-server@1.19.14(hono@4.12.26)': + '@hono/node-server@1.19.14(hono@4.12.31)': dependencies: - hono: 4.12.26 + hono: 4.12.31 '@humanfs/core@0.19.2': dependencies: @@ -2299,7 +2299,7 @@ snapshots: '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.26) + '@hono/node-server': 1.19.14(hono@4.12.31) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -2309,7 +2309,7 @@ snapshots: eventsource-parser: 3.1.0 express: 5.2.1 express-rate-limit: 8.5.2(express@5.2.1) - hono: 4.12.26 + hono: 4.12.31 jose: 6.2.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -2513,7 +2513,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 + fast-uri: 3.1.4 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -2613,12 +2613,12 @@ snapshots: dependencies: big-integer: 1.6.52 - brace-expansion@1.1.13: + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.6: + brace-expansion@5.0.7: dependencies: balanced-match: 4.0.4 @@ -3153,7 +3153,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.2: {} + fast-uri@3.1.4: {} fast-wrap-ansi@0.2.2: dependencies: @@ -3318,7 +3318,7 @@ snapshots: he@1.2.0: {} - hono@4.12.26: {} + hono@4.12.31: {} http-errors@2.0.1: dependencies: @@ -3589,19 +3589,19 @@ snapshots: minimatch@10.2.3: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 5.0.7 minimatch@10.2.5: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 5.0.7 minimatch@3.1.4: dependencies: - brace-expansion: 1.1.13 + brace-expansion: 1.1.16 minimatch@9.0.7: dependencies: - brace-expansion: 5.0.6 + brace-expansion: 5.0.7 minimist@1.2.8: {} From cbac88bcac3b350918cb0bbd0d4f68446a3662b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:21:05 +0100 Subject: [PATCH 36/78] chore: bump eslint-plugin-unicorn from 69.0.0 to 71.1.0 (#85) Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 69.0.0 to 71.1.0. - [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases) - [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v69.0.0...v71.1.0) --- updated-dependencies: - dependency-name: eslint-plugin-unicorn dependency-version: 71.1.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 163 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 127 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 8d24f9c..d5d48ce 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-unicorn": "^69.0.0", + "eslint-plugin-unicorn": "^71.1.0", "husky": "^9.1.7", "mocha": "^11.7.6", "prettier": "^3.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7904438..4628be1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,8 +109,8 @@ importers: specifier: ^2.32.0 version: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0) eslint-plugin-unicorn: - specifier: ^69.0.0 - version: 69.0.0(eslint@10.6.0) + specifier: ^71.1.0 + version: 71.1.0(eslint@10.6.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -135,10 +135,6 @@ importers: packages: - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - '@clack/core@1.4.3': resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} engines: {node: '>= 20.12.0'} @@ -615,8 +611,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.40: - resolution: {integrity: sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==} + baseline-browser-mapping@2.10.43: + resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -646,8 +642,8 @@ packages: browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - browserslist@4.28.4: - resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==} + browserslist@4.28.6: + resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -682,8 +678,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001799: - resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + caniuse-lite@1.0.30001805: + resolution: {integrity: sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -744,6 +740,10 @@ packages: resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} engines: {node: '>=18'} + convert-hrtime@5.0.0: + resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} + engines: {node: '>=12'} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -840,8 +840,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.381: - resolution: {integrity: sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg==} + electron-to-chromium@1.5.389: + resolution: {integrity: sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -944,8 +944,8 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-unicorn@69.0.0: - resolution: {integrity: sha512-ZN/KtHr9hQ6AOByANSNJpsDbo/+Nn+EyQ6blK4w+dcmS/xpYkqLLfrUc+NA/wOK6vF5uEUvhn8my5B/3sruB9g==} + eslint-plugin-unicorn@71.1.0: + resolution: {integrity: sha512-dn3YmR3qLLUeYyo/os3ubZ7UHQJ1WbBAgC9cIhnLTyMj9J6kivuc2U1fCmYetLexUlTDVYtBqhjSj/VaebTe6Q==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -1110,6 +1110,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function-timeout@1.0.2: + resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} + engines: {node: '>=18'} + function.prototype.name@1.2.0: resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} engines: {node: '>= 0.4'} @@ -1221,6 +1225,10 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + identifier-regex@1.1.0: + resolution: {integrity: sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA==} + engines: {node: '>=18'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1316,6 +1324,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-identifier@1.1.0: + resolution: {integrity: sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==} + engines: {node: '>=18'} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -1476,6 +1488,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + make-asynchronous@1.1.0: + resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} + engines: {node: '>=18'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1559,8 +1575,8 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} - node-releases@2.0.50: - resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} engines: {node: '>=18'} node-stream-zip@1.15.0: @@ -1618,6 +1634,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-event@6.0.1: + resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} + engines: {node: '>=16.17'} + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -1630,6 +1650,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1719,6 +1743,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quote-js-string@0.1.0: + resolution: {integrity: sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA==} + engines: {node: '>=22'} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -1758,6 +1786,10 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + reserved-identifiers@1.2.0: + resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} + engines: {node: '>=18'} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -1942,6 +1974,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + super-regex@1.1.0: + resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1958,6 +1994,10 @@ packages: resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==} engines: {node: '>=18'} + time-span@5.1.0: + resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} + engines: {node: '>=12'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -1995,6 +2035,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + type-is@2.1.0: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} @@ -2054,6 +2098,9 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2138,8 +2185,6 @@ packages: snapshots: - '@babel/helper-validator-identifier@7.29.7': {} - '@clack/core@1.4.3': dependencies: fast-wrap-ansi: 0.2.2 @@ -2591,7 +2636,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.40: {} + baseline-browser-mapping@2.10.43: {} big-integer@1.6.52: {} @@ -2628,13 +2673,13 @@ snapshots: browser-stdout@1.3.1: {} - browserslist@4.28.4: + browserslist@4.28.6: dependencies: - baseline-browser-mapping: 2.10.40 - caniuse-lite: 1.0.30001799 - electron-to-chromium: 1.5.381 - node-releases: 2.0.50 - update-browserslist-db: 1.2.3(browserslist@4.28.4) + baseline-browser-mapping: 2.10.43 + caniuse-lite: 1.0.30001805 + electron-to-chromium: 1.5.389 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.6) buffer-crc32@1.0.0: {} @@ -2663,7 +2708,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001799: {} + caniuse-lite@1.0.30001805: {} chai@6.2.2: {} @@ -2711,13 +2756,15 @@ snapshots: content-type@2.0.0: {} + convert-hrtime@5.0.0: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} core-js-compat@3.49.0: dependencies: - browserslist: 4.28.4 + browserslist: 4.28.6 cors@2.8.6: dependencies: @@ -2804,7 +2851,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.381: {} + electron-to-chromium@1.5.389: {} emoji-regex@8.0.0: {} @@ -2993,11 +3040,10 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-unicorn@69.0.0(eslint@10.6.0): + eslint-plugin-unicorn@71.1.0(eslint@10.6.0): dependencies: - '@babel/helper-validator-identifier': 7.29.7 '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) - browserslist: 4.28.4 + browserslist: 4.28.6 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.49.0 @@ -3007,9 +3053,11 @@ snapshots: globals: 17.7.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 - jsesc: 3.1.0 + is-identifier: 1.1.0 pluralize: 8.0.0 + quote-js-string: 0.1.0 regjsparser: 0.13.2 + reserved-identifiers: 1.2.0 semver: 7.8.5 strip-indent: 4.1.1 @@ -3220,6 +3268,8 @@ snapshots: function-bind@1.1.2: {} + function-timeout@1.0.2: {} + function.prototype.name@1.2.0: dependencies: call-bind: 1.0.9 @@ -3336,6 +3386,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + identifier-regex@1.1.0: + dependencies: + reserved-identifiers: 1.2.0 + ignore@5.3.2: {} ignore@7.0.6: {} @@ -3426,6 +3480,11 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-identifier@1.1.0: + dependencies: + identifier-regex: 1.1.0 + super-regex: 1.1.0 + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -3568,6 +3627,12 @@ snapshots: lru-cache@10.4.3: {} + make-asynchronous@1.1.0: + dependencies: + p-event: 6.0.1 + type-fest: 4.41.0 + web-worker: 1.5.0 + math-intrinsics@1.1.0: {} media-typer@1.1.0: {} @@ -3656,7 +3721,7 @@ snapshots: node-forge@1.4.0: {} - node-releases@2.0.50: {} + node-releases@2.0.51: {} node-stream-zip@1.15.0: {} @@ -3729,6 +3794,10 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-event@6.0.1: + dependencies: + p-timeout: 6.1.4 + p-finally@1.0.0: {} p-limit@3.1.0: @@ -3739,6 +3808,8 @@ snapshots: dependencies: p-limit: 3.1.0 + p-timeout@6.1.4: {} + package-json-from-dist@1.0.1: {} parseurl@1.3.3: {} @@ -3805,6 +3876,8 @@ snapshots: queue-microtask@1.2.3: {} + quote-js-string@0.1.0: {} + range-parser@1.2.1: {} raw-body@3.0.2: @@ -3850,6 +3923,8 @@ snapshots: requires-port@1.0.0: {} + reserved-identifiers@1.2.0: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -4079,6 +4154,12 @@ snapshots: strip-json-comments@3.1.1: {} + super-regex@1.1.0: + dependencies: + function-timeout: 1.0.2 + make-asynchronous: 1.1.0 + time-span: 5.1.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4097,6 +4178,10 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + time-span@5.1.0: + dependencies: + convert-hrtime: 5.0.0 + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.5) @@ -4141,6 +4226,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@4.41.0: {} + type-is@2.1.0: dependencies: content-type: 2.0.0 @@ -4204,9 +4291,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.4): + update-browserslist-db@1.2.3(browserslist@4.28.6): dependencies: - browserslist: 4.28.4 + browserslist: 4.28.6 escalade: 3.2.0 picocolors: 1.1.1 @@ -4221,6 +4308,8 @@ snapshots: vary@1.1.2: {} + web-worker@1.5.0: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 From 5730af72d96063f603d08b699e69cc376d35988c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:24:37 +0100 Subject: [PATCH 37/78] ci: bump actions/setup-node from 6 to 7 in the actions group (#86) Bumps the actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node). Updates `actions/setup-node` from 6 to 7 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tom Riglar --- .github/workflows/cli-ci.yml | 2 +- .github/workflows/npm-publish.yml | 2 +- .github/workflows/release-binaries.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 961eb4a..09d8cd5 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -74,7 +74,7 @@ jobs: run_install: false - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: '22' cache: 'pnpm' diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 8ff9b19..22481f7 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -31,7 +31,7 @@ jobs: with: run_install: false - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version: '22.x' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index ba1b227..bfa1bf6 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -31,7 +31,7 @@ jobs: with: run_install: false - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version: '22.x' cache: 'pnpm' From f333be97d848e2cd40cbff124584de5af4f89a88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:26:51 +0100 Subject: [PATCH 38/78] deps: bump the minor-and-patch group across 1 directory with 7 updates (#92) Bumps the minor-and-patch group with 7 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@supabase/supabase-js](https://github.com/supabase/supabase-js/tree/HEAD/packages/core/supabase-js) | `2.110.2` | `2.110.8` | | [node-stream-zip](https://github.com/antelle/node-stream-zip) | `1.15.0` | `1.16.0` | | [tar](https://github.com/isaacs/node-tar) | `7.5.19` | `7.5.21` | | [eslint](https://github.com/eslint/eslint) | `10.6.0` | `10.7.0` | | [prettier](https://github.com/prettier/prettier) | `3.9.5` | `3.9.6` | | [tsx](https://github.com/privatenumber/tsx) | `4.23.0` | `4.23.1` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.63.0` | `8.65.0` | Updates `@supabase/supabase-js` from 2.110.2 to 2.110.8 - [Release notes](https://github.com/supabase/supabase-js/releases) - [Changelog](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/CHANGELOG.md) - [Commits](https://github.com/supabase/supabase-js/commits/v2.110.8/packages/core/supabase-js) Updates `node-stream-zip` from 1.15.0 to 1.16.0 - [Changelog](https://github.com/antelle/node-stream-zip/blob/master/release-notes.md) - [Commits](https://github.com/antelle/node-stream-zip/compare/1.15.0...1.16.0) Updates `tar` from 7.5.19 to 7.5.21 - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v7.5.19...v7.5.21) Updates `eslint` from 10.6.0 to 10.7.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.6.0...v10.7.0) Updates `prettier` from 3.9.5 to 3.9.6 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.9.5...3.9.6) Updates `tsx` from 4.23.0 to 4.23.1 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.23.0...v4.23.1) Updates `typescript-eslint` from 8.63.0 to 8.65.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@supabase/supabase-js" dependency-version: 2.110.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: node-stream-zip dependency-version: 1.16.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: tar dependency-version: 7.5.21 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: eslint dependency-version: 10.7.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: prettier dependency-version: 3.9.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: tsx dependency-version: 4.23.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: typescript-eslint dependency-version: 8.65.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 295 +++++++++++++++++++++++++------------------------ 1 file changed, 153 insertions(+), 142 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4628be1..2679e78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,7 +43,7 @@ importers: version: 1.29.0(zod@4.4.3) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.110.2 + version: 2.110.8 bplist-parser: specifier: ^0.3.2 version: 0.3.2 @@ -61,13 +61,13 @@ importers: version: 1.2.1 node-stream-zip: specifier: ^1.15.0 - version: 1.15.0 + version: 1.16.0 plist: specifier: ^5.0.0 version: 5.0.0 tar: specifier: ^7.5.16 - version: 7.5.19 + version: 7.5.21 tus-js-client: specifier: ^4.3.1 version: 4.3.1 @@ -80,7 +80,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.6.0) + version: 10.0.1(eslint@10.7.0) '@types/chai': specifier: ^5.2.3 version: 5.2.3 @@ -101,16 +101,16 @@ importers: version: 6.2.2 eslint: specifier: ^10.5.0 - version: 10.6.0 + version: 10.7.0 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.6.0) + version: 10.1.8(eslint@10.7.0) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0) + version: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0) eslint-plugin-unicorn: specifier: ^71.1.0 - version: 71.1.0(eslint@10.6.0) + version: 71.1.0(eslint@10.7.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -119,19 +119,19 @@ importers: version: 11.7.6 prettier: specifier: ^3.8.4 - version: 3.9.5 + version: 3.9.6 shx: specifier: ^0.4.0 version: 0.4.0 tsx: specifier: ^4.22.4 - version: 4.23.0 + version: 4.23.1 typescript: specifier: ^6.0.3 version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.63.0(eslint@10.6.0)(typescript@6.0.3) + version: 8.65.0(eslint@10.7.0)(typescript@6.0.3) packages: @@ -299,6 +299,12 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -401,31 +407,31 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@supabase/auth-js@2.110.2': - resolution: {integrity: sha512-Qj7a6EDP+AMMQFWqGv+qFa8r6re//dk+qQI5bA0KK+PZmnI3JPu97TDeNt6SMiQ2FkklP79hP2yDFYSnA989OA==} + '@supabase/auth-js@2.110.8': + resolution: {integrity: sha512-TQ5neTUDX2C2WmyYa03yGhLMkhdE/SkHXtK8/qxO/APUy3rsymsJCBP48p4jcN6iO2G0ow6RRexQd2mX+dSyJg==} engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.110.2': - resolution: {integrity: sha512-ZjjqrXpxM9/rE+eAtZxiK45EWy9EBoJQ322Q5Y75LccYQNh212neHTgXP/o4MIzmH0LNXT8UzvTZtQOfOzyoeQ==} + '@supabase/functions-js@2.110.8': + resolution: {integrity: sha512-5yB9TLYzvv2oSQxwb0gamEvIAsuH66pVt7AM/pz03S7wN6ehD34GNgbShrccetqPedXQSz7e/1hAJ9NeEhoZVg==} engines: {node: '>=22.0.0'} - '@supabase/phoenix@0.4.4': - resolution: {integrity: sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==} + '@supabase/phoenix@0.4.5': + resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} - '@supabase/postgrest-js@2.110.2': - resolution: {integrity: sha512-++LBmcIMwCtgO4tISQUmo9+2xkRwHQqS8ZKMCnhXLe9P8k8YQRXuMoh/RiSzQSoev8gqet0W7yOboW0cUxnt0Q==} + '@supabase/postgrest-js@2.110.8': + resolution: {integrity: sha512-QeRROxl1PpOZw5Jzi7BwdN9icsycMrLlCCvsjS0hYLW+nZoaT46zdagz/glJirj8jHF4jSd5Jyipuae2cBClCw==} engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.110.2': - resolution: {integrity: sha512-z3jTOTPgyn6E3r6dVOOQ10He4yAMB2czjFw7xVdX3s16MHElna5rY1gVaePs0NIo6xvtMYbtmOXlFaFt/ePLpg==} + '@supabase/realtime-js@2.110.8': + resolution: {integrity: sha512-mwX7ituX6O31fLf+0g65rpLlNxqgnMaPltPsQwzox6jfmbfVl3tCxXrfr3HEsQcCRjpjuJG1+A0vFzP1yVjKHA==} engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.110.2': - resolution: {integrity: sha512-EhsRSwSnmQefKJsAxoRUZ0hvHr92ECM8DDGAKR5z0HdoJx4heI60PjHUTruVNZxKX6XeobLGDyLud020Bw1iwg==} + '@supabase/storage-js@2.110.8': + resolution: {integrity: sha512-CcfhkZFBLxsthgUabZKxwfsoXdrikIGsL3LsGoV3FZTqCMx/s1y49taT4jT/oya5+1IuB0sFFHw6pF0o0iJniQ==} engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.110.2': - resolution: {integrity: sha512-r9q9w4ZQ6mOjh36aqUNFSisBF611vzpO8JphBESr2Q1SWvmGFQeI7Jq7Y+PaNMZ6Zszz+S2yTlJStCpnaMSnQg==} + '@supabase/supabase-js@2.110.8': + resolution: {integrity: sha512-E5qzoe74zhJRv4wRcbO9eMYzeQDb/+h6c603pL8shcxLGBjTKsIF7XXj05IcNj23TLDgJN1WkMw7mwAPyu5dZg==} engines: {node: '>=22.0.0'} '@types/chai@5.2.3': @@ -458,63 +464,63 @@ packages: '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} - '@typescript-eslint/eslint-plugin@8.63.0': - resolution: {integrity: sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==} + '@typescript-eslint/eslint-plugin@8.65.0': + resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.63.0 + '@typescript-eslint/parser': ^8.65.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.63.0': - resolution: {integrity: sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==} + '@typescript-eslint/parser@8.65.0': + resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.63.0': - resolution: {integrity: sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==} + '@typescript-eslint/project-service@8.65.0': + resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.63.0': - resolution: {integrity: sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==} + '@typescript-eslint/scope-manager@8.65.0': + resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.63.0': - resolution: {integrity: sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==} + '@typescript-eslint/tsconfig-utils@8.65.0': + resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.63.0': - resolution: {integrity: sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==} + '@typescript-eslint/type-utils@8.65.0': + resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.63.0': - resolution: {integrity: sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==} + '@typescript-eslint/types@8.65.0': + resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.63.0': - resolution: {integrity: sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==} + '@typescript-eslint/typescript-estree@8.65.0': + resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.63.0': - resolution: {integrity: sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==} + '@typescript-eslint/utils@8.65.0': + resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.63.0': - resolution: {integrity: sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==} + '@typescript-eslint/visitor-keys@8.65.0': + resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.10': @@ -962,8 +968,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.6.0: - resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} + eslint@10.7.0: + resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1083,8 +1089,8 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.3: + resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} @@ -1579,8 +1585,8 @@ packages: resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} engines: {node: '>=18'} - node-stream-zip@1.15.0: - resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} + node-stream-zip@1.16.0: + resolution: {integrity: sha512-ObaRrRoR8T68wF6suxHd7R4XQNamij6ZQHrwG7Dx1D2zeHcDNLsIOBcWrIwtDm7AsCXBguaPHgXhcjxDa2szrg==} engines: {node: '>=0.12.0'} npm-run-path@2.0.2: @@ -1714,8 +1720,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.9.5: - resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} engines: {node: '>=14'} hasBin: true @@ -1990,8 +1996,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tar@7.5.19: - resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==} + tar@7.5.21: + resolution: {integrity: sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==} engines: {node: '>=18'} time-span@5.1.0: @@ -2022,8 +2028,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.23.0: - resolution: {integrity: sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==} + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -2059,8 +2065,8 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.63.0: - resolution: {integrity: sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==} + typescript-eslint@8.65.0: + resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2275,9 +2281,14 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0)': + '@eslint-community/eslint-utils@4.10.1(eslint@10.7.0)': + dependencies: + eslint: 10.7.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0)': dependencies: - eslint: 10.6.0 + eslint: 10.7.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2298,9 +2309,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.6.0)': + '@eslint/js@10.0.1(eslint@10.7.0)': optionalDependencies: - eslint: 10.6.0 + eslint: 10.7.0 '@eslint/object-schema@3.0.5': {} @@ -2381,37 +2392,37 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@supabase/auth-js@2.110.2': + '@supabase/auth-js@2.110.8': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.110.2': + '@supabase/functions-js@2.110.8': dependencies: tslib: 2.8.1 - '@supabase/phoenix@0.4.4': {} + '@supabase/phoenix@0.4.5': {} - '@supabase/postgrest-js@2.110.2': + '@supabase/postgrest-js@2.110.8': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.110.2': + '@supabase/realtime-js@2.110.8': dependencies: - '@supabase/phoenix': 0.4.4 + '@supabase/phoenix': 0.4.5 tslib: 2.8.1 - '@supabase/storage-js@2.110.2': + '@supabase/storage-js@2.110.8': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.110.2': + '@supabase/supabase-js@2.110.8': dependencies: - '@supabase/auth-js': 2.110.2 - '@supabase/functions-js': 2.110.2 - '@supabase/postgrest-js': 2.110.2 - '@supabase/realtime-js': 2.110.2 - '@supabase/storage-js': 2.110.2 + '@supabase/auth-js': 2.110.8 + '@supabase/functions-js': 2.110.8 + '@supabase/postgrest-js': 2.110.8 + '@supabase/realtime-js': 2.110.8 + '@supabase/storage-js': 2.110.8 '@types/chai@5.2.3': dependencies: @@ -2440,15 +2451,15 @@ snapshots: dependencies: '@types/node': 26.1.1 - '@typescript-eslint/eslint-plugin@8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/type-utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.63.0 - eslint: 10.6.0 + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/type-utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.65.0 + eslint: 10.7.0 ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2456,56 +2467,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.6.0 + eslint: 10.7.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.63.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.65.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) - '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) + '@typescript-eslint/types': 8.65.0 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.63.0': + '@typescript-eslint/scope-manager@8.65.0': dependencies: - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 - '@typescript-eslint/tsconfig-utils@8.63.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.65.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.63.0(eslint@10.6.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.65.0(eslint@10.7.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.6.0 + eslint: 10.7.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.63.0': {} + '@typescript-eslint/types@8.65.0': {} - '@typescript-eslint/typescript-estree@8.63.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.65.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.63.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/project-service': 8.65.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.3 semver: 7.8.5 @@ -2515,20 +2526,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.63.0(eslint@10.6.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.65.0(eslint@10.7.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - eslint: 10.6.0 + '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + eslint: 10.7.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.63.0': + '@typescript-eslint/visitor-keys@8.65.0': dependencies: - '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/types': 8.65.0 eslint-visitor-keys: 5.0.1 '@xmldom/xmldom@0.9.10': {} @@ -2989,9 +3000,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.6.0): + eslint-config-prettier@10.1.8(eslint@10.7.0): dependencies: - eslint: 10.6.0 + eslint: 10.7.0 eslint-import-resolver-node@0.3.10: dependencies: @@ -3001,17 +3012,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.6.0): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.7.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) - eslint: 10.6.0 + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + eslint: 10.7.0 eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3020,9 +3031,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.6.0 + eslint: 10.7.0 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.6.0) + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.7.0) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -3034,21 +3045,21 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-unicorn@71.1.0(eslint@10.6.0): + eslint-plugin-unicorn@71.1.0(eslint@10.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0) browserslist: 4.28.6 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.49.0 detect-indent: 7.0.2 - eslint: 10.6.0 + eslint: 10.7.0 find-up-simple: 1.0.1 globals: 17.7.0 indent-string: 5.0.0 @@ -3072,9 +3083,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.6.0: + eslint@10.7.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -3243,12 +3254,12 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.3 keyv: 4.5.4 flat@5.0.2: {} - flatted@3.4.2: {} + flatted@3.4.3: {} for-each@0.3.5: dependencies: @@ -3723,7 +3734,7 @@ snapshots: node-releases@2.0.51: {} - node-stream-zip@1.15.0: {} + node-stream-zip@1.16.0: {} npm-run-path@2.0.2: dependencies: @@ -3848,7 +3859,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.9.5: {} + prettier@3.9.6: {} proper-lockfile@4.1.2: dependencies: @@ -4170,7 +4181,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tar@7.5.19: + tar@7.5.21: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -4206,7 +4217,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.23.0: + tsx@4.23.1: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -4267,13 +4278,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.63.0(eslint@10.6.0)(typescript@6.0.3): + typescript-eslint@8.65.0(eslint@10.7.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) - eslint: 10.6.0 + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + eslint: 10.7.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color From a4f11ff8d771de213b5acdb4f6fcf40f665e5d7b Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Mon, 27 Jul 2026 09:42:01 +0100 Subject: [PATCH 39/78] deps: patch js-yaml and brace-expansion DoS advisories (#95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deps: patch js-yaml and brace-expansion DoS advisories `pnpm audit --audit-level moderate` was failing CI on three high advisories: - js-yaml 5.2.1 -> 5.2.2 (GHSA-pm4m-ph32-ghv5): exponential parsing time in flow collections. This one is reachable at runtime — js-yaml parses user Maestro flow files. Bumped the declared range and added a transitive override guard alongside the existing 3.x/4.x entries. - brace-expansion 5.0.7 -> 5.0.8 (GHSA-mh99-v99m-4gvg): unbounded expansion length causing an uncatchable OOM crash. Widened the existing override range. - brace-expansion 1.1.16, reached via eslint-plugin-import -> minimatch@3. Upstream published no 1.x or 2.x backport (maintenance-v1 is still 1.1.16, maintenance-v2 2.1.2 — both inside the vulnerable <=5.0.7 range), and 5.x cannot be forced into that path: v5 exports `{ expand }` while minimatch@3 requires a callable default. Overriding minimatch@3 -> 10.x breaks differently, since its CJS build has no callable default for eslint-plugin-import's interop require. So eslint-plugin-import is dropped instead. eslint.config.cjs registered it with no rules enabled — purely so legacy `import/...` disable comments resolve — and exactly one such comment remained (src/methods.ts, itself already a dead directive since neither `import/namespace` nor `new-cap` is enabled). Removing the plugin deletes the whole minimatch@3 -> brace-expansion@1.x subtree and avoids adding the GHSA to `ignoreGhsas`, which would also have silenced the fixable 5.x path. No other dependency resolutions change. The remaining ignored moderate (@hono/node-server, GHSA-frvp-7c67-39w9) is unchanged — the MCP SDK is already at its latest release, so there is no fix to take. Co-Authored-By: Claude Opus 5 (1M context) * ci: scope the gitleaks scan to the branch under test `fetch-depth: 0` fetches refs/heads/* — every branch — and `gitleaks git` scans all reachable commits, not just the checked-out ref. So a branch that legitimately commits a high-entropy value together with its own .gitleaks.toml allowlist entry fails the secret-scan of every OTHER branch, because those are judged against the allowlist at their own tip. That is currently a hard deadlock. feat/binary-envelope-encryption (#94) pins two KEK public keys and allowlists them in the same branch, so any PR branched from dev fails secret-scan on commit bfa2b4c, while #94 itself cannot merge until it picks up this branch's `pnpm audit` fix. --log-opts=HEAD limits the scan to commits reachable from what is checked out: the full history of the branch, or of the PR merge commit (base + PR commits). Coverage is unchanged in the way that matters — every commit of the branch under test is still scanned, each branch is fully scanned by its own PR, and pushes to dev/production scan their full history. Verified with the pinned gitleaks 8.30.1: scoped to this branch, 61 commits, no leaks; scoped to feat/binary-envelope-encryption with this branch's allowlist, the same 2 findings still fire — so the scoping narrows which commits are in scope without weakening detection. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 5 (1M context) --- .github/workflows/cli-ci.yml | 11 +- eslint.config.cjs | 13 +- package.json | 6 +- pnpm-lock.yaml | 1094 +--------------------------------- src/methods.ts | 2 +- 5 files changed, 36 insertions(+), 1090 deletions(-) diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 09d8cd5..46f035b 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -31,7 +31,16 @@ jobs: run: | curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ | tar -xz gitleaks - ./gitleaks git . --redact --verbose --no-banner + # --log-opts=HEAD scopes the scan to commits reachable from what's + # checked out: the whole history of this branch (or of the PR merge + # commit, i.e. base + PR commits), but NOT unrelated branches. + # `fetch-depth: 0` fetches refs/heads/* — every branch — and gitleaks + # otherwise scans all of them, so an open branch that legitimately + # commits a high-entropy value plus its own .gitleaks.toml allowlist + # would fail every OTHER branch's scan, which is judged against the + # allowlist at its own tip. Each branch is still fully scanned by its + # own PR, and pushes to dev/production scan their full history. + ./gitleaks git . --redact --verbose --no-banner --log-opts=HEAD lint-and-test: runs-on: ubuntu-latest diff --git a/eslint.config.cjs b/eslint.config.cjs index 7bbdba9..aded632 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -6,11 +6,9 @@ const js = require('@eslint/js'); const tseslint = require('typescript-eslint'); -// These plugins ship as ESM with a `default` export under CJS interop. +// This plugin ships as ESM with a `default` export under CJS interop. const unicornPlugin = require('eslint-plugin-unicorn').default ?? require('eslint-plugin-unicorn'); -const importPlugin = - require('eslint-plugin-import').default ?? require('eslint-plugin-import'); module.exports = tseslint.config( { @@ -23,12 +21,15 @@ module.exports = tseslint.config( js.configs.recommended, ...tseslint.configs.recommended, { - // `unicorn` and `import` are only registered so legacy + // `unicorn` is only registered so legacy // `// eslint-disable-next-line unicorn/...` comments scattered through - // the source resolve. We don't enable any rules from them. + // the source resolve. We don't enable any rules from it. + // `eslint-plugin-import` used to be registered here for the same reason, + // but it was dropped: it dragged in minimatch@3 -> brace-expansion@1.x, + // which has an unpatched DoS advisory (GHSA-mh99-v99m-4gvg, no 1.x + // backport) and failed `pnpm audit`. No rules from it were ever enabled. plugins: { unicorn: unicornPlugin, - import: importPlugin, }, languageOptions: { ecmaVersion: 2022, diff --git a/package.json b/package.json index d5d48ce..d1370cd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "bplist-parser": "^0.3.2", "chalk": "^5.6.2", "citty": "^0.2.2", - "js-yaml": "^5.0.0", + "js-yaml": "^5.2.2", "node-apk": "^1.2.1", "node-stream-zip": "^1.15.0", "plist": "^5.0.0", @@ -31,7 +31,6 @@ "chai": "^6.2.2", "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-import": "^2.32.0", "eslint-plugin-unicorn": "^71.1.0", "husky": "^9.1.7", "mocha": "^11.7.6", @@ -87,6 +86,7 @@ "overrides": { "js-yaml@<3.14.2": ">=3.14.2", "js-yaml@>=4.0.0 <4.2.0": ">=4.2.0", + "js-yaml@>=5.0.0 <5.2.2": ">=5.2.2", "tar@<7.5.16": ">=7.5.16", "@isaacs/brace-expansion": ">=5.0.1", "fast-xml-parser": ">=5.5.7", @@ -105,7 +105,7 @@ "diff@>=6.0.0": "8.0.3", "brace-expansion@<1.1.16": "1.1.16", "brace-expansion@>=2.0.0 <2.0.3": "2.0.3", - "brace-expansion@>=3.0.0 <5.0.7": "5.0.7", + "brace-expansion@>=3.0.0 <5.0.8": "5.0.8", "ws@>=8.0.0 <8.21.0": "8.21.0", "esbuild@<0.28.1": ">=0.28.1", "micromatch>picomatch": "^2.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2679e78..de367bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ settings: overrides: js-yaml@<3.14.2: '>=3.14.2' js-yaml@>=4.0.0 <4.2.0: '>=4.2.0' + js-yaml@>=5.0.0 <5.2.2: '>=5.2.2' tar@<7.5.16: '>=7.5.16' '@isaacs/brace-expansion': '>=5.0.1' fast-xml-parser: '>=5.5.7' @@ -25,7 +26,7 @@ overrides: diff@>=6.0.0: 8.0.3 brace-expansion@<1.1.16: 1.1.16 brace-expansion@>=2.0.0 <2.0.3: 2.0.3 - brace-expansion@>=3.0.0 <5.0.7: 5.0.7 + brace-expansion@>=3.0.0 <5.0.8: 5.0.8 ws@>=8.0.0 <8.21.0: 8.21.0 esbuild@<0.28.1: '>=0.28.1' micromatch>picomatch: ^2.3.2 @@ -54,8 +55,8 @@ importers: specifier: ^0.2.2 version: 0.2.2 js-yaml: - specifier: ^5.0.0 - version: 5.2.1 + specifier: ^5.2.2 + version: 5.2.2 node-apk: specifier: ^1.2.1 version: 1.2.1 @@ -105,9 +106,6 @@ importers: eslint-config-prettier: specifier: ^10.1.8 version: 10.1.8(eslint@10.7.0) - eslint-plugin-import: - specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0) eslint-plugin-unicorn: specifier: ^71.1.0 version: 71.1.0(eslint@10.7.0) @@ -404,9 +402,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rtsao/scc@1.1.0': - resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@supabase/auth-js@2.110.8': resolution: {integrity: sha512-TQ5neTUDX2C2WmyYa03yGhLMkhdE/SkHXtK8/qxO/APUy3rsymsJCBP48p4jcN6iO2G0ow6RRexQd2mX+dSyJg==} engines: {node: '>=22.0.0'} @@ -452,9 +447,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} @@ -574,45 +566,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} - - array.prototype.findlastindex@1.2.6: - resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -634,12 +591,9 @@ packages: resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} engines: {node: '>= 5.10.0'} - brace-expansion@1.1.16: - resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} - - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.8: + resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -672,10 +626,6 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} - engines: {node: '>= 0.4'} - call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -731,9 +681,6 @@ packages: combine-errors@3.0.3: resolution: {integrity: sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@1.1.0: resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} @@ -776,26 +723,6 @@ packages: custom-error-instance@2.1.1: resolution: {integrity: sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==} - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -812,14 +739,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -832,10 +751,6 @@ packages: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -862,14 +777,6 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - es-abstract-get@1.0.0: - resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} - engines: {node: '>= 0.4'} - - es-abstract@1.24.2: - resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} - engines: {node: '>= 0.4'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -882,18 +789,6 @@ packages: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.1: - resolution: {integrity: sha512-CxN9N56HYfd2m/acc/NOFrZQsN9kU4eh+2kk6A707Kz1krH8tKmfrs5RnftB8WNX80T0NS7vSQsDOlg23diR2g==} - engines: {node: '>= 0.4'} - esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -916,40 +811,6 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-import-resolver-node@0.3.10: - resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} - - eslint-module-utils@2.13.0: - resolution: {integrity: sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-import@2.32.0: - resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint-plugin-unicorn@71.1.0: resolution: {integrity: sha512-dn3YmR3qLLUeYyo/os3ubZ7UHQJ1WbBAgC9cIhnLTyMj9J6kivuc2U1fCmYetLexUlTDVYtBqhjSj/VaebTe6Q==} engines: {node: '>=22'} @@ -1092,10 +953,6 @@ packages: flatted@3.4.3: resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1120,17 +977,6 @@ packages: resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} engines: {node: '>=18'} - function.prototype.name@1.2.0: - resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1147,10 +993,6 @@ packages: resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} engines: {node: '>=6'} - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1168,10 +1010,6 @@ packages: resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} engines: {node: '>=18'} - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1179,29 +1017,14 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - hasown@2.0.4: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} @@ -1254,10 +1077,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - interpret@1.4.0: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} @@ -1270,62 +1089,22 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - is-builtin-module@5.0.0: resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} engines: {node: '>=18.20'} - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - is-core-module@2.16.2: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-document.all@1.0.0: - resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} - engines: {node: '>= 0.4'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1334,18 +1113,6 @@ packages: resolution: {integrity: sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==} engines: {node: '>=18'} - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1361,18 +1128,6 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - is-stream@1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} engines: {node: '>=0.10.0'} @@ -1381,37 +1136,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1424,8 +1152,8 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-yaml@5.2.1: - resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==} + js-yaml@5.2.2: + resolution: {integrity: sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==} hasBin: true jsesc@3.1.0: @@ -1448,10 +1176,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1534,9 +1258,6 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.4: - resolution: {integrity: sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==} - minimatch@9.0.7: resolution: {integrity: sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==} engines: {node: '>=16 || 14 >=14.17'} @@ -1573,10 +1294,6 @@ packages: node-apk@1.2.1: resolution: {integrity: sha512-I0TY1x5m1pkFzjYdaGrrAu/Mh9qnnk2/BoMAU6bvBxTTD/oNQyTWbu3LTdONgV2rnLHf23jJ00Y/VV4BzZ6YXQ==} - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} - engines: {node: '>= 0.4'} - node-forge@1.4.0: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} @@ -1601,30 +1318,6 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -1636,10 +1329,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - p-event@6.0.1: resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} engines: {node: '>=16.17'} @@ -1712,10 +1401,6 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1769,14 +1454,6 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - regjsparser@0.13.2: resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} hasBin: true @@ -1801,11 +1478,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - resolve@2.0.0-next.7: - resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} - engines: {node: '>= 0.4'} - hasBin: true - retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -1821,18 +1493,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-array-concat@1.1.4: - resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} - engines: {node: '>=0.4'} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1840,10 +1500,6 @@ packages: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -1861,18 +1517,6 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -1932,10 +1576,6 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1944,18 +1584,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.trim@1.2.11: - resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.10: - resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1964,10 +1592,6 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - strip-eof@1.0.0: resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} engines: {node: '>=0.10.0'} @@ -2022,9 +1646,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2049,22 +1670,6 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.8: - resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} - engines: {node: '>= 0.4'} - typescript-eslint@8.65.0: resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2077,10 +1682,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} @@ -2107,22 +1708,6 @@ packages: web-worker@1.5.0: resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.22: - resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} - engines: {node: '>= 0.4'} - which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -2390,8 +1975,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rtsao/scc@1.1.0': {} - '@supabase/auth-js@2.110.8': dependencies: tslib: 2.8.1 @@ -2439,8 +2022,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/json5@0.0.29': {} - '@types/mocha@10.0.10': {} '@types/node@26.1.1': @@ -2585,66 +2166,8 @@ snapshots: argparse@2.0.1: {} - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - array-includes@3.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 - - array.prototype.findlastindex@1.2.6: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - es-shim-unscopables: 1.1.0 - - array.prototype.flat@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - array.prototype.flatmap@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - assertion-error@2.0.1: {} - async-function@1.0.0: {} - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - balanced-match@1.0.2: {} - balanced-match@4.0.4: {} baseline-browser-mapping@2.10.43: {} @@ -2669,12 +2192,7 @@ snapshots: dependencies: big-integer: 1.6.52 - brace-expansion@1.1.16: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@5.0.7: + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.4 @@ -2705,13 +2223,6 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.9: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2759,8 +2270,6 @@ snapshots: custom-error-instance: 2.1.1 lodash.uniqby: 4.5.0 - concat-map@0.0.1: {} - content-disposition@1.1.0: {} content-type@1.0.5: {} @@ -2798,28 +2307,6 @@ snapshots: custom-error-instance@2.1.1: {} - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - debug@3.2.7: - dependencies: - ms: 2.1.3 - debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 @@ -2830,28 +2317,12 @@ snapshots: deep-is@0.1.4: {} - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - depd@2.0.0: {} detect-indent@7.0.2: {} diff@8.0.3: {} - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2874,70 +2345,6 @@ snapshots: dependencies: once: 1.4.0 - es-abstract-get@1.0.0: - dependencies: - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - is-callable: 1.2.7 - object-inspect: 1.13.4 - - es-abstract@1.24.2: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.1 - function.prototype.name: 1.2.0 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.4 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.11 - string.prototype.trimend: 1.0.10 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.8 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.22 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -2946,25 +2353,6 @@ snapshots: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.4 - - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.4 - - es-to-primitive@1.3.1: - dependencies: - es-abstract-get: 1.0.0 - es-errors: 1.3.0 - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -3004,53 +2392,6 @@ snapshots: dependencies: eslint: 10.7.0 - eslint-import-resolver-node@0.3.10: - dependencies: - debug: 3.2.7 - is-core-module: 2.16.2 - resolve: 2.0.0-next.7 - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.7.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) - eslint: 10.7.0 - eslint-import-resolver-node: 0.3.10 - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 10.7.0 - eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.7.0) - hasown: 2.0.4 - is-core-module: 2.16.2 - is-glob: 4.0.3 - minimatch: 3.1.4 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.10 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-unicorn@71.1.0(eslint@10.7.0): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0) @@ -3261,10 +2602,6 @@ snapshots: flatted@3.4.3: {} - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -3281,22 +2618,6 @@ snapshots: function-timeout@1.0.2: {} - function.prototype.name@1.2.0: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - es-define-property: 1.0.1 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - hasown: 2.0.4 - is-callable: 1.2.7 - is-document.all: 1.0.0 - - functions-have-names@1.2.3: {} - - generator-function@2.0.1: {} - get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -3321,12 +2642,6 @@ snapshots: dependencies: pump: 3.0.4 - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3346,33 +2661,14 @@ snapshots: globals@17.7.0: {} - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - gopd@1.2.0: {} graceful-fs@4.2.11: {} - has-bigints@1.1.0: {} - has-flag@4.0.0: {} - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - has-symbols@1.1.0: {} - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -3411,82 +2707,24 @@ snapshots: inherits@2.0.4: {} - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.4 - side-channel: 1.1.1 - interpret@1.4.0: {} ip-address@10.2.0: {} ipaddr.js@1.9.1: {} - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - is-builtin-module@5.0.0: dependencies: builtin-modules: 5.3.0 - is-callable@1.2.7: {} - is-core-module@2.16.2: dependencies: hasown: 2.0.4 - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-document.all@1.0.0: - dependencies: - call-bound: 1.0.4 - is-extglob@2.1.1: {} - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -3496,15 +2734,6 @@ snapshots: identifier-regex: 1.1.0 super-regex: 1.1.0 - is-map@2.0.3: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - is-number@7.0.0: {} is-path-inside@3.0.3: {} @@ -3513,53 +2742,12 @@ snapshots: is-promise@4.0.0: {} - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.4 - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - is-stream@1.1.0: {} is-stream@2.0.1: {} - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.22 - is-unicode-supported@0.1.0: {} - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - isarray@2.0.5: {} - isexe@2.0.0: {} jackspeak@3.4.3: @@ -3572,7 +2760,7 @@ snapshots: js-base64@3.7.8: {} - js-yaml@5.2.1: + js-yaml@5.2.2: dependencies: argparse: 2.0.1 @@ -3588,10 +2776,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json5@1.0.2: - dependencies: - minimist: 1.2.8 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3665,19 +2849,15 @@ snapshots: minimatch@10.2.3: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.8 minimatch@10.2.5: dependencies: - brace-expansion: 5.0.7 - - minimatch@3.1.4: - dependencies: - brace-expansion: 1.1.16 + brace-expansion: 5.0.8 minimatch@9.0.7: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.8 minimist@1.2.8: {} @@ -3698,7 +2878,7 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 5.2.1 + js-yaml: 5.2.2 log-symbols: 4.1.0 minimatch: 9.0.7 ms: 2.1.3 @@ -3723,13 +2903,6 @@ snapshots: dependencies: node-forge: 1.4.0 - node-exports-info@1.6.0: - dependencies: - array.prototype.flatmap: 1.3.3 - es-errors: 1.3.0 - object.entries: 1.1.9 - semver: 6.3.1 - node-forge@1.4.0: {} node-releases@2.0.51: {} @@ -3744,44 +2917,6 @@ snapshots: object-inspect@1.13.4: {} - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - object.entries@1.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 - - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - - object.values@1.2.1: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -3799,12 +2934,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - p-event@6.0.1: dependencies: p-timeout: 6.1.4 @@ -3855,8 +2984,6 @@ snapshots: pluralize@8.0.0: {} - possible-typed-array-names@1.1.0: {} - prelude-ls@1.2.1: {} prettier@3.9.6: {} @@ -3904,26 +3031,6 @@ snapshots: dependencies: resolve: 1.22.12 - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 - - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 - regjsparser@0.13.2: dependencies: jsesc: 3.1.0 @@ -3943,15 +3050,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.7: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - node-exports-info: 1.6.0 - object-keys: 1.1.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - retry@0.12.0: {} reusify@1.1.0: {} @@ -3970,31 +3068,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 - safe-array-concat@1.1.4: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 - - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - safer-buffer@2.1.2: {} semver@5.7.2: {} - semver@6.3.1: {} - semver@7.8.5: {} send@1.2.1: @@ -4024,28 +3101,6 @@ snapshots: transitivePeerDependencies: - supports-color - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - setprototypeof@1.2.0: {} shebang-command@1.2.0: @@ -4108,11 +3163,6 @@ snapshots: statuses@2.0.2: {} - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4125,30 +3175,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.2.0 - string.prototype.trim@1.2.11: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 - has-property-descriptors: 1.0.2 - safe-regex-test: 1.1.0 - - string.prototype.trimend@1.0.10: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4157,8 +3183,6 @@ snapshots: dependencies: ansi-regex: 6.2.2 - strip-bom@3.0.0: {} - strip-eof@1.0.0: {} strip-indent@4.1.1: {} @@ -4208,13 +3232,6 @@ snapshots: dependencies: typescript: 6.0.3 - tsconfig-paths@3.15.0: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - tslib@2.8.1: {} tsx@4.23.1: @@ -4245,39 +3262,6 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typed-array-byte-length@1.0.3: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - - typed-array-byte-offset@1.0.4: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 - - typed-array-length@1.0.8: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 - typescript-eslint@8.65.0(eslint@10.7.0)(typescript@6.0.3): dependencies: '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0)(typescript@6.0.3) @@ -4291,13 +3275,6 @@ snapshots: typescript@6.0.3: {} - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 - undici-types@8.3.0: {} unpipe@1.0.0: {} @@ -4321,47 +3298,6 @@ snapshots: web-worker@1.5.0: {} - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 - - which-builtin-type@1.2.1: - dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.2.0 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.22 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 - - which-typed-array@1.1.22: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - which@1.3.1: dependencies: isexe: 2.0.0 diff --git a/src/methods.ts b/src/methods.ts index ebc7a0f..fdd8734 100644 --- a/src/methods.ts +++ b/src/methods.ts @@ -94,7 +94,7 @@ export const compressFilesFromRelativePath = async ( }; export const verifyAppZip = async (zipPath: string) => { - // eslint-disable-next-line import/namespace, new-cap + // eslint-disable-next-line new-cap const zip = await new StreamZip.async({ file: zipPath, storeEntries: true, From 1b29d2d5ec58d391c04dc29ea2e4c989a0a66e0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:05:45 +0100 Subject: [PATCH 40/78] deps: bump chalk from 5.6.2 to 6.0.0 (#98) Bumps [chalk](https://github.com/chalk/chalk) from 5.6.2 to 6.0.0. - [Release notes](https://github.com/chalk/chalk/releases) - [Commits](https://github.com/chalk/chalk/compare/v5.6.2...v6.0.0) --- updated-dependencies: - dependency-name: chalk dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d1370cd..c8234d6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@supabase/supabase-js": "^2.108.2", "bplist-parser": "^0.3.2", - "chalk": "^5.6.2", + "chalk": "^6.0.0", "citty": "^0.2.2", "js-yaml": "^5.2.2", "node-apk": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de367bf..4eacb26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,8 +49,8 @@ importers: specifier: ^0.3.2 version: 0.3.2 chalk: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^6.0.0 + version: 6.0.0 citty: specifier: ^0.2.2 version: 0.2.2 @@ -645,9 +645,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chalk@6.0.0: + resolution: {integrity: sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==} + engines: {node: '>=22'} change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} @@ -2239,7 +2239,7 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} + chalk@6.0.0: {} change-case@5.4.4: {} From 7d85979600f84a61a25a18ab961dccddfd742d91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:06:21 +0100 Subject: [PATCH 41/78] chore: bump eslint-plugin-unicorn from 71.1.0 to 72.0.0 (#97) Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 71.1.0 to 72.0.0. - [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases) - [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v71.1.0...v72.0.0) --- updated-dependencies: - dependency-name: eslint-plugin-unicorn dependency-version: 72.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 107 ++++++++++++++++++++++++++++++------------------- 2 files changed, 67 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index c8234d6..c46c51c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "chai": "^6.2.2", "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-unicorn": "^71.1.0", + "eslint-plugin-unicorn": "^72.0.0", "husky": "^9.1.7", "mocha": "^11.7.6", "prettier": "^3.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eacb26..af6d007 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,8 +107,8 @@ importers: specifier: ^10.1.8 version: 10.1.8(eslint@10.7.0) eslint-plugin-unicorn: - specifier: ^71.1.0 - version: 71.1.0(eslint@10.7.0) + specifier: ^72.0.0 + version: 72.0.0(eslint@10.7.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -303,12 +303,6 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.2': resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -325,6 +319,10 @@ packages: resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/css-tree@4.0.5': + resolution: {integrity: sha512-iPmijIAq4hlIJB86PYmY/fcZORHtjphSqICDbwuw32A/JmkhZQ/K/6TjHE03zqf3n5yABpVcbRAMG8Mi9ojy8g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/js@10.0.1': resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -574,8 +572,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.43: - resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + baseline-browser-mapping@2.11.4: + resolution: {integrity: sha512-s4+sLr9mZ/CyqeRritFeYV/Zx73OAtmaHn6kkBS1XRoJn1hrg3xIDUcpicAEX68tkcIN0iBCgti31C8zxtkhsQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -602,8 +600,8 @@ packages: browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - browserslist@4.28.6: - resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + browserslist@4.28.7: + resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -634,8 +632,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001805: - resolution: {integrity: sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==} + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -761,8 +759,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.389: - resolution: {integrity: sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==} + electron-to-chromium@1.5.396: + resolution: {integrity: sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -777,6 +775,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -811,8 +813,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-unicorn@71.1.0: - resolution: {integrity: sha512-dn3YmR3qLLUeYyo/os3ubZ7UHQJ1WbBAgC9cIhnLTyMj9J6kivuc2U1fCmYetLexUlTDVYtBqhjSj/VaebTe6Q==} + eslint-plugin-unicorn@72.0.0: + resolution: {integrity: sha512-hqO6ksoOHO+ZhdseTuKRVQbx9U7PRO/cv8qAR1mctwzdVO2hYud8uS9luAhp43RJgziYgHAph8eHyipT8GL0ng==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -1006,8 +1008,8 @@ packages: 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 hasBin: true - globals@17.7.0: - resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + globals@17.8.0: + resolution: {integrity: sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==} engines: {node: '>=18'} gopd@1.2.0: @@ -1226,6 +1228,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.29.0: + resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -1572,6 +1577,10 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -1747,6 +1756,11 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -1871,11 +1885,6 @@ snapshots: eslint: 10.7.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0)': - dependencies: - eslint: 10.7.0 - eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.2': {} '@eslint/config-array@0.23.5': @@ -1894,6 +1903,11 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@eslint/css-tree@4.0.5': + dependencies: + mdn-data: 2.29.0 + source-map-js: 1.2.1 + '@eslint/js@10.0.1(eslint@10.7.0)': optionalDependencies: eslint: 10.7.0 @@ -2170,7 +2184,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.43: {} + baseline-browser-mapping@2.11.4: {} big-integer@1.6.52: {} @@ -2202,13 +2216,13 @@ snapshots: browser-stdout@1.3.1: {} - browserslist@4.28.6: + browserslist@4.28.7: dependencies: - baseline-browser-mapping: 2.10.43 - caniuse-lite: 1.0.30001805 - electron-to-chromium: 1.5.389 + baseline-browser-mapping: 2.11.4 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.396 node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.6) + update-browserslist-db: 1.2.3(browserslist@4.28.7) buffer-crc32@1.0.0: {} @@ -2230,7 +2244,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001805: {} + caniuse-lite@1.0.30001806: {} chai@6.2.2: {} @@ -2284,7 +2298,7 @@ snapshots: core-js-compat@3.49.0: dependencies: - browserslist: 4.28.6 + browserslist: 4.28.7 cors@2.8.6: dependencies: @@ -2333,7 +2347,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.389: {} + electron-to-chromium@1.5.396: {} emoji-regex@8.0.0: {} @@ -2345,6 +2359,8 @@ snapshots: dependencies: once: 1.4.0 + entities@4.5.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -2392,17 +2408,19 @@ snapshots: dependencies: eslint: 10.7.0 - eslint-plugin-unicorn@71.1.0(eslint@10.7.0): + eslint-plugin-unicorn@72.0.0(eslint@10.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0) - browserslist: 4.28.6 + '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0) + '@eslint/css-tree': 4.0.5 + browserslist: 4.28.7 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.49.0 detect-indent: 7.0.2 + entities: 4.5.0 eslint: 10.7.0 find-up-simple: 1.0.1 - globals: 17.7.0 + globals: 17.8.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 is-identifier: 1.1.0 @@ -2412,6 +2430,7 @@ snapshots: reserved-identifiers: 1.2.0 semver: 7.8.5 strip-indent: 4.1.1 + yaml: 2.9.0 eslint-scope@9.1.2: dependencies: @@ -2659,7 +2678,7 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - globals@17.7.0: {} + globals@17.8.0: {} gopd@1.2.0: {} @@ -2830,6 +2849,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.29.0: {} + media-typer@1.1.0: {} merge-descriptors@2.0.0: {} @@ -3161,6 +3182,8 @@ snapshots: sisteransi@1.0.5: {} + source-map-js@1.2.1: {} + statuses@2.0.2: {} string-width@4.2.3: @@ -3279,9 +3302,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.6): + update-browserslist-db@1.2.3(browserslist@4.28.7): dependencies: - browserslist: 4.28.6 + browserslist: 4.28.7 escalade: 3.2.0 picocolors: 1.1.1 @@ -3330,6 +3353,8 @@ snapshots: yallist@5.0.0: {} + yaml@2.9.0: {} + yargs-parser@21.1.1: {} yargs-unparser@2.0.0: From f6fcfafa4ce5935decd15952107cd3ac1ebaad7b Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Thu, 30 Jul 2026 10:20:14 +0100 Subject: [PATCH 42/78] feat(artifacts): prefer server-assembled bundle delivery for downloads (#93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(artifacts): prefer server-assembled bundle delivery for downloads Artifact and HTML-report downloads now try a bundle-delivery path first: the API returns a signed manifest plus a URL to a delivery service that streams the ZIP straight from storage, so large downloads don't flow through the API. The client relays the signed { manifest, sig } to that URL (no auth header — the manifest is the signed token) and streams the result to disk. Falls back to the existing inline download automatically when bundle delivery isn't offered (501) or anything about the path doesn't pan out, so behaviour is unchanged on older deployments. Applies to artifacts and the HTML report; junit and allure keep using their existing endpoints. Adds a shared tryBundleDownload helper in the API gateway and unit tests covering the bundle path, the no-auth relay, the 501 fallback, and the HTML report. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(artifacts): fall back to inline download on bundle stream failure streamResponseToFile threw past both tryBundleDownload call sites on a mid-stream failure or null body, escaping the inline fallback and leaving a partial file. Wrap it to return false like every other failure path. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- src/gateways/api-gateway.ts | 107 +++++++++++++++ test/unit/report-download.service.test.ts | 153 ++++++++++++++++++++++ 2 files changed, 260 insertions(+) diff --git a/src/gateways/api-gateway.ts b/src/gateways/api-gateway.ts index 48eeb18..c06eaa8 100644 --- a/src/gateways/api-gateway.ts +++ b/src/gateways/api-gateway.ts @@ -179,6 +179,84 @@ export const ApiGateway = { ); }, + /** + * Prefer server-assembled bundle delivery. The API returns a signed manifest + * plus a URL to a delivery service that streams the ZIP straight from + * storage, so large downloads don't flow through the API itself. The client + * just relays the signed `{ manifest, sig }` to that URL — no auth header, + * because the manifest is already signed. + * + * Returns true once the bundle has been streamed to disk. Returns false when + * the bundle path is unavailable or anything about it doesn't pan out — a + * `501` (deployment doesn't offer it), a non-OK manifest response, a body + * that isn't a manifest, or a delivery-service error — so the caller can fall + * back to the inline download endpoint. Definitive errors (e.g. not found) + * surface through that inline path instead. + */ + async tryBundleDownload( + baseUrl: string, + auth: AuthContext, + manifestEndpoint: string, + destinationPath: string, + operation: string, + ): Promise { + let manifestRes: Response; + try { + manifestRes = await fetch(`${baseUrl}${manifestEndpoint}`, { + headers: { ...auth.headers }, + method: 'GET', + }); + } catch { + return false; + } + + // 501 => this deployment has no bundle delivery; any other non-OK => let + // the inline path re-request and surface the real error. + if (!manifestRes.ok) { + return false; + } + + let bundle: { + bundleUrl?: string; + manifest?: string; + sig?: string; + }; + try { + bundle = (await manifestRes.json()) as typeof bundle; + } catch { + return false; + } + if (!bundle?.bundleUrl || !bundle?.manifest || !bundle?.sig) { + return false; + } + + let zipRes: Response; + try { + zipRes = await fetch(bundle.bundleUrl, { + body: JSON.stringify({ manifest: bundle.manifest, sig: bundle.sig }), + // No auth header: the manifest is signed and is the access token. + headers: { 'content-type': 'application/json' }, + method: 'POST', + }); + } catch { + return false; + } + if (!zipRes.ok) { + return false; + } + + // A mid-stream failure (dropped/truncated connection) or a null body on an + // otherwise-OK response throws here — fall back to the inline path rather + // than let it escape past the caller's fallback. The inline path re-opens + // the destination with flags: 'w', truncating any partial file left behind. + try { + await this.streamResponseToFile(zipRes, destinationPath, operation); + } catch { + return false; + } + return true; + }, + async checkForExistingUpload( baseUrl: string, auth: AuthContext, @@ -219,6 +297,19 @@ export const ApiGateway = { results: 'ALL' | 'FAILED', artifactsPath: string = './artifacts.zip', ) { + // Prefer bundle delivery; fall back to the inline download below. + if ( + await this.tryBundleDownload( + baseUrl, + auth, + `/results/${uploadId}/artifacts-bundle?results=${results}`, + artifactsPath, + 'Failed to download artifacts', + ) + ) { + return; + } + try { const res = await fetch(`${baseUrl}/results/${uploadId}/download`, { body: JSON.stringify({ results }), @@ -677,6 +768,22 @@ export const ApiGateway = { const finalReportPath = reportPath || path.resolve(process.cwd(), defaultFilename); const url = `${baseUrl}${endpoint}`; + // The HTML report is a ZIP bundle; prefer bundle delivery when available. + // (junit is a single small file and allure has its own endpoint — both stay + // on the inline path.) + if ( + reportType === 'html' && + (await this.tryBundleDownload( + baseUrl, + auth, + `/results/${uploadId}/report-bundle`, + finalReportPath, + errorPrefix, + )) + ) { + return; + } + try { // Make the download request const res = await fetch(url, { diff --git a/test/unit/report-download.service.test.ts b/test/unit/report-download.service.test.ts index 2ed8566..8c75f13 100644 --- a/test/unit/report-download.service.test.ts +++ b/test/unit/report-download.service.test.ts @@ -303,4 +303,157 @@ describe('ReportDownloadService', () => { expect(warnings.join(' ')).to.match(/failed to download allure/i); }); }); + + // ------------------------------------------------------------------------- + // bundle delivery + // ------------------------------------------------------------------------- + + describe('bundle delivery', () => { + const BASE = { + auth: TEST_AUTH, + apiUrl: 'https://api.example.com', + uploadId: 'run-42', + }; + + let calls: Array<{ + headers: Record; + method: string; + url: string; + }>; + + /** + * Route fetch by URL: a `*-bundle` endpoint returns a signed manifest, and + * the manifest's `bundleUrl` streams the ZIP. Records every call so the + * flow (manifest GET, then bundle POST, no inline call) can be asserted. + */ + function mockBundleFetch( + opts: { manifestStatus?: number; zipBody?: string } = {}, + ) { + const { manifestStatus = 200, zipBody = 'bundle-zip' } = opts; + const encoder = new TextEncoder(); + const stream = (s: string) => + new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(s)); + controller.close(); + }, + }); + + const impl = async ( + input: URL | string, + init?: RequestInit, + ): Promise => { + const url = input.toString(); + calls.push({ + headers: Object.fromEntries( + Object.entries((init?.headers as Record) ?? {}), + ), + method: (init?.method ?? 'GET').toUpperCase(), + url, + }); + + if (url.includes('artifacts-bundle') || url.includes('report-bundle')) { + return new Response( + stream( + JSON.stringify({ + bundleUrl: 'https://cdn.example.com/bundle', + entryCount: 3, + filename: 'artifacts-all.zip', + manifest: '{"version":1}', + sig: 'SIG', + }), + ), + { + headers: { 'content-type': 'application/json' }, + status: manifestStatus, + }, + ); + } + if (url === 'https://cdn.example.com/bundle') { + return new Response(stream(zipBody), { status: 200 }); + } + return new Response(stream('inline-zip'), { status: 200 }); + }; + globalThis.fetch = impl as typeof fetch; + } + + beforeEach(() => { + calls = []; + }); + + it('streams from the bundle URL and skips the inline endpoint', async () => { + mockBundleFetch(); + const outPath = path.join(tempDir, 'bundle.zip'); + + await service.downloadArtifacts({ + ...BASE, + artifactsPath: outPath, + downloadType: 'ALL', + }); + + expect(calls[0]).to.include({ + method: 'GET', + url: 'https://api.example.com/results/run-42/artifacts-bundle?results=ALL', + }); + expect(calls[1]).to.include({ + method: 'POST', + url: 'https://cdn.example.com/bundle', + }); + expect(calls.some((c) => c.url.endsWith('/download'))).to.be.false; + expect(fs.readFileSync(outPath, 'utf8')).to.equal('bundle-zip'); + }); + + it('does not send the auth header to the (pre-signed) bundle URL', async () => { + mockBundleFetch(); + + await service.downloadArtifacts({ + ...BASE, + artifactsPath: path.join(tempDir, 'bundle-noauth.zip'), + downloadType: 'ALL', + }); + + const bundlePost = calls.find( + (c) => c.url === 'https://cdn.example.com/bundle', + ); + expect(bundlePost).to.not.be.undefined; + expect(bundlePost!.headers['x-app-api-key']).to.be.undefined; + }); + + it('falls back to the inline download when unavailable (501)', async () => { + mockBundleFetch({ manifestStatus: 501 }); + const outPath = path.join(tempDir, 'bundle-fallback.zip'); + + await service.downloadArtifacts({ + ...BASE, + artifactsPath: outPath, + downloadType: 'ALL', + }); + + expect( + calls.some((c) => c.url.endsWith('/artifacts-bundle?results=ALL')), + ).to.be.true; + expect( + calls.some((c) => c.method === 'POST' && c.url.endsWith('/download')), + ).to.be.true; + expect(fs.readFileSync(outPath, 'utf8')).to.equal('inline-zip'); + }); + + it('uses bundle delivery for the html report', async () => { + mockBundleFetch(); + + await service.downloadReports({ + ...BASE, + htmlPath: path.join(tempDir, 'report-bundle.zip'), + reportType: 'html', + }); + + expect(calls[0].url).to.equal( + 'https://api.example.com/results/run-42/report-bundle', + ); + expect(calls[1]).to.include({ + method: 'POST', + url: 'https://cdn.example.com/bundle', + }); + }); + }); }); From 47b9d905daa49d4d8491b1a8d710545e05822bb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:20:46 +0100 Subject: [PATCH 43/78] deps: bump the minor-and-patch group across 1 directory with 5 updates (#100) Bumps the minor-and-patch group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) | `1.29.0` | `1.30.0` | | [@supabase/supabase-js](https://github.com/supabase/supabase-js/tree/HEAD/packages/core/supabase-js) | `2.110.8` | `2.111.0` | | [tar](https://github.com/isaacs/node-tar) | `7.5.21` | `7.5.22` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.1` | `26.1.2` | | [eslint](https://github.com/eslint/eslint) | `10.7.0` | `10.8.0` | Updates `@modelcontextprotocol/sdk` from 1.29.0 to 1.30.0 - [Release notes](https://github.com/modelcontextprotocol/typescript-sdk/releases) - [Commits](https://github.com/modelcontextprotocol/typescript-sdk/compare/v1.29.0...1.30.0) Updates `@supabase/supabase-js` from 2.110.8 to 2.111.0 - [Release notes](https://github.com/supabase/supabase-js/releases) - [Changelog](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/CHANGELOG.md) - [Commits](https://github.com/supabase/supabase-js/commits/v2.111.0/packages/core/supabase-js) Updates `tar` from 7.5.21 to 7.5.22 - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v7.5.21...v7.5.22) Updates `@types/node` from 26.1.1 to 26.1.2 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `eslint` from 10.7.0 to 10.8.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.7.0...v10.8.0) --- updated-dependencies: - dependency-name: "@modelcontextprotocol/sdk" dependency-version: 1.30.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: "@supabase/supabase-js" dependency-version: 2.111.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: tar dependency-version: 7.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: "@types/node" dependency-version: 26.1.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: eslint dependency-version: 10.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 264 +++++++++++++++++++++++++------------------------ 1 file changed, 134 insertions(+), 130 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af6d007..3d9a3f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,10 +41,10 @@ importers: version: 1.7.0 '@modelcontextprotocol/sdk': specifier: ^1.29.0 - version: 1.29.0(zod@4.4.3) + version: 1.30.0(zod@4.4.3) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.110.8 + version: 2.111.0 bplist-parser: specifier: ^0.3.2 version: 0.3.2 @@ -68,7 +68,7 @@ importers: version: 5.0.0 tar: specifier: ^7.5.16 - version: 7.5.21 + version: 7.5.22 tus-js-client: specifier: ^4.3.1 version: 4.3.1 @@ -81,7 +81,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.7.0) + version: 10.0.1(eslint@10.8.0) '@types/chai': specifier: ^5.2.3 version: 5.2.3 @@ -93,7 +93,7 @@ importers: version: 10.0.10 '@types/node': specifier: ^26.0.0 - version: 26.1.1 + version: 26.1.2 '@types/yazl': specifier: ^3.3.1 version: 3.3.1 @@ -102,13 +102,13 @@ importers: version: 6.2.2 eslint: specifier: ^10.5.0 - version: 10.7.0 + version: 10.8.0 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.7.0) + version: 10.1.8(eslint@10.8.0) eslint-plugin-unicorn: specifier: ^72.0.0 - version: 72.0.0(eslint@10.7.0) + version: 72.0.0(eslint@10.8.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -129,7 +129,7 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.65.0(eslint@10.7.0)(typescript@6.0.3) + version: 8.65.0(eslint@10.8.0)(typescript@6.0.3) packages: @@ -311,8 +311,8 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.6.0': - resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': @@ -340,9 +340,9 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@hono/node-server@1.19.14': - resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} - engines: {node: '>=18.14.1'} + '@hono/node-server@2.0.12': + resolution: {integrity: sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==} + engines: {node: '>=20'} peerDependencies: hono: ^4 @@ -374,8 +374,8 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@modelcontextprotocol/sdk@1.29.0': - resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + '@modelcontextprotocol/sdk@1.30.0': + resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -400,31 +400,31 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@supabase/auth-js@2.110.8': - resolution: {integrity: sha512-TQ5neTUDX2C2WmyYa03yGhLMkhdE/SkHXtK8/qxO/APUy3rsymsJCBP48p4jcN6iO2G0ow6RRexQd2mX+dSyJg==} + '@supabase/auth-js@2.111.0': + resolution: {integrity: sha512-hbRLgyQZEX0SDyF4LYXpv94qOIQyFATfpT5SIs2V0SisHnisVRkYgiPQWzv80l4S2mO3qof78rmoH9j5zoFsYQ==} engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.110.8': - resolution: {integrity: sha512-5yB9TLYzvv2oSQxwb0gamEvIAsuH66pVt7AM/pz03S7wN6ehD34GNgbShrccetqPedXQSz7e/1hAJ9NeEhoZVg==} + '@supabase/functions-js@2.111.0': + resolution: {integrity: sha512-RW/OCsd6MO592zU8ifzP8/f8XzxxIdpb+Up5XaOtE26Fw+3zTp475WX7+GuuktiD1WF8pFUDe6khUPbMp77RCw==} engines: {node: '>=22.0.0'} '@supabase/phoenix@0.4.5': resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} - '@supabase/postgrest-js@2.110.8': - resolution: {integrity: sha512-QeRROxl1PpOZw5Jzi7BwdN9icsycMrLlCCvsjS0hYLW+nZoaT46zdagz/glJirj8jHF4jSd5Jyipuae2cBClCw==} + '@supabase/postgrest-js@2.111.0': + resolution: {integrity: sha512-pcqeDsnWP0lx9GawduYxNZJHeuTm53O7L0SC8RF8tniV3GWIPY6me6OTdnwzdwNUmNy1dzUVtSyIfE6+OflzPQ==} engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.110.8': - resolution: {integrity: sha512-mwX7ituX6O31fLf+0g65rpLlNxqgnMaPltPsQwzox6jfmbfVl3tCxXrfr3HEsQcCRjpjuJG1+A0vFzP1yVjKHA==} + '@supabase/realtime-js@2.111.0': + resolution: {integrity: sha512-6oRf/vZyRwg8f8GbFSJkrD2w4HAu/yTvyMViHXHS+H5hNJzdXCrUR7cP5oW7daT3YlRnzRPY9LcGSJKZAmfMSg==} engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.110.8': - resolution: {integrity: sha512-CcfhkZFBLxsthgUabZKxwfsoXdrikIGsL3LsGoV3FZTqCMx/s1y49taT4jT/oya5+1IuB0sFFHw6pF0o0iJniQ==} + '@supabase/storage-js@2.111.0': + resolution: {integrity: sha512-UEViNmTzVOxE8dqUA81wls+n9xgmlvSFfhfwo6QxrO4kQOytCYyw3ciYFoi4XoD4Jl95NJ3jnndHN5iIudWzqw==} engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.110.8': - resolution: {integrity: sha512-E5qzoe74zhJRv4wRcbO9eMYzeQDb/+h6c603pL8shcxLGBjTKsIF7XXj05IcNj23TLDgJN1WkMw7mwAPyu5dZg==} + '@supabase/supabase-js@2.111.0': + resolution: {integrity: sha512-9q0/AULthQnWeiDh1vGyjoJZbSY04bu6qHcWit70pqEYn5Kv/dkCPY62Ja1123jEnJbB9Vd2pjY7Kvk/lK3peA==} engines: {node: '>=22.0.0'} '@types/chai@5.2.3': @@ -448,8 +448,8 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@26.1.1': - resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} + '@types/node@26.1.2': + resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} @@ -526,8 +526,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} hasBin: true @@ -831,8 +831,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.7.0: - resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} + eslint@10.8.0: + resolution: {integrity: sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -877,8 +877,8 @@ packages: resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} engines: {node: '>=6'} - express-rate-limit@8.5.2: - resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + express-rate-limit@8.6.1: + resolution: {integrity: sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -1035,8 +1035,8 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hono@4.12.31: - resolution: {integrity: sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==} + hono@4.12.32: + resolution: {integrity: sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==} engines: {node: '>=16.9.0'} http-errors@2.0.1: @@ -1052,8 +1052,8 @@ packages: resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} engines: {node: '>=20.0.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} identifier-regex@1.1.0: @@ -1083,8 +1083,8 @@ packages: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} - ip-address@10.2.0: - resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + ip-address@10.3.1: + resolution: {integrity: sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -1148,8 +1148,8 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jose@6.2.3: - resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + jose@6.2.5: + resolution: {integrity: sha512-2E5L2yRp03FnwreJLJX8/r7mHiZICCf8kG7fAsTWkSQTDAcc46NIZoQLKy+EJ8sPoJlxyS4OQR5H70LjIZZlIQ==} js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -1231,8 +1231,8 @@ packages: mdn-data@2.29.0: resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==} - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} engines: {node: '>= 0.8'} merge-descriptors@2.0.0: @@ -1259,8 +1259,8 @@ packages: resolution: {integrity: sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==} engines: {node: 18 || 20 || >=22} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} minimatch@9.0.7: @@ -1429,8 +1429,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} querystringify@2.2.0: @@ -1443,8 +1443,8 @@ packages: resolution: {integrity: sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA==} engines: {node: '>=22'} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} engines: {node: '>= 0.6'} raw-body@3.0.2: @@ -1629,8 +1629,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tar@7.5.21: - resolution: {integrity: sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==} + tar@7.5.22: + resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==} engines: {node: '>=18'} time-span@5.1.0: @@ -1880,9 +1880,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.7.0)': + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0)': dependencies: - eslint: 10.7.0 + eslint: 10.8.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -1891,11 +1891,11 @@ snapshots: dependencies: '@eslint/object-schema': 3.0.5 debug: 4.4.3(supports-color@8.1.1) - minimatch: 10.2.5 + minimatch: 10.2.6 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.6.0': + '@eslint/config-helpers@0.7.0': dependencies: '@eslint/core': 1.2.1 @@ -1908,9 +1908,9 @@ snapshots: mdn-data: 2.29.0 source-map-js: 1.2.1 - '@eslint/js@10.0.1(eslint@10.7.0)': + '@eslint/js@10.0.1(eslint@10.8.0)': optionalDependencies: - eslint: 10.7.0 + eslint: 10.8.0 '@eslint/object-schema@3.0.5': {} @@ -1919,9 +1919,9 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@hono/node-server@1.19.14(hono@4.12.31)': + '@hono/node-server@2.0.12(hono@4.12.32)': dependencies: - hono: 4.12.31 + hono: 4.12.32 '@humanfs/core@0.19.2': dependencies: @@ -1952,9 +1952,9 @@ snapshots: dependencies: minipass: 7.1.3 - '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + '@modelcontextprotocol/sdk@1.30.0(zod@4.4.3)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.31) + '@hono/node-server': 2.0.12(hono@4.12.32) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -1963,9 +1963,9 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.1.0 express: 5.2.1 - express-rate-limit: 8.5.2(express@5.2.1) - hono: 4.12.31 - jose: 6.2.3 + express-rate-limit: 8.6.1(express@5.2.1) + hono: 4.12.32 + jose: 6.2.5 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 @@ -1989,37 +1989,37 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@supabase/auth-js@2.110.8': + '@supabase/auth-js@2.111.0': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.110.8': + '@supabase/functions-js@2.111.0': dependencies: tslib: 2.8.1 '@supabase/phoenix@0.4.5': {} - '@supabase/postgrest-js@2.110.8': + '@supabase/postgrest-js@2.111.0': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.110.8': + '@supabase/realtime-js@2.111.0': dependencies: '@supabase/phoenix': 0.4.5 tslib: 2.8.1 - '@supabase/storage-js@2.110.8': + '@supabase/storage-js@2.111.0': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.110.8': + '@supabase/supabase-js@2.111.0': dependencies: - '@supabase/auth-js': 2.110.8 - '@supabase/functions-js': 2.110.8 - '@supabase/postgrest-js': 2.110.8 - '@supabase/realtime-js': 2.110.8 - '@supabase/storage-js': 2.110.8 + '@supabase/auth-js': 2.111.0 + '@supabase/functions-js': 2.111.0 + '@supabase/postgrest-js': 2.111.0 + '@supabase/realtime-js': 2.111.0 + '@supabase/storage-js': 2.111.0 '@types/chai@5.2.3': dependencies: @@ -2038,23 +2038,23 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@26.1.1': + '@types/node@26.1.2': dependencies: undici-types: 8.3.0 '@types/yazl@3.3.1': dependencies: - '@types/node': 26.1.1 + '@types/node': 26.1.2 - '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.8.0)(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.65.0 - eslint: 10.7.0 + eslint: 10.8.0 ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2062,14 +2062,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.65.0(eslint@10.8.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.7.0 + eslint: 10.8.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2092,13 +2092,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.65.0(eslint@10.7.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.65.0(eslint@10.8.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.65.0 '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.7.0 + eslint: 10.8.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -2121,13 +2121,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@10.7.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.65.0(eslint@10.8.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) '@typescript-eslint/scope-manager': 8.65.0 '@typescript-eslint/types': 8.65.0 '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) - eslint: 10.7.0 + eslint: 10.8.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2144,11 +2144,11 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-jsx@5.3.2(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 - acorn@8.17.0: {} + acorn@8.18.0: {} ajv-formats@3.0.1(ajv@8.20.0): optionalDependencies: @@ -2194,9 +2194,9 @@ snapshots: content-type: 2.0.0 debug: 4.4.3(supports-color@8.1.1) http-errors: 2.0.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 on-finished: 2.4.1 - qs: 6.15.2 + qs: 6.15.3 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -2404,13 +2404,13 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.7.0): + eslint-config-prettier@10.1.8(eslint@10.8.0): dependencies: - eslint: 10.7.0 + eslint: 10.8.0 - eslint-plugin-unicorn@72.0.0(eslint@10.7.0): + eslint-plugin-unicorn@72.0.0(eslint@10.8.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) '@eslint/css-tree': 4.0.5 browserslist: 4.28.7 change-case: 5.4.4 @@ -2418,7 +2418,7 @@ snapshots: core-js-compat: 3.49.0 detect-indent: 7.0.2 entities: 4.5.0 - eslint: 10.7.0 + eslint: 10.8.0 find-up-simple: 1.0.1 globals: 17.8.0 indent-string: 5.0.0 @@ -2443,12 +2443,12 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.7.0: + eslint@10.8.0: dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 - '@eslint/config-helpers': 0.6.0 + '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.2 '@humanfs/node': 0.16.8 @@ -2472,7 +2472,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.5 + minimatch: 10.2.6 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: @@ -2480,8 +2480,8 @@ snapshots: espree@11.2.0: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 5.0.1 esquery@1.7.0: @@ -2514,10 +2514,13 @@ snapshots: signal-exit: 3.0.7 strip-eof: 1.0.0 - express-rate-limit@8.5.2(express@5.2.1): + express-rate-limit@8.6.1(express@5.2.1): dependencies: + debug: 4.4.3(supports-color@8.1.1) express: 5.2.1 - ip-address: 10.2.0 + ip-address: 10.3.1 + transitivePeerDependencies: + - supports-color express@5.2.1: dependencies: @@ -2541,8 +2544,8 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.2 - range-parser: 1.2.1 + qs: 6.15.3 + range-parser: 1.3.0 router: 2.2.0 send: 1.2.1 serve-static: 2.2.1 @@ -2694,7 +2697,7 @@ snapshots: he@1.2.0: {} - hono@4.12.31: {} + hono@4.12.32: {} http-errors@2.0.1: dependencies: @@ -2708,7 +2711,7 @@ snapshots: iceberg-js@0.8.1: {} - iconv-lite@0.7.2: + iconv-lite@0.7.3: dependencies: safer-buffer: 2.1.2 @@ -2728,7 +2731,7 @@ snapshots: interpret@1.4.0: {} - ip-address@10.2.0: {} + ip-address@10.3.1: {} ipaddr.js@1.9.1: {} @@ -2775,7 +2778,7 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jose@6.2.3: {} + jose@6.2.5: {} js-base64@3.7.8: {} @@ -2851,7 +2854,7 @@ snapshots: mdn-data@2.29.0: {} - media-typer@1.1.0: {} + media-typer@1.1.1: {} merge-descriptors@2.0.0: {} @@ -2872,7 +2875,7 @@ snapshots: dependencies: brace-expansion: 5.0.8 - minimatch@10.2.5: + minimatch@10.2.6: dependencies: brace-expansion: 5.0.8 @@ -3027,8 +3030,9 @@ snapshots: punycode@2.3.1: {} - qs@6.15.2: + qs@6.15.3: dependencies: + es-define-property: 1.0.1 side-channel: 1.1.1 querystringify@2.2.0: {} @@ -3037,13 +3041,13 @@ snapshots: quote-js-string@0.1.0: {} - range-parser@1.2.1: {} + range-parser@1.3.0: {} raw-body@3.0.2: dependencies: bytes: 3.1.2 http-errors: 2.0.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 unpipe: 1.0.0 readdirp@4.1.2: {} @@ -3106,7 +3110,7 @@ snapshots: mime-types: 3.0.2 ms: 2.1.3 on-finished: 2.4.1 - range-parser: 1.2.1 + range-parser: 1.3.0 statuses: 2.0.2 transitivePeerDependencies: - supports-color @@ -3228,7 +3232,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tar@7.5.21: + tar@7.5.22: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -3282,16 +3286,16 @@ snapshots: type-is@2.1.0: dependencies: content-type: 2.0.0 - media-typer: 1.1.0 + media-typer: 1.1.1 mime-types: 3.0.2 - typescript-eslint@8.65.0(eslint@10.7.0)(typescript@6.0.3): + typescript-eslint@8.65.0(eslint@10.8.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.65.0(eslint@10.7.0)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.8.0)(typescript@6.0.3) '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.7.0)(typescript@6.0.3) - eslint: 10.7.0 + '@typescript-eslint/utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) + eslint: 10.8.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color From a34d9d4516a985e92e4e07ec54f703358af45f6e Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Thu, 30 Jul 2026 10:46:27 +0100 Subject: [PATCH 44/78] feat: client-side envelope encryption of binaries, flow zips & env vars (#94) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: client-side envelope encryption of app binaries before upload (#1138) Encrypt half of the dcd#1138 contract (the platform api + simulators are the decrypt half). Opt-in via `--encrypt` or `DCD_ENCRYPT_BINARIES=1`; off by default, so uploads are unchanged unless requested. - src/utils/envelope.ts: per-upload DEK, chunked AES-256-GCM container (streamed, constant memory), X25519 sealed-box DEK wrap. Byte-compatible with api/src/common/crypto/envelope.ts (format in dcd/docs/binary-envelope-encryption.md). - src/config/environments.ts: pinned per-env KEK public key slot (null until provisioned; DCD_BINARY_KEK_PUBLIC override for testing). - src/methods.ts: encrypt source in place before hashing/upload so the SHA, dedup check, and both uploaders operate on ciphertext (binaries.sha = ciphertext hash); attach the envelope to binaries.metadata.enc at finalise. - src/types.ts: TAppMetadata.enc; binary.flags.ts: --encrypt; cloud/upload thread the flag. - test/unit/envelope.test.ts: round-trips (incl. the 15MB wikipedia.apk fixture) through a decrypt mirroring the platform, tamper detection, and DEK wrap/unwrap. Verified end-to-end: CLI-encrypted output + wrapped DEK decrypt byte-for-byte with the real dcd api envelope code. Co-Authored-By: Claude Opus 4.8 (1M context) * chore: pin dev and production KEK public keys for binary encryption Fill in the previously-null kekPublicKey slots (version 1) now that the key-encryption keypairs are provisioned. These are X25519 *public* keys and are safe to embed in a public release, exactly like the Supabase anon keys already checked in here; the matching private halves live only on the platform API and are never shipped. Update the resolver test to assert each environment resolves to its pinned version and key rather than null. Co-Authored-By: Claude Opus 4.8 (1M context) * feat: client-side encryption of flow zips and env vars Extends the binary envelope scheme to the two remaining sensitive client-side inputs — the Maestro flow zip and the injected --env KEY=VALUE secrets — so the platform stores only ciphertext at rest. Enabling encryption now covers the binary, the flow zip, and the env vars, each with its OWN per-upload DEK (all wrapped under the same pinned per-environment KEK public key). - utils/envelope.ts: encryptToContainer (in-memory DCDE twin of the streaming file encryptor), encryptFlowBuffer, and encryptEnv (a single-segment inline blob for results.env.enc), plus a shared isEncryptionEnabled(). Byte-compatible with the platform decrypt half. - test-submission.service.ts is the single seam: when encrypting, the flow zip becomes a DCDE container (the sha sent to the API is the CIPHERTEXT hash), the flow envelope rides fields.enc as a JSON string (so both the JSON and legacy multipart submission paths carry it identically), and the env map becomes an { enc } envelope. Both the cloud command and the MCP tool get this for free via buildTestPayload. - --encrypt (and DCD_ENCRYPT=1) now gate all three; DCD_ENCRYPT_BINARIES=1 stays as an alias for the binary-only behaviour. - Round-trips flow + env ciphertext through a mirror of the platform decrypt. Co-Authored-By: Claude Opus 4.8 (1M context) * chore: allowlist the pinned KEK public keys in gitleaks The two KEK public keys pinned in bfa2b4c tripped gitleaks' default generic-api-key rule on entropy alone (5.02 / 4.89), failing the secret-scan job. They are base64 of the raw 32-byte X25519 *public* halves — encrypt-only, with the private halves living solely in API env config, never in this repo. Allowlisted by exact value, matching the convention already used for the Supabase anon keys and deliberately not by file path or by the whole rule: a KEK private key is byte-identical in shape to its public half, so a path allowlist would blind the scanner to a genuine leak in precisely the file most likely to contain one. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .gitleaks.toml | 29 ++- src/commands/cloud.ts | 10 + src/commands/upload.ts | 3 + src/config/environments.ts | 16 ++ src/config/flags/binary.flags.ts | 5 + src/mcp/tools/run-cloud-test.ts | 8 + src/methods.ts | 85 ++++++- src/services/test-submission.service.ts | 64 ++++- src/types.ts | 8 + src/utils/envelope.ts | 301 ++++++++++++++++++++++++ test/unit/envelope.test.ts | 241 +++++++++++++++++++ 11 files changed, 755 insertions(+), 15 deletions(-) create mode 100644 src/utils/envelope.ts create mode 100644 test/unit/envelope.test.ts diff --git a/.gitleaks.toml b/.gitleaks.toml index eb5cdaf..555ff26 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -1,20 +1,33 @@ # Gitleaks configuration — extends the default ruleset. # -# The only allowlisted secrets are the two PUBLIC Supabase anon keys committed -# in src/config/environments.ts. Those JWTs are anon-role keys, designed to be -# embedded in client code and gated by RLS (see the doc comment in that file) — -# they are intentionally not secret. +# The only allowlisted secrets are PUBLIC key material committed in +# src/config/environments.ts: +# - the two Supabase anon keys — anon-role JWTs, designed to be embedded in +# client code and gated by RLS (see the doc comment in that file). +# - the two KEK public keys (prod + dev) for client-side binary envelope +# encryption — base64 of the raw 32-byte X25519 PUBLIC half. They can only +# *encrypt*; the private halves live solely on the API, never in this repo. +# Both are intentionally not secret; gitleaks flags them on entropy alone +# (generic-api-key), not because it recognizes them as credentials. # # They are allowlisted by EXACT VALUE, deliberately not by file path or by the -# whole `jwt` rule: a Supabase service_role key is also a JWT, so a path/rule -# allowlist would let a genuinely sensitive key pasted into the same file slip -# through. Matching exact values keeps that detection intact. +# whole `jwt` / `generic-api-key` rule: a Supabase service_role key is also a +# JWT, and a KEK *private* key is the same shape as its public half, so a +# path/rule allowlist would let a genuinely sensitive key pasted into the same +# file slip through. Matching exact values keeps that detection intact. +# +# When a KEK is rotated, replace the corresponding value below — do not simply +# append, or the retired key stops being distinguishable from a live secret. [extend] useDefault = true [allowlist] -description = "Public Supabase anon keys (safe to commit, gated by RLS)" +description = "Public Supabase anon keys and KEK public keys (safe to commit)" regexes = [ '''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0\.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8''', '''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30\.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ''', + # prod kekPublicKey v1 + '''wtfyWEwK7nJzwI4PD\+9RAW8jxIR1u8kMQq2IhsrVnH4=''', + # dev kekPublicKey v1 + '''RgcToF/OJpcQI9koYvSvtj/WLaebfcN4v5GJoqtr/00=''', ] diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index bedb952..37803b5 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -40,6 +40,7 @@ import { matrixIsIos, parseDeviceMatrix, } from '../utils/device-matrix.js'; +import { isEncryptionEnabled } from '../utils/envelope.js'; import { detectCiContext, isCI } from '../utils/ci.js'; import { CliError, @@ -194,6 +195,12 @@ export const cloudCommand = defineCommand({ let flows = args.flows as string | undefined; const googlePlay = Boolean(args['google-play']); const ignoreShaCheck = Boolean(args['ignore-sha-check']); + // Single opt-in for client-side envelope encryption of every sensitive + // artifact — the binary (#1138), the flow zip (#1151), and env vars + // (#1152). Flag wins; otherwise DCD_ENCRYPT / DCD_ENCRYPT_BINARIES. + const encrypt = isEncryptionEnabled( + args['encrypt'] ? true : undefined, + ); const includeTags = coerceArray( collectRepeatedFlag(rawArgs, ['--include-tags']), ); @@ -762,6 +769,7 @@ export const cloudCommand = defineCommand({ auth, apiUrl, debug, + encrypt, filePath: finalAppFile, ignoreShaCheck, log: !json, @@ -791,6 +799,7 @@ export const cloudCommand = defineCommand({ androidApiLevel, androidDevice, androidNoSnapshot, + apiUrl, appBinaryId: finalBinaryId, cliVersion, commonRoot, @@ -798,6 +807,7 @@ export const cloudCommand = defineCommand({ debug, deviceLocale, deviceMatrix, + encrypt, env, executionPlan, flowFile, diff --git a/src/commands/upload.ts b/src/commands/upload.ts index cdb01eb..f56cb83 100644 --- a/src/commands/upload.ts +++ b/src/commands/upload.ts @@ -21,6 +21,7 @@ export const uploadCommand = defineCommand({ ...apiFlags, 'app-url': binaryFlags['app-url'], 'ignore-sha-check': binaryFlags['ignore-sha-check'], + encrypt: binaryFlags.encrypt, debug: outputFlags.debug, json: outputFlags.json, appFile: { @@ -42,6 +43,7 @@ export const uploadCommand = defineCommand({ const apiUrl = resolveApiUrl(args['api-url'] as string | undefined); const appUrl = args['app-url'] as string | undefined; const ignoreShaCheck = Boolean(args['ignore-sha-check']); + const encryptBinary = Boolean(args['encrypt']); const debug = Boolean(args.debug); const positional = args.appFile as string | undefined; @@ -88,6 +90,7 @@ export const uploadCommand = defineCommand({ auth, apiUrl, debug, + encrypt: encryptBinary, filePath: resolvedFile, ignoreShaCheck, log: !json, diff --git a/src/config/environments.ts b/src/config/environments.ts index 9dd7011..ccf06e5 100644 --- a/src/config/environments.ts +++ b/src/config/environments.ts @@ -19,6 +19,20 @@ export interface DcdEnvironment { projectRef: string; anonKey: string; }; + /** + * Pinned KEK public key for client-side binary envelope encryption (dcd#1138). + * `key` is base64 of the raw 32-byte X25519 public key; `version` selects which + * KEK the platform API unwraps with. **Public — safe to embed** (like the anon + * key above): it can only *encrypt*; the private half lives solely on the API. + * + * Left `null` until the KEK is generated and provisioned. Generate a keypair + * with, e.g.: + * node -e 'const c=require("crypto");const{publicKey,privateKey}=c.generateKeyPairSync("x25519");const pub=publicKey.export({type:"spki",format:"der"}).subarray(12);const priv=privateKey.export({type:"pkcs8",format:"der"}).subarray(16);console.log("public :",pub.toString("base64"));console.log("private:",priv.toString("base64"))' + * Pin `public` here; set `private` as `BINARY_KEK_PRIVATE_KEYS={"":""}` + * on the API service and keep one offline escrow copy. Until then, encryption + * can be exercised via the `DCD_BINARY_KEK_PUBLIC` env override (see envelope.ts). + */ + kekPublicKey: { version: number; key: string } | null; } export const ENVIRONMENTS: Record = { @@ -31,6 +45,7 @@ export const ENVIRONMENTS: Record = { anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8', }, + kekPublicKey: { version: 1, key: 'wtfyWEwK7nJzwI4PD+9RAW8jxIR1u8kMQq2IhsrVnH4=' }, }, dev: { apiUrl: 'https://api.dev.devicecloud.dev', @@ -41,6 +56,7 @@ export const ENVIRONMENTS: Record = { anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ', }, + kekPublicKey: { version: 1, key: 'RgcToF/OJpcQI9koYvSvtj/WLaebfcN4v5GJoqtr/00=' }, }, }; diff --git a/src/config/flags/binary.flags.ts b/src/config/flags/binary.flags.ts index c855790..6f92306 100644 --- a/src/config/flags/binary.flags.ts +++ b/src/config/flags/binary.flags.ts @@ -24,4 +24,9 @@ export const binaryFlags = { description: 'Ignore the sha hash check and upload the binary regardless of whether it already exists (not recommended)', }, + encrypt: { + type: 'boolean', + description: + 'Encrypt the app binary, flow zip, and env vars before upload (client-side envelope encryption; each gets its own key). Can also be enabled with DCD_ENCRYPT=1.', + }, } as const satisfies ArgsDef; diff --git a/src/mcp/tools/run-cloud-test.ts b/src/mcp/tools/run-cloud-test.ts index 1ba76f3..d33ccdf 100644 --- a/src/mcp/tools/run-cloud-test.ts +++ b/src/mcp/tools/run-cloud-test.ts @@ -12,6 +12,7 @@ import { VersionService } from '../../services/version.service.js'; import { uploadBinary, uploadFlowZip, verifyAppZip } from '../../methods.js'; import { getCliVersion } from '../../utils/cli.js'; import { fetchCompatibilityData } from '../../utils/compatibility.js'; +import { isEncryptionEnabled } from '../../utils/envelope.js'; import { getConsoleUrl } from '../../utils/styling.js'; import { getContext, logStderr } from '../context.js'; import { jsonResult, runTool } from '../helpers.js'; @@ -173,6 +174,10 @@ export function registerRunCloudTest(server: McpServer): void { }); } + // Client-side envelope encryption (binary/flow/env). No MCP flag, so + // it's env-driven: DCD_ENCRYPT / DCD_ENCRYPT_BINARIES. + const encrypt = isEncryptionEnabled(); + // Resolve the binary: existing id, or upload the local file. let appBinaryId = args.appBinaryId; if (!appBinaryId) { @@ -190,6 +195,7 @@ export function registerRunCloudTest(server: McpServer): void { appBinaryId = await uploadBinary({ auth, apiUrl, + encrypt, filePath: args.appFile, ignoreShaCheck: Boolean(args.ignoreShaCheck), log: false, @@ -199,10 +205,12 @@ export function registerRunCloudTest(server: McpServer): void { const { continueOnFailure = true } = executionPlan.sequence ?? {}; const testSubmissionService = new TestSubmissionService(); const { buffer, fields } = await testSubmissionService.buildTestPayload({ + apiUrl, appBinaryId, cliVersion, commonRoot, continueOnFailure, + encrypt, executionPlan, flowFile, env: args.env ?? [], diff --git a/src/methods.ts b/src/methods.ts index fdd8734..ddf5425 100644 --- a/src/methods.ts +++ b/src/methods.ts @@ -20,6 +20,14 @@ import { SupabaseGateway } from './gateways/supabase-gateway.js'; import { MetadataExtractorService } from './services/metadata-extractor.service.js'; import { TAppMetadata } from './types.js'; import type { AuthContext } from './types/domain/auth.types.js'; +import { + type BinaryEnvelope, + encryptFileToPath, + generateDek, + isEncryptionEnabled, + resolveKekPublicKey, + wrapDek, +} from './utils/envelope.js'; import { colors, formatId } from './utils/styling.js'; const mimeTypeLookupByExtension: Record = { @@ -129,6 +137,12 @@ interface UploadBinaryConfig { auth: AuthContext; apiUrl: string; debug?: boolean; + /** + * Encrypt the binary before upload (client-side envelope encryption, #1138). + * Defaults to `DCD_ENCRYPT` / `DCD_ENCRYPT_BINARIES` when unset (see + * {@link isEncryptionEnabled}). + */ + encrypt?: boolean; filePath: string; ignoreShaCheck?: boolean; log?: boolean; @@ -136,6 +150,7 @@ interface UploadBinaryConfig { export const uploadBinary = async (config: UploadBinaryConfig) => { const { filePath, apiUrl, auth, ignoreShaCheck = false, log = true, debug = false } = config; + const encrypt = isEncryptionEnabled(config.encrypt); if (log) { ux.action.start(colors.bold('Checking and uploading binary'), colors.dim('Initializing'), { stdout: true, @@ -151,11 +166,24 @@ export const uploadBinary = async (config: UploadBinaryConfig) => { const startTime = Date.now(); let source: UploadSource | undefined; + let encCleanupDir: string | undefined; + let enc: BinaryEnvelope | undefined; try { // Prepare file for upload source = await prepareFileForUpload(filePath, debug, startTime); + // Encrypt before hashing/upload so the SHA, dedup check, and both uploaders + // all operate on ciphertext (binaries.sha = ciphertext hash, per #1138). + if (encrypt) { + const encrypted = await encryptUploadSource(source, apiUrl, debug); + enc = encrypted.enc; + encCleanupDir = encrypted.cleanupDir; + if (log) { + ux.info(colors.dim(`Encrypting binary before upload (KEK v${enc.kek})`)); + } + } + // Calculate SHA hash const sha = await calculateFileHash(source, debug, log); @@ -176,7 +204,7 @@ export const uploadBinary = async (config: UploadBinaryConfig) => { } // Perform the upload - const uploadId = await performUpload({ auth, apiUrl, debug, filePath, sha, source, startTime }); + const uploadId = await performUpload({ auth, apiUrl, debug, enc, filePath, sha, source, startTime }); if (log) { ux.action.stop(colors.success('\n✓ Binary uploaded with ID: ') + formatId(uploadId)); @@ -205,9 +233,54 @@ export const uploadBinary = async (config: UploadBinaryConfig) => { if (source?.cleanupDir) { await rm(source.cleanupDir, { recursive: true, force: true }).catch(() => {}); } + if (encCleanupDir) { + await rm(encCleanupDir, { recursive: true, force: true }).catch(() => {}); + } } }; +/** + * Encrypt the prepared upload source in place (dcd#1138): generate a per-upload + * DEK, stream-encrypt `source.diskPath` into a temp ciphertext file, wrap the + * DEK with the environment's pinned KEK public key, and repoint `source` at the + * ciphertext so the SHA, dedup check, and both uploaders operate on ciphertext. + * Returns the envelope metadata (for `binaries.metadata.enc`) plus the temp dir + * to clean up. Throws if no KEK is available for the environment. + */ +async function encryptUploadSource( + source: UploadSource, + apiUrl: string, + debug: boolean, +): Promise<{ enc: BinaryEnvelope; cleanupDir: string }> { + const kek = resolveKekPublicKey(apiUrl); + if (!kek) { + throw new Error( + 'Binary encryption was requested but no KEK public key is configured for this environment. ' + + 'Set DCD_BINARY_KEK_PUBLIC=: or pin one in src/config/environments.ts.', + ); + } + + const dek = generateDek(); + const enc = wrapDek(dek, kek); + const cleanupDir = await mkdtemp(path.join(os.tmpdir(), 'dcd-enc-')); + const cipherPath = path.join(cleanupDir, 'binary.enc'); + + if (debug) { + console.log(`[DEBUG] Encrypting binary with KEK v${kek.version} -> ${cipherPath}`); + } + + await encryptFileToPath(source.diskPath, cipherPath, dek, kek.version); + const { size } = await stat(cipherPath); + source.diskPath = cipherPath; + source.size = size; + + if (debug) { + console.log(`[DEBUG] Ciphertext size: ${(size / 1024 / 1024).toFixed(2)} MB`); + } + + return { enc, cleanupDir }; +} + /** * Disk-backed description of the binary to upload. Every upload path streams * from `diskPath` instead of materializing the file in memory — a 1.5 GB iOS @@ -416,6 +489,8 @@ interface PerformUploadConfig { auth: AuthContext; apiUrl: string; debug: boolean; + /** Envelope metadata when the binary was encrypted (#1138); undefined otherwise. */ + enc?: BinaryEnvelope; filePath: string; sha: string | undefined; source: UploadSource; @@ -694,13 +769,17 @@ function validateUploadResults( * @returns Promise resolving to upload ID */ async function performUpload(config: PerformUploadConfig): Promise { - const { filePath, apiUrl, auth, source, sha, debug, startTime } = config; + const { filePath, apiUrl, auth, enc, source, sha, debug, startTime } = config; // Request upload URL and paths const { id, tempPath, finalPath, b2 } = await requestUploadPaths(apiUrl, auth, filePath, source.size, debug); - // Extract app metadata + // Extract app metadata from the original (plaintext) file. Attach the + // envelope so it lands on binaries.metadata.enc (#1138). const metadata = await extractBinaryMetadata(filePath, debug); + if (enc) { + metadata.enc = enc; + } const env = inferEnvFromApiUrl(apiUrl); diff --git a/src/services/test-submission.service.ts b/src/services/test-submission.service.ts index bbff215..678c66f 100644 --- a/src/services/test-submission.service.ts +++ b/src/services/test-submission.service.ts @@ -3,6 +3,12 @@ import * as path from 'node:path'; import { compressFilesFromRelativePath } from '../methods.js'; import { DeviceMatrixConfig } from '../types/domain/device.types.js'; +import { + type BinaryEnvelope, + encryptEnv, + encryptFlowBuffer, + resolveKekPublicKey, +} from '../utils/envelope.js'; import { toPortableRelativePath } from '../utils/paths.js'; import { IExecutionPlan } from './execution-plan.service.js'; @@ -10,6 +16,7 @@ export interface TestSubmissionConfig { androidApiLevel?: string; androidDevice?: string; androidNoSnapshot?: boolean; + apiUrl?: string; appBinaryId: string; cliVersion: string; commonRoot: string; @@ -18,6 +25,12 @@ export interface TestSubmissionConfig { deviceLocale?: string; deviceMatrix?: DeviceMatrixConfig[]; disableAnimations?: boolean; + /** + * Encrypt the flow zip and env vars before upload (#1151/#1152), each with its + * own per-upload DEK wrapped under the environment KEK. Requires `apiUrl` to + * resolve the pinned KEK public key. + */ + encrypt?: boolean; env?: string[]; executionPlan: IExecutionPlan; flowFile: string; @@ -60,7 +73,9 @@ export class TestSubmissionService { config: TestSubmissionConfig, ): Promise<{ buffer: Buffer; fields: Record; sha: string }> { const { + apiUrl, appBinaryId, + encrypt = false, flowFile, executionPlan, commonRoot, @@ -144,7 +159,7 @@ export class TestSubmissionService { this.logDebug(debug, logger, `[DEBUG] Compressing files from path: ${flowFile}`); - const buffer = await compressFilesFromRelativePath( + const plaintextZip = await compressFilesFromRelativePath( flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml') ? path.dirname(flowFile) : flowFile, @@ -158,9 +173,45 @@ export class TestSubmissionService { commonRoot, ); - this.logDebug(debug, logger, `[DEBUG] Compressed file size: ${buffer.length} bytes`); + this.logDebug(debug, logger, `[DEBUG] Compressed file size: ${plaintextZip.length} bytes`); + + // Client-side envelope encryption (#1151 flow zip, #1152 env vars). Each + // gets its own per-upload DEK wrapped under the environment KEK; the flow + // zip becomes a DCDE container and `sha` is the CIPHERTEXT hash (uploads.sha + // stays a ciphertext hash, mirroring binaries.sha). `enc` rides `fields` + // as a JSON string like every other field, so both the JSON submitFlowTest + // body and the legacy multipart form carry it identically. + let buffer = plaintextZip; + let envObjectToSend: Record = envObject; + let flowEnc: BinaryEnvelope | undefined; + if (encrypt) { + if (!apiUrl) { + throw new Error('Encryption requires apiUrl to resolve the KEK public key'); + } + const kek = resolveKekPublicKey(apiUrl); + if (!kek) { + throw new Error( + 'Encryption was requested but no KEK public key is configured for this environment. ' + + 'Set DCD_BINARY_KEK_PUBLIC=: or pin one in src/config/environments.ts.', + ); + } + const flow = encryptFlowBuffer(plaintextZip, kek); + buffer = flow.ciphertext; + flowEnc = flow.enc; + this.logDebug( + debug, + logger, + `[DEBUG] Encrypting flow zip before upload (KEK v${kek.version}); ciphertext ${buffer.length} bytes`, + ); + // Only encrypt when there are env vars; an empty map has no secret to + // protect and stays a plaintext `{}` (no `enc` marker, passes through). + if (Object.keys(envObject).length > 0) { + envObjectToSend = { enc: encryptEnv(envObject, kek) }; + this.logDebug(debug, logger, `[DEBUG] Encrypting ${Object.keys(envObject).length} env var(s)`); + } + } - // Calculate SHA-256 hash of the flow ZIP + // SHA-256 of what actually gets uploaded (ciphertext when encrypted). const sha = createHash('sha256').update(buffer).digest('hex'); this.logDebug(debug, logger, `[DEBUG] Flow ZIP SHA-256: ${sha}`); @@ -182,7 +233,12 @@ export class TestSubmissionService { fields.sequentialFlows = JSON.stringify( this.normalizePaths(sequentialFlows, commonRoot), ); - fields.env = JSON.stringify(envObject); + fields.env = JSON.stringify(envObjectToSend); + // Flow-zip envelope for uploads.metadata.enc (#1151). JSON string so both + // submission paths carry it like every other field; the API parses it. + if (flowEnc) { + fields.enc = JSON.stringify(flowEnc); + } // Note: googlePlay is now included in configPayload below instead of as a separate field // to work around a FormData parsing issue in the API diff --git a/src/types.ts b/src/types.ts index ff898d3..69f4a35 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,12 @@ +import type { BinaryEnvelope } from './utils/envelope.js'; + export type TAppMetadata = { appId: string; platform: 'android' | 'ios'; + /** + * Present when the binary was client-side envelope-encrypted before upload + * (dcd#1138). Stored on `binaries.metadata.enc`; the platform reads it to + * release the DEK and decrypt. Absent = legacy plaintext upload. + */ + enc?: BinaryEnvelope; }; diff --git a/src/utils/envelope.ts b/src/utils/envelope.ts new file mode 100644 index 0000000..747705b --- /dev/null +++ b/src/utils/envelope.ts @@ -0,0 +1,301 @@ +import { + createCipheriv, + createPublicKey, + diffieHellman, + generateKeyPairSync, + hkdfSync, + randomBytes, +} from 'node:crypto'; +import { open, stat } from 'node:fs/promises'; +import { inferEnvFromApiUrl } from '../config/environments.js'; +import { ENVIRONMENTS } from '../config/environments.js'; + +/** + * Client-side envelope encryption of app binaries before upload (dcd#1138). + * + * The CLI is the *encrypt* half of the contract; the platform (api + + * simulators, in the `dcd` repo) is the *decrypt* half. This module MUST stay + * byte-compatible with `api/src/common/crypto/envelope.ts` — the wire format is + * specified in `dcd/docs/binary-envelope-encryption.md`. + * + * Scheme: a per-upload random 256-bit DEK encrypts the binary (chunked + * AES-256-GCM); the DEK is wrapped with the environment's pinned X25519 KEK + * public key (sealed box). Only the platform API holds the KEK private half, so + * every storage/transport tier sees ciphertext only. + */ + +const MAGIC = Buffer.from('DCDE', 'ascii'); +const CONTAINER_VERSION = 1; +const NONCE_LEN = 12; +const NONCE_PREFIX_LEN = 7; +const DEK_LEN = 32; +export const CHUNK_SIZE = 1024 * 1024; // 1 MiB plaintext segments + +const HKDF_INFO = Buffer.from('dcd-binary-dek-wrap-v1', 'ascii'); +// DER prefix that turns a raw 32-byte X25519 public key into an importable SPKI. +const SPKI_PREFIX = Buffer.from('302a300506032b656e032100', 'hex'); + +/** `binaries.metadata.enc` / `uploads.metadata.enc` shape (binary + flow zip). */ +export interface BinaryEnvelope { + v: number; + kek: number; + wrapped_key: string; +} + +/** + * `results.env.enc` shape (#1152). Same wrapped-DEK fields as a binary, plus the + * env ciphertext carried **inline** (the env map is tiny, so a single DCDE + * segment rides in-column rather than as a separate uploaded blob). + */ +export interface EnvEnvelope extends BinaryEnvelope { + /** base64 of a single-segment DCDE container of `JSON.stringify(env)`. */ + ciphertext: string; +} + +/** + * Whether client-side envelope encryption is on. Explicit `flag` (the + * `--encrypt` CLI flag) wins; otherwise `DCD_ENCRYPT=1` enables it for binary, + * flow, and env, and the legacy `DCD_ENCRYPT_BINARIES=1` is kept as an alias. + * When on, the binary, the flow zip, and the env map are each encrypted with + * their **own** per-upload DEK (all wrapped under the same per-env KEK). + */ +export function isEncryptionEnabled(flag?: boolean): boolean { + if (flag !== undefined) return flag; + return ( + process.env.DCD_ENCRYPT === '1' || process.env.DCD_ENCRYPT_BINARIES === '1' + ); +} + +/** Pinned KEK public key (base64 raw 32-byte X25519) + version, per env. */ +interface KekPublicKey { + version: number; + keyRaw: Buffer; +} + +function x25519PublicFromRaw(raw: Buffer) { + return createPublicKey({ + key: Buffer.concat([SPKI_PREFIX, raw]), + format: 'der', + type: 'spki', + }); +} + +/** + * Resolve the KEK public key for the environment behind `apiUrl`. Order: + * 1. `DCD_BINARY_KEK_PUBLIC` env override (`:`, e.g. `1:AAAA…`) + * — lets the feature be exercised before keys are pinned in the release. + * 2. the pinned `ENVIRONMENTS[env].kekPublicKey`. + * Returns null when no key is available (encryption cannot proceed). + */ +export function resolveKekPublicKey(apiUrl: string): KekPublicKey | null { + const override = process.env.DCD_BINARY_KEK_PUBLIC; + if (override) { + const [versionPart, b64] = override.split(':'); + const version = Number(versionPart); + if (b64 && Number.isInteger(version)) { + const keyRaw = Buffer.from(b64, 'base64'); + if (keyRaw.length === 32) return { version, keyRaw }; + } + throw new Error( + 'DCD_BINARY_KEK_PUBLIC must be ":"', + ); + } + + const env = inferEnvFromApiUrl(apiUrl); + const pinned = ENVIRONMENTS[env].kekPublicKey; + if (!pinned) return null; + const keyRaw = Buffer.from(pinned.key, 'base64'); + if (keyRaw.length !== 32) { + throw new Error(`Pinned KEK public key for ${env} is not a 32-byte key`); + } + return { version: pinned.version, keyRaw }; +} + +/** + * Wrap a DEK for the given KEK public key (X25519 sealed box). Produces base64 of + * `ephPub(32) || iv(12) || ciphertext(32) || tag(16)`. + */ +export function wrapDek(dek: Buffer, kek: KekPublicKey): BinaryEnvelope { + const eph = generateKeyPairSync('x25519'); + const ephPubRaw = eph.publicKey + .export({ type: 'spki', format: 'der' }) + .subarray(SPKI_PREFIX.length); + const shared = diffieHellman({ + privateKey: eph.privateKey, + publicKey: x25519PublicFromRaw(kek.keyRaw), + }); + const key = Buffer.from( + hkdfSync('sha256', shared, Buffer.alloc(0), HKDF_INFO, 32), + ); + const iv = randomBytes(NONCE_LEN); + const cipher = createCipheriv('aes-256-gcm', key, iv); + const ct = Buffer.concat([cipher.update(dek), cipher.final()]); + const wrapped = Buffer.concat([ + ephPubRaw, + iv, + ct, + cipher.getAuthTag(), + ]).toString('base64'); + return { v: CONTAINER_VERSION, kek: kek.version, wrapped_key: wrapped }; +} + +function segmentNonce( + prefix: Buffer, + index: number, + isLast: boolean, +): Buffer { + const nonce = Buffer.alloc(NONCE_LEN); + prefix.copy(nonce, 0, 0, NONCE_PREFIX_LEN); + nonce.writeUInt32BE(index, NONCE_PREFIX_LEN); + nonce.writeUInt8(isLast ? 1 : 0, NONCE_PREFIX_LEN + 4); + return nonce; +} + +/** + * Stream-encrypt `srcPath` into a DCDE container at `destPath` using `dek`, in + * constant memory (one chunk buffered at a time). `kekVersion` is written into + * the header (mirrors the wrapped-key's KEK version). + */ +export async function encryptFileToPath( + srcPath: string, + destPath: string, + dek: Buffer, + kekVersion: number, + chunkSize: number = CHUNK_SIZE, +): Promise { + if (dek.length !== DEK_LEN) { + throw new Error(`DEK must be ${DEK_LEN} bytes`); + } + const { size } = await stat(srcPath); + const noncePrefix = randomBytes(NONCE_PREFIX_LEN); + + const header = Buffer.alloc(17); + MAGIC.copy(header, 0); + header.writeUInt8(CONTAINER_VERSION, 4); + header.writeUInt8(kekVersion, 5); + header.writeUInt32BE(chunkSize, 6); + noncePrefix.copy(header, 10); + + const input = await open(srcPath, 'r'); + const output = await open(destPath, 'w'); + try { + await output.write(header); + const buf = Buffer.alloc(chunkSize); + let index = 0; + let readTotal = 0; + // size 0 → no segments (matches the API's empty-input handling). + while (readTotal < size) { + const { bytesRead } = await input.read(buf, 0, chunkSize, null); + if (bytesRead === 0) break; + readTotal += bytesRead; + const isLast = readTotal >= size; + const cipher = createCipheriv( + 'aes-256-gcm', + dek, + segmentNonce(noncePrefix, index, isLast), + ); + const ct = Buffer.concat([ + cipher.update(buf.subarray(0, bytesRead)), + cipher.final(), + ]); + await output.write(ct); + await output.write(cipher.getAuthTag()); + index += 1; + if (isLast) break; + } + } finally { + await input.close(); + await output.close(); + } +} + +/** + * In-memory twin of {@link encryptFileToPath}: encrypt `plaintext` into a DCDE + * container Buffer using `dek`. Byte-identical wire format (same 17-byte header, + * same per-segment nonce/tag scheme), for payloads already held in memory (flow + * zips, the env map). A payload no larger than `chunkSize` is a single segment. + */ +export function encryptToContainer( + plaintext: Buffer, + dek: Buffer, + kekVersion: number, + chunkSize: number = CHUNK_SIZE, +): Buffer { + if (dek.length !== DEK_LEN) { + throw new Error(`DEK must be ${DEK_LEN} bytes`); + } + const noncePrefix = randomBytes(NONCE_PREFIX_LEN); + + const header = Buffer.alloc(17); + MAGIC.copy(header, 0); + header.writeUInt8(CONTAINER_VERSION, 4); + header.writeUInt8(kekVersion, 5); + header.writeUInt32BE(chunkSize, 6); + noncePrefix.copy(header, 10); + + const parts: Buffer[] = [header]; + // size 0 → no segments (matches encryptFileToPath / the API's empty input). + let index = 0; + let offset = 0; + while (offset < plaintext.length) { + const end = Math.min(offset + chunkSize, plaintext.length); + const isLast = end >= plaintext.length; + const cipher = createCipheriv( + 'aes-256-gcm', + dek, + segmentNonce(noncePrefix, index, isLast), + ); + const ct = Buffer.concat([ + cipher.update(plaintext.subarray(offset, end)), + cipher.final(), + ]); + parts.push(ct, cipher.getAuthTag()); + offset = end; + index += 1; + } + return Buffer.concat(parts); +} + +/** + * Encrypt a flow zip buffer with its own per-upload DEK. Returns the ciphertext + * (a DCDE container) plus the envelope for `uploads.metadata.enc`. `uploads.sha` + * must be recomputed from the returned ciphertext by the caller. + */ +export function encryptFlowBuffer( + buffer: Buffer, + kek: KekPublicKey, +): { ciphertext: Buffer; enc: BinaryEnvelope } { + const dek = generateDek(); + const enc = wrapDek(dek, kek); + const ciphertext = encryptToContainer(buffer, dek, kek.version); + return { ciphertext, enc }; +} + +/** + * Encrypt the `--env KEY=VALUE` map with its own per-submission DEK into the + * `results.env.enc` envelope (#1152). The full map is serialized, encrypted as a + * single-segment container, and carried inline as base64 `ciphertext`. + */ +export function encryptEnv( + env: Record, + kek: KekPublicKey, +): EnvEnvelope { + const dek = generateDek(); + const { wrapped_key } = wrapDek(dek, kek); + const container = encryptToContainer( + Buffer.from(JSON.stringify(env), 'utf8'), + dek, + kek.version, + ); + return { + v: CONTAINER_VERSION, + kek: kek.version, + wrapped_key, + ciphertext: container.toString('base64'), + }; +} + +/** Generate a fresh per-upload DEK. */ +export function generateDek(): Buffer { + return randomBytes(DEK_LEN); +} diff --git a/test/unit/envelope.test.ts b/test/unit/envelope.test.ts new file mode 100644 index 0000000..2c8f1a0 --- /dev/null +++ b/test/unit/envelope.test.ts @@ -0,0 +1,241 @@ +import { expect } from 'chai'; +import { + createDecipheriv, + createPublicKey, + createPrivateKey, + diffieHellman, + generateKeyPairSync, + hkdfSync, + randomBytes, +} from 'node:crypto'; +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { + encryptEnv, + encryptFileToPath, + encryptFlowBuffer, + generateDek, + resolveKekPublicKey, + wrapDek, +} from '../../src/utils/envelope.js'; + +/** + * Reference implementation of the platform *decrypt* half (as implemented in + * dcd `api/src/common/crypto/envelope.ts` and `simulators/gateways/ + * EnvelopeGateway.ts`). If the CLI's ciphertext round-trips through this, it is + * byte-compatible with the platform. + */ +const SPKI_PREFIX = Buffer.from('302a300506032b656e032100', 'hex'); +const PKCS8_PREFIX = Buffer.from('302e020100300506032b656e04220420', 'hex'); +const HKDF_INFO = Buffer.from('dcd-binary-dek-wrap-v1', 'ascii'); + +function refDecryptContainer(ciphertext: Buffer, dek: Buffer): Buffer { + expect(ciphertext.subarray(0, 4).toString('ascii')).to.equal('DCDE'); + expect(ciphertext.readUInt8(4)).to.equal(1); // container version + const chunkSize = ciphertext.readUInt32BE(6); + const noncePrefix = ciphertext.subarray(10, 17); + const body = ciphertext.subarray(17); + const segBytes = chunkSize + 16; + + const out: Buffer[] = []; + let off = 0; + let index = 0; + while (off < body.length) { + const end = Math.min(off + segBytes, body.length); + const seg = body.subarray(off, end); + const isLast = end >= body.length; + const ct = seg.subarray(0, seg.length - 16); + const tag = seg.subarray(seg.length - 16); + const nonce = Buffer.alloc(12); + noncePrefix.copy(nonce, 0, 0, 7); + nonce.writeUInt32BE(index, 7); + nonce.writeUInt8(isLast ? 1 : 0, 11); + const d = createDecipheriv('aes-256-gcm', dek, nonce); + d.setAuthTag(tag); + out.push(Buffer.concat([d.update(ct), d.final()])); + off = end; + index += 1; + } + return Buffer.concat(out); +} + +function refUnwrapDek(wrappedB64: string, kekPrivRaw: Buffer): Buffer { + const kekPriv = createPrivateKey({ + key: Buffer.concat([PKCS8_PREFIX, kekPrivRaw]), + format: 'der', + type: 'pkcs8', + }); + const blob = Buffer.from(wrappedB64, 'base64'); + const ephPubRaw = blob.subarray(0, 32); + const iv = blob.subarray(32, 44); + const ct = blob.subarray(44, 76); + const tag = blob.subarray(76, 92); + const ephPub = createPublicKey({ + key: Buffer.concat([SPKI_PREFIX, ephPubRaw]), + format: 'der', + type: 'spki', + }); + const shared = diffieHellman({ privateKey: kekPriv, publicKey: ephPub }); + const key = Buffer.from( + hkdfSync('sha256', shared, Buffer.alloc(0), HKDF_INFO, 32), + ); + const d = createDecipheriv('aes-256-gcm', key, iv); + d.setAuthTag(tag); + return Buffer.concat([d.update(ct), d.final()]); +} + +function rawX25519(): { privRaw: Buffer; pubRaw: Buffer } { + const { publicKey, privateKey } = generateKeyPairSync('x25519'); + return { + pubRaw: publicKey + .export({ type: 'spki', format: 'der' }) + .subarray(SPKI_PREFIX.length), + privRaw: privateKey + .export({ type: 'pkcs8', format: 'der' }) + .subarray(PKCS8_PREFIX.length), + }; +} + +describe('binary envelope encryption (#1138)', () => { + let dir: string; + + beforeEach(async () => { + dir = await mkdtemp(path.join(os.tmpdir(), 'dcd-enc-test-')); + }); + afterEach(async () => { + await rm(dir, { recursive: true, force: true }); + delete process.env.DCD_BINARY_KEK_PUBLIC; + }); + + const sizes: Array<[string, number]> = [ + ['sub-chunk', 500], + ['exactly one chunk', 1024], + ['one chunk + 1 byte', 1025], + ['several chunks + remainder', 4096 + 321], + ]; + + for (const [label, size] of sizes) { + it(`encrypts ${label} payloads into a DCDE container the platform decrypt recovers`, async () => { + const plaintext = randomBytes(size); + const src = path.join(dir, 'plain.bin'); + const dest = path.join(dir, 'cipher.enc'); + await writeFile(src, plaintext); + + const dek = generateDek(); + await encryptFileToPath(src, dest, dek, 1, 1024); // small chunk → multi-segment + + const ciphertext = await readFile(dest); + expect(refDecryptContainer(ciphertext, dek).equals(plaintext)).to.equal( + true, + ); + }); + } + + it('round-trips the wikipedia.apk fixture at the 1 MiB default chunk size', async () => { + const apk = await readFile( + new URL('../fixtures/wikipedia.apk', import.meta.url), + ); + const src = path.join(dir, 'wikipedia.apk'); + const dest = path.join(dir, 'wikipedia.apk.enc'); + await writeFile(src, apk); + + const dek = generateDek(); + await encryptFileToPath(src, dest, dek, 1); + + const ciphertext = await readFile(dest); + // Ciphertext must differ from plaintext and be recoverable byte-for-byte. + expect(ciphertext.subarray(0, 4).toString('ascii')).to.equal('DCDE'); + expect(refDecryptContainer(ciphertext, dek).equals(apk)).to.equal(true); + }); + + it('detects tampering (GCM tag mismatch)', async () => { + const src = path.join(dir, 'p.bin'); + const dest = path.join(dir, 'c.enc'); + await writeFile(src, randomBytes(3000)); + const dek = generateDek(); + await encryptFileToPath(src, dest, dek, 1, 1024); + const ciphertext = await readFile(dest); + ciphertext[25] ^= 0xff; // flip a ciphertext byte + expect(() => refDecryptContainer(ciphertext, dek)).to.throw(); + }); + + it('wraps a DEK that the matching KEK private key unwraps', () => { + const { privRaw, pubRaw } = rawX25519(); + process.env.DCD_BINARY_KEK_PUBLIC = `3:${pubRaw.toString('base64')}`; + const kek = resolveKekPublicKey('https://api.devicecloud.dev'); + expect(kek).to.not.equal(null); + + const dek = generateDek(); + const envelope = wrapDek(dek, kek!); + expect(envelope.v).to.equal(1); + expect(envelope.kek).to.equal(3); + expect(refUnwrapDek(envelope.wrapped_key, privRaw).equals(dek)).to.equal( + true, + ); + }); + + it('resolves the pinned KEK public key for each environment', () => { + const prod = resolveKekPublicKey('https://api.devicecloud.dev'); + expect(prod).to.not.equal(null); + expect(prod!.version).to.equal(1); + expect(prod!.keyRaw.toString('base64')).to.equal( + 'wtfyWEwK7nJzwI4PD+9RAW8jxIR1u8kMQq2IhsrVnH4=', + ); + + const dev = resolveKekPublicKey('https://api.dev.devicecloud.dev'); + expect(dev).to.not.equal(null); + expect(dev!.version).to.equal(1); + expect(dev!.keyRaw.toString('base64')).to.equal( + 'RgcToF/OJpcQI9koYvSvtj/WLaebfcN4v5GJoqtr/00=', + ); + }); +}); + +describe('flow + env envelope encryption (#1151, #1152)', () => { + afterEach(() => { + delete process.env.DCD_BINARY_KEK_PUBLIC; + }); + + it('encryptFlowBuffer produces a container the platform decrypts to the original zip', () => { + const { privRaw, pubRaw } = rawX25519(); + process.env.DCD_BINARY_KEK_PUBLIC = `2:${pubRaw.toString('base64')}`; + const kek = resolveKekPublicKey('https://api.dev.devicecloud.dev')!; + + const zip = randomBytes(5000); + const { ciphertext, enc } = encryptFlowBuffer(zip, kek); + + expect(enc.v).to.equal(1); + expect(enc.kek).to.equal(2); + expect(ciphertext.subarray(0, 4).toString('ascii')).to.equal('DCDE'); + const dek = refUnwrapDek(enc.wrapped_key, privRaw); + expect(refDecryptContainer(ciphertext, dek).equals(zip)).to.equal(true); + }); + + it('encryptEnv produces an inline envelope the platform decrypts back to the map', () => { + const { privRaw, pubRaw } = rawX25519(); + process.env.DCD_BINARY_KEK_PUBLIC = `1:${pubRaw.toString('base64')}`; + const kek = resolveKekPublicKey('https://api.dev.devicecloud.dev')!; + + const env = { API_TOKEN: 'secret', PASSWORD: 'p@ss word=1', EMPTY: '' }; + const enc = encryptEnv(env, kek); + + expect(enc.v).to.equal(1); + expect(enc.kek).to.equal(1); + // API unwraps the DEK from wrapped_key; runner decrypts the inline blob. + const dek = refUnwrapDek(enc.wrapped_key, privRaw); + const plain = refDecryptContainer(Buffer.from(enc.ciphertext, 'base64'), dek); + expect(JSON.parse(plain.toString('utf8'))).to.deep.equal(env); + }); + + it('gives the flow zip and env their own distinct DEKs', () => { + const { pubRaw } = rawX25519(); + process.env.DCD_BINARY_KEK_PUBLIC = `1:${pubRaw.toString('base64')}`; + const kek = resolveKekPublicKey('https://api.dev.devicecloud.dev')!; + + const flow = encryptFlowBuffer(randomBytes(100), kek); + const env = encryptEnv({ A: 'b' }, kek); + // Independent sealed boxes → the wrapped keys must differ. + expect(flow.enc.wrapped_key).to.not.equal(env.wrapped_key); + }); +}); From 6e244a90dedff47c0bba5b1041fa6da4fe2e9d77 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Mon, 3 Aug 2026 10:15:14 +0100 Subject: [PATCH 45/78] feat(upload): dedup encrypted binaries on the plaintext hash (#101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `--encrypt` uses a fresh random DEK per upload, so identical plaintext produces different ciphertext every time. Since the dedup key was the hash of whatever gets uploaded, encryption silently disabled dedup entirely: every run re-uploaded and re-stored the full binary. Verified on dev — two `--encrypt` runs of a byte-identical APK produced two distinct binaries, while the plaintext run deduped. Hash the plaintext first, dedup on that, and only encrypt on a miss. Because the check now happens before encryption, a hit skips the encryption work too, not just the upload — an encrypted re-run is now faster than the old plaintext path rather than merely matching it. `binaries.sha` is untouched and remains the ciphertext hash: the hosts that verify it (B2, Supabase Storage, storage-cache, Mac LRU) hold ciphertext and no DEK, so they can only hash the bytes they actually have. The plaintext hash rides alongside as `shaPlain` and is sent only when encrypting. A dedup hit is honoured only if the server confirms the matched binary is itself encrypted. This matters: a previously-uploaded *plaintext* copy of the same app has the same plaintext hash, so a lookup blind to encryption state would hand back an unencrypted binary to someone who asked for encryption. The API applies the same predicate (devicecloud-dev/dcd#1168); this is the client refusing to depend on that, so an older or misbehaving deployment degrades into a redundant upload instead of a silent loss of encryption. Requires the API side of dcd#1168 for the encrypted path to dedup; against an older deployment the check finds nothing and behaviour is exactly as it is today. The unencrypted path is unchanged on the wire. One cost worth naming: an encrypted upload that misses now hashes twice (once plaintext, once ciphertext). Hashing is cheap next to encrypt + upload, and only the miss path pays it. Co-authored-by: Claude Opus 5 (1M context) --- src/config/flags/binary.flags.ts | 2 +- src/gateways/api-gateway.ts | 35 ++- src/methods.ts | 102 ++++++-- test/unit/encrypted-dedup.test.ts | 375 ++++++++++++++++++++++++++++++ 4 files changed, 485 insertions(+), 29 deletions(-) create mode 100644 test/unit/encrypted-dedup.test.ts diff --git a/src/config/flags/binary.flags.ts b/src/config/flags/binary.flags.ts index 6f92306..32dde91 100644 --- a/src/config/flags/binary.flags.ts +++ b/src/config/flags/binary.flags.ts @@ -27,6 +27,6 @@ export const binaryFlags = { encrypt: { type: 'boolean', description: - 'Encrypt the app binary, flow zip, and env vars before upload (client-side envelope encryption; each gets its own key). Can also be enabled with DCD_ENCRYPT=1.', + 'Encrypt the app binary, flow zip, and env vars before upload (client-side envelope encryption; each gets its own key). The binary is still deduplicated across runs, so an unchanged app is not re-uploaded. Can also be enabled with DCD_ENCRYPT=1.', }, } as const satisfies ArgsDef; diff --git a/src/gateways/api-gateway.ts b/src/gateways/api-gateway.ts index c06eaa8..2c3dfd9 100644 --- a/src/gateways/api-gateway.ts +++ b/src/gateways/api-gateway.ts @@ -257,14 +257,32 @@ export const ApiGateway = { return true; }, + /** + * Look for an already-uploaded binary to skip re-uploading. + * + * Two lookup keys, because encryption changes what is stable (dcd#1168). + * Unencrypted uploads pass `sha` (the hash of exactly what gets stored). + * Encrypted uploads pass `shaPlain` plus `encrypted: true`: their ciphertext is + * freshly keyed on every upload, so its hash never matches, and the plaintext + * hash is the only stable key. `sha` is deliberately not sent in that case — + * see the caller in methods.ts for why sending it would be unsafe. + * + * `encrypted` in the response reports whether the *matched* binary is stored + * encrypted, so the caller can verify the invariant it asked for instead of + * trusting the server to have applied the right predicate. + */ async checkForExistingUpload( baseUrl: string, auth: AuthContext, - sha: string, + lookup: { encrypted?: boolean; sha?: string; shaPlain?: string } | string, ) { + // Historically this took a bare sha string; keep that shape working. + const body = + typeof lookup === 'string' ? { sha: lookup } : { ...lookup }; + try { const res = await fetch(`${baseUrl}/uploads/checkForExistingUpload`, { - body: JSON.stringify({ sha }), + body: JSON.stringify(body), headers: { 'content-type': 'application/json', ...auth.headers, @@ -277,7 +295,9 @@ export const ApiGateway = { } return await parseJsonResponse< - paths['/uploads/checkForExistingUpload']['post']['responses']['201']['content']['application/json'] + paths['/uploads/checkForExistingUpload']['post']['responses']['201']['content']['application/json'] & { + encrypted?: boolean; + } >(res, 'Failed to check for existing upload'); } catch (error) { // Handle network-level errors (DNS, connection refused, timeout, etc.) @@ -342,9 +362,15 @@ export const ApiGateway = { metadata: TAppMetadata; path: string; sha?: string; + /** + * Hash of the PLAINTEXT, sent only for encrypted uploads (dcd#1168). Stored + * as `binaries.sha_plain` so later encrypted uploads of the same input can + * dedup; `sha` remains the ciphertext hash. + */ + shaPlain?: string; supabaseSuccess: boolean; }) { - const { baseUrl, auth, id, metadata, path, sha, supabaseSuccess, backblazeSuccess, bytes } = config; + const { baseUrl, auth, id, metadata, path, sha, shaPlain, supabaseSuccess, backblazeSuccess, bytes } = config; try { const res = await fetch(`${baseUrl}/uploads/finaliseUpload`, { body: JSON.stringify({ @@ -354,6 +380,7 @@ export const ApiGateway = { metadata, path, // This is tempPath for TUS uploads ...(sha ? { sha } : {}), + ...(shaPlain ? { shaPlain } : {}), supabaseSuccess, }), headers: { diff --git a/src/methods.ts b/src/methods.ts index ddf5425..e3401f8 100644 --- a/src/methods.ts +++ b/src/methods.ts @@ -173,23 +173,21 @@ export const uploadBinary = async (config: UploadBinaryConfig) => { // Prepare file for upload source = await prepareFileForUpload(filePath, debug, startTime); - // Encrypt before hashing/upload so the SHA, dedup check, and both uploaders - // all operate on ciphertext (binaries.sha = ciphertext hash, per #1138). - if (encrypt) { - const encrypted = await encryptUploadSource(source, apiUrl, debug); - enc = encrypted.enc; - encCleanupDir = encrypted.cleanupDir; - if (log) { - ux.info(colors.dim(`Encrypting binary before upload (KEK v${enc.kek})`)); - } - } - - // Calculate SHA hash - const sha = await calculateFileHash(source, debug, log); - - // Check for existing upload with same SHA - if (!ignoreShaCheck && sha) { - const { exists, binaryId } = await checkExistingUpload(apiUrl, auth, sha, debug); + // Hash the PLAINTEXT first, before any encryption (dcd#1168). Encryption + // uses a fresh random DEK per upload, so the ciphertext hash differs every + // time and cannot dedup — the plaintext hash is the only stable key. Doing it + // in this order also means a dedup hit skips the encryption work entirely, + // not just the upload. + const shaPlain = await calculateFileHash(source, debug, log); + + // Check for an existing upload before spending anything on encryption. + if (!ignoreShaCheck && shaPlain) { + const { exists, binaryId } = await checkExistingUpload( + apiUrl, + auth, + encrypt ? { encrypted: true, shaPlain } : { sha: shaPlain }, + debug, + ); if (exists && binaryId) { if (log) { @@ -203,8 +201,35 @@ export const uploadBinary = async (config: UploadBinaryConfig) => { } } + // Encrypt after the dedup check, so the SHA sent at finalise, and both + // uploaders, operate on ciphertext (binaries.sha = ciphertext hash, #1138). + if (encrypt) { + const encrypted = await encryptUploadSource(source, apiUrl, debug); + enc = encrypted.enc; + encCleanupDir = encrypted.cleanupDir; + if (log) { + ux.info(colors.dim(`Encrypting binary before upload (KEK v${enc.kek})`)); + } + } + + // Re-hash once encrypted: what lands in storage is the ciphertext, and every + // downstream verifySha hashes the bytes it actually holds (no DEK required). + const sha = encrypt ? await calculateFileHash(source, debug, false) : shaPlain; + // Perform the upload - const uploadId = await performUpload({ auth, apiUrl, debug, enc, filePath, sha, source, startTime }); + const uploadId = await performUpload({ + auth, + apiUrl, + debug, + enc, + filePath, + sha, + // Only encrypted uploads record a plaintext hash; it is what makes the + // next encrypted run of this binary dedupable. + shaPlain: encrypt ? shaPlain : undefined, + source, + startTime, + }); if (log) { ux.action.stop(colors.success('\n✓ Binary uploaded with ID: ') + formatId(uploadId)); @@ -425,30 +450,43 @@ async function calculateFileHash( } /** - * Checks if an upload with the same SHA already exists + * Checks whether a matching binary has already been uploaded. + * + * `lookup` is `{ sha }` for a plaintext upload, or `{ shaPlain, encrypted: true }` + * for an encrypted one (dcd#1168) — see {@link ApiGateway.checkForExistingUpload}. + * + * When asking as an encrypting client, a hit is only honoured if the server + * confirms the matched binary is itself encrypted. Any binary is a *plausible* + * match on plaintext hash, including a previously-uploaded plaintext copy of the + * same app, and reusing that would hand back an unencrypted binary while the user + * had asked for encryption. The server applies the same predicate; this is the + * client refusing to depend on that, so an older or misbehaving deployment + * degrades into a redundant upload rather than a silent loss of encryption. + * * @param apiUrl API base URL * @param auth AuthContext carrying request headers - * @param sha SHA-256 hash to check + * @param lookup Dedup key — plaintext hash for encrypted uploads, else the sha * @param debug Whether debug logging is enabled * @returns Promise resolving to object with exists flag and optional binaryId */ async function checkExistingUpload( apiUrl: string, auth: AuthContext, - sha: string, + lookup: { encrypted?: boolean; sha?: string; shaPlain?: string }, debug: boolean, ): Promise<{ binaryId?: string; exists: boolean }> { try { if (debug) { console.log('[DEBUG] Checking for existing upload with matching SHA...'); + console.log(`[DEBUG] Lookup: ${JSON.stringify(lookup)}`); console.log(`[DEBUG] Target endpoint: ${apiUrl}/uploads/checkForExistingUpload`); } const shaCheckStartTime = Date.now(); - const { appBinaryId, exists } = await ApiGateway.checkForExistingUpload( + const { appBinaryId, encrypted, exists } = await ApiGateway.checkForExistingUpload( apiUrl, auth, - sha as string, + lookup, ); if (debug) { @@ -459,6 +497,16 @@ async function checkExistingUpload( } } + if (exists && lookup.encrypted && encrypted !== true) { + if (debug) { + console.log( + '[DEBUG] Ignoring dedup hit: encryption was requested but the matched binary is not encrypted', + ); + } + + return { exists: false }; + } + return { binaryId: appBinaryId, exists }; } catch (error) { // Invalid credentials will fail every subsequent request — surface now @@ -493,6 +541,11 @@ interface PerformUploadConfig { enc?: BinaryEnvelope; filePath: string; sha: string | undefined; + /** + * Hash of the plaintext, set only for encrypted uploads (#1168). Persisted as + * `binaries.sha_plain` so the next encrypted upload of this binary can dedup. + */ + shaPlain?: string; source: UploadSource; startTime: number; } @@ -769,7 +822,7 @@ function validateUploadResults( * @returns Promise resolving to upload ID */ async function performUpload(config: PerformUploadConfig): Promise { - const { filePath, apiUrl, auth, enc, source, sha, debug, startTime } = config; + const { filePath, apiUrl, auth, enc, source, sha, shaPlain, debug, startTime } = config; // Request upload URL and paths const { id, tempPath, finalPath, b2 } = await requestUploadPaths(apiUrl, auth, filePath, source.size, debug); @@ -832,6 +885,7 @@ async function performUpload(config: PerformUploadConfig): Promise { path: tempPath, // sha is undefined when hash calculation failed — omit it explicitly ...(sha ? { sha } : {}), + ...(shaPlain ? { shaPlain } : {}), supabaseSuccess: supabaseResult.success, }); diff --git a/test/unit/encrypted-dedup.test.ts b/test/unit/encrypted-dedup.test.ts new file mode 100644 index 0000000..47e4c5c --- /dev/null +++ b/test/unit/encrypted-dedup.test.ts @@ -0,0 +1,375 @@ +import { expect } from 'chai'; +import { generateKeyPairSync } from 'node:crypto'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; + +import { ApiGateway } from '../../src/gateways/api-gateway.js'; +import { uploadBinary } from '../../src/methods.js'; +import type { AuthContext } from '../../src/types/domain/auth.types.js'; + +/** + * Dedup for client-side encrypted binaries (dcd#1168). + * + * Encryption uses a fresh random DEK per upload, so the ciphertext hash differs + * every time and cannot serve as a dedup key. The CLI therefore hashes the + * PLAINTEXT first, deduplicates on that, and only encrypts on a miss. + * + * The security-critical half is the invariant check: a plaintext row has the same + * plaintext hash as its encrypted twin, so a dedup hit must be rejected unless + * the server confirms the matched binary is itself encrypted. + */ + +const TEST_AUTH: AuthContext = { + mode: 'apiKey', + headers: { 'x-app-api-key': 'test-key' }, +}; + +const API = 'http://localhost:9999'; +const APK = path.join(process.cwd(), 'test/fixtures/wikipedia.apk'); + +const originalFetch = (global as any).fetch; + +type Call = { body: unknown; url: string }; + +/** + * Mock global.fetch with a per-endpoint handler map, recording every call. + * Any endpoint without a handler resolves to a 500 carrying a marker string, so + * a test can assert control reached it. + * @param handlers Map of URL substring to a response factory + * @returns The recorded call list + */ +function mockFetch( + handlers: Record { body: unknown; status: number }>, +): Call[] { + const calls: Call[] = []; + (global as any).fetch = async ( + input: URL | string, + init?: RequestInit, + ): Promise => { + const url = input.toString(); + calls.push({ + body: init?.body ? JSON.parse(String(init.body)) : null, + url, + }); + + const key = Object.keys(handlers).find((k) => url.includes(k)); + const { body, status } = key + ? handlers[key]() + : { body: { message: 'REACHED_UPLOAD_PATH' }, status: 500 }; + + return new Response(JSON.stringify(body), { + headers: { 'content-type': 'application/json' }, + status, + }); + }; + return calls; +} + +/** A throwaway X25519 public key so encryption can run without a pinned KEK. */ +function setTestKek() { + const { publicKey } = generateKeyPairSync('x25519'); + const raw = publicKey.export({ format: 'der', type: 'spki' }).subarray(12); + process.env.DCD_BINARY_KEK_PUBLIC = `1:${raw.toString('base64')}`; +} + +describe('encrypted binary dedup (#1168)', () => { + afterEach(() => { + (global as any).fetch = originalFetch; + delete process.env.DCD_BINARY_KEK_PUBLIC; + }); + + describe('ApiGateway.checkForExistingUpload wire format', () => { + it('sends shaPlain + encrypted (and NOT sha) for an encrypted lookup', async () => { + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'enc-binary', encrypted: true, exists: true }, + status: 200, + }), + }); + + const res = await ApiGateway.checkForExistingUpload(API, TEST_AUTH, { + encrypted: true, + shaPlain: 'plain-hash', + }); + + expect(calls[0].body).to.deep.equal({ + encrypted: true, + shaPlain: 'plain-hash', + }); + // The ciphertext hash is not known at dedup time and must not be implied. + expect(calls[0].body).to.not.have.property('sha'); + expect(res.encrypted).to.equal(true); + expect(res.appBinaryId).to.equal('enc-binary'); + }); + + it('sends a bare sha for an unencrypted lookup', async () => { + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'plain-binary', encrypted: false, exists: true }, + status: 200, + }), + }); + + await ApiGateway.checkForExistingUpload(API, TEST_AUTH, { + sha: 'cipher-or-plain-hash', + }); + + expect(calls[0].body).to.deep.equal({ sha: 'cipher-or-plain-hash' }); + }); + + it('still accepts a bare string sha (back-compat with the old signature)', async () => { + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'b', exists: true }, + status: 200, + }), + }); + + await ApiGateway.checkForExistingUpload(API, TEST_AUTH, 'legacy-sha'); + + expect(calls[0].body).to.deep.equal({ sha: 'legacy-sha' }); + }); + }); + + describe('uploadBinary dedup behaviour', () => { + it('reuses an encrypted match without encrypting or uploading', async () => { + setTestKek(); + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'enc-binary', encrypted: true, exists: true }, + status: 200, + }), + }); + + const id = await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: true, + filePath: APK, + log: false, + }); + + expect(id).to.equal('enc-binary'); + // Nothing beyond the dedup check should have been attempted. + expect(calls).to.have.lengthOf(1); + expect(calls[0].url).to.contain('checkForExistingUpload'); + }); + + it('REJECTS a plaintext match when encryption was requested', async () => { + // The trap: the plaintext row has the same plaintext hash, so the server + // could answer with it. Honouring that hit would return an unencrypted + // binary to a caller who asked for encryption. + setTestKek(); + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'plain-binary', encrypted: false, exists: true }, + status: 200, + }), + }); + + let returned: string | undefined; + try { + returned = await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: true, + filePath: APK, + log: false, + }); + } catch { + // The mocked upload path fails; reaching it at all is the assertion. + } + + // Proceeded past dedup into the upload path rather than silently handing + // back the unencrypted binary. + expect(returned).to.equal(undefined); + expect(calls.some((c) => c.url.includes('getBinaryUploadUrl'))).to.equal( + true, + ); + }); + + it('also rejects a hit when the server omits the encrypted field entirely', async () => { + // An older deployment predating #1168 has no encryption predicate on the + // lookup, so the absence of confirmation must be treated as a miss. + setTestKek(); + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'unknown-binary', exists: true }, + status: 200, + }), + }); + + let returned: string | undefined; + try { + returned = await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: true, + filePath: APK, + log: false, + }); + } catch { + // As above — the mocked upload path fails by design. + } + + expect(returned).to.equal(undefined); + expect(calls.some((c) => c.url.includes('getBinaryUploadUrl'))).to.equal( + true, + ); + }); + + it('dedups on the plaintext hash, so the key is stable across encrypted runs', async () => { + setTestKek(); + const seen: unknown[] = []; + mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'enc-binary', encrypted: true, exists: true }, + status: 200, + }), + }); + + for (let i = 0; i < 2; i++) { + + await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: true, + filePath: APK, + log: false, + }); + } + + // Reset and capture the lookup keys from two independent invocations. + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'enc-binary', encrypted: true, exists: true }, + status: 200, + }), + }); + await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: true, + filePath: APK, + log: false, + }); + await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: true, + filePath: APK, + log: false, + }); + for (const c of calls) { + seen.push((c.body as { shaPlain?: string }).shaPlain); + } + + expect(seen).to.have.lengthOf(2); + expect(seen[0]).to.be.a('string'); + // Identical input ⇒ identical lookup key, which is the whole point: the + // ciphertext hash would have differed on every run. + expect(seen[0]).to.equal(seen[1]); + }); + + it('leaves the unencrypted path deduping on the sha exactly as before', async () => { + const calls = mockFetch({ + checkForExistingUpload: () => ({ + body: { appBinaryId: 'plain-binary', encrypted: false, exists: true }, + status: 200, + }), + }); + + const id = await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: false, + filePath: APK, + log: false, + }); + + expect(id).to.equal('plain-binary'); + expect(calls[0].body).to.have.property('sha'); + expect(calls[0].body).to.not.have.property('encrypted'); + expect(calls[0].body).to.not.have.property('shaPlain'); + }); + + it('honours --ignore-sha-check by skipping the lookup altogether', async () => { + setTestKek(); + const calls = mockFetch({}); + + try { + await uploadBinary({ + apiUrl: API, + auth: TEST_AUTH, + encrypt: true, + filePath: APK, + ignoreShaCheck: true, + log: false, + }); + } catch { + // Upload path is mocked to fail; only the absence of a lookup matters. + } + + expect(calls.some((c) => c.url.includes('checkForExistingUpload'))).to.equal( + false, + ); + }); + }); + + describe('finalise payload', () => { + it('sends sha (ciphertext) alongside shaPlain for an encrypted upload', async () => { + const calls = mockFetch({ + finaliseUpload: () => ({ body: {}, status: 200 }), + }); + + await ApiGateway.finaliseUpload({ + auth: TEST_AUTH, + backblazeSuccess: true, + baseUrl: API, + bytes: 10, + id: 'upload-id', + + metadata: {} as any, + path: 'p', + sha: 'ciphertext-hash', + shaPlain: 'plaintext-hash', + supabaseSuccess: true, + }); + + expect(calls[0].body).to.include({ + sha: 'ciphertext-hash', + shaPlain: 'plaintext-hash', + }); + }); + + it('omits shaPlain for an unencrypted upload', async () => { + const calls = mockFetch({ + finaliseUpload: () => ({ body: {}, status: 200 }), + }); + + await ApiGateway.finaliseUpload({ + auth: TEST_AUTH, + backblazeSuccess: true, + baseUrl: API, + bytes: 10, + id: 'upload-id', + + metadata: {} as any, + path: 'p', + sha: 'plain-hash', + supabaseSuccess: true, + }); + + expect(calls[0].body).to.not.have.property('shaPlain'); + }); + }); +}); + +// Keep the fixture path assumption honest — every uploadBinary case depends on it. +describe('encrypted dedup test fixture', () => { + it('has the wikipedia.apk fixture available', () => { + expect(fs.existsSync(APK), `missing fixture: ${APK}`).to.equal(true); + expect(os.tmpdir()).to.be.a('string'); + }); +}); From 3888fc5cb0ea06828ed94c2d672e0215492f5147 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Mon, 3 Aug 2026 15:00:31 +0100 Subject: [PATCH 46/78] refactor(cloud): remove mitmproxy flags (#102) Drops the enterprise-only --mitmHost / --mitmPath flags and everything downstream of them: - environment.flags.ts: both flag definitions - cloud.ts: the arg reads and the "--mitmPath requires --mitmHost" check - test-submission.service.ts: TestSubmissionConfig fields, destructuring, and the two configPayload keys - run-cloud-test.ts: the doc comment listing what the MCP tool omits The submitted config payload is unchanged for existing runs: both keys were undefined when the flags were unset, and JSON.stringify drops undefined, so submissions that never passed them stay byte-identical. Co-authored-by: Claude Opus 5 (1M context) --- src/commands/cloud.ts | 8 -------- src/config/flags/environment.flags.ts | 10 ---------- src/mcp/tools/run-cloud-test.ts | 2 +- src/services/test-submission.service.ts | 6 ------ 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 37803b5..52cefbb 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -237,8 +237,6 @@ export const cloudCommand = defineCommand({ collectRepeatedFlag(rawArgs, ['--metadata', '-m']), false, ); - const mitmHost = args.mitmHost as string | undefined; - const mitmPath = args.mitmPath as string | undefined; const moropoApiKey = args['moropo-v1-api-key'] as string | undefined; const name = args.name as string | undefined; const orientation = validateEnum( @@ -286,10 +284,6 @@ export const cloudCommand = defineCommand({ ); } - if (mitmPath && !mitmHost) { - throw new CliError('--mitmPath requires --mitmHost to be set'); - } - if (jsonFileName && !jsonFileFlag) { throw new CliError('--json-file-name requires --json-file'); } @@ -817,8 +811,6 @@ export const cloudCommand = defineCommand({ logger: (m: string) => out(m), maestroVersion: resolvedMaestroVersion, metadata: mergedMetadata, - mitmHost, - mitmPath, name, orientation, raw: [], diff --git a/src/config/flags/environment.flags.ts b/src/config/flags/environment.flags.ts index e15c3b3..f20ba7d 100644 --- a/src/config/flags/environment.flags.ts +++ b/src/config/flags/environment.flags.ts @@ -17,16 +17,6 @@ export const environmentFlags = { description: 'Arbitrary key-value metadata to include with your test run (format: key=value, may be repeated)', }, - mitmHost: { - type: 'string', - description: - 'used for mitmproxy support, enterprise only, contact support if interested', - }, - mitmPath: { - type: 'string', - description: - 'used for mitmproxy support, enterprise only, contact support if interested', - }, 'moropo-v1-api-key': { type: 'string', description: 'API key for Moropo v1 integration', diff --git a/src/mcp/tools/run-cloud-test.ts b/src/mcp/tools/run-cloud-test.ts index d33ccdf..2cb7072 100644 --- a/src/mcp/tools/run-cloud-test.ts +++ b/src/mcp/tools/run-cloud-test.ts @@ -34,7 +34,7 @@ const sleep = (ms: number) => * `waitTimeoutSeconds`. * * Mirrors the `dcd cloud` command's submission path but headless: no Expo URL - * download, mitm, GitHub metadata, or JSON-file output. Use the CLI for those. + * download, GitHub metadata, or JSON-file output. Use the CLI for those. */ export function registerRunCloudTest(server: McpServer): void { server.registerTool( diff --git a/src/services/test-submission.service.ts b/src/services/test-submission.service.ts index 678c66f..dc04b24 100644 --- a/src/services/test-submission.service.ts +++ b/src/services/test-submission.service.ts @@ -41,8 +41,6 @@ export interface TestSubmissionConfig { maestroChromeOnboarding?: boolean; maestroVersion: string; metadata?: string[]; - mitmHost?: string; - mitmPath?: string; name?: string; orientation?: string; raw?: unknown; @@ -93,8 +91,6 @@ export class TestSubmissionService { maestroVersion, deviceLocale, orientation, - mitmHost, - mitmPath, retry, continueOnFailure = true, report, @@ -283,8 +279,6 @@ export class TestSubmissionService { deviceLocale, googlePlay, maestroVersion, - mitmHost, - mitmPath, orientation, raw: JSON.stringify(raw), report, From f7934b0743a4bf561f876082f124a87acc41041a Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:32:38 +0100 Subject: [PATCH 47/78] fix(deps): resolve three new transitive security advisories (#106) pnpm audit --audit-level moderate started failing CI on every branch, including a clean dev - these advisories were published after dev's last green run, so nothing in the tree had to change to break it. - brace-expansion: the existing override pinned 5.0.8, which GHSA-rgw5-rvv9-x895 now covers (vulnerable <5.0.9). Range and target bumped to 5.0.9. - fast-uri (via @modelcontextprotocol/sdk > ajv): GHSA-7p8r-x3mc-p8w7, pinned 3.1.5. - hono (via @modelcontextprotocol/sdk): GHSA-8j4g-w8fx-2239, pinned 4.12.34. All three pinned to the lowest patched version within their current major, following the existing exact-pin overrides. A first pass using open '>=' ranges pulled fast-uri 4.1.2 - a major jump inside ajv - for no benefit; these land on 3.1.5 and 4.12.34 instead. audit clean; lint, typecheck, build and all 194 tests pass. fast-uri and hono are both MCP SDK dependencies, so also smoke-tested dist/mcp/index.js over stdio: initialize and tools/list both return, all five tools registered. --- package.json | 4 +++- pnpm-lock.yaml | 40 +++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index c46c51c..83e841f 100644 --- a/package.json +++ b/package.json @@ -105,9 +105,11 @@ "diff@>=6.0.0": "8.0.3", "brace-expansion@<1.1.16": "1.1.16", "brace-expansion@>=2.0.0 <2.0.3": "2.0.3", - "brace-expansion@>=3.0.0 <5.0.8": "5.0.8", + "brace-expansion@>=3.0.0 <5.0.9": "5.0.9", "ws@>=8.0.0 <8.21.0": "8.21.0", "esbuild@<0.28.1": ">=0.28.1", + "fast-uri@>=3.0.0 <3.1.5": "3.1.5", + "hono@>=4.0.0 <4.12.34": "4.12.34", "micromatch>picomatch": "^2.3.2", "tinyglobby>picomatch": "^4.0.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d9a3f8..a75f3d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,11 @@ overrides: diff@>=6.0.0: 8.0.3 brace-expansion@<1.1.16: 1.1.16 brace-expansion@>=2.0.0 <2.0.3: 2.0.3 - brace-expansion@>=3.0.0 <5.0.8: 5.0.8 + brace-expansion@>=3.0.0 <5.0.9: 5.0.9 ws@>=8.0.0 <8.21.0: 8.21.0 esbuild@<0.28.1: '>=0.28.1' + fast-uri@>=3.0.0 <3.1.5: 3.1.5 + hono@>=4.0.0 <4.12.34: 4.12.34 micromatch>picomatch: ^2.3.2 tinyglobby>picomatch: ^4.0.4 @@ -344,7 +346,7 @@ packages: resolution: {integrity: sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==} engines: {node: '>=20'} peerDependencies: - hono: ^4 + hono: 4.12.34 '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} @@ -589,8 +591,8 @@ packages: resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} engines: {node: '>= 5.10.0'} - brace-expansion@5.0.8: - resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} braces@3.0.3: @@ -906,8 +908,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.4: - resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -1035,8 +1037,8 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hono@4.12.32: - resolution: {integrity: sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==} + hono@4.12.34: + resolution: {integrity: sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==} engines: {node: '>=16.9.0'} http-errors@2.0.1: @@ -1919,9 +1921,9 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@hono/node-server@2.0.12(hono@4.12.32)': + '@hono/node-server@2.0.12(hono@4.12.34)': dependencies: - hono: 4.12.32 + hono: 4.12.34 '@humanfs/core@0.19.2': dependencies: @@ -1954,7 +1956,7 @@ snapshots: '@modelcontextprotocol/sdk@1.30.0(zod@4.4.3)': dependencies: - '@hono/node-server': 2.0.12(hono@4.12.32) + '@hono/node-server': 2.0.12(hono@4.12.34) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -1964,7 +1966,7 @@ snapshots: eventsource-parser: 3.1.0 express: 5.2.1 express-rate-limit: 8.6.1(express@5.2.1) - hono: 4.12.32 + hono: 4.12.34 jose: 6.2.5 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -2164,7 +2166,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.4 + fast-uri: 3.1.5 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -2206,7 +2208,7 @@ snapshots: dependencies: big-integer: 1.6.52 - brace-expansion@5.0.8: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -2575,7 +2577,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.4: {} + fast-uri@3.1.5: {} fast-wrap-ansi@0.2.2: dependencies: @@ -2697,7 +2699,7 @@ snapshots: he@1.2.0: {} - hono@4.12.32: {} + hono@4.12.34: {} http-errors@2.0.1: dependencies: @@ -2873,15 +2875,15 @@ snapshots: minimatch@10.2.3: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimatch@10.2.6: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimatch@9.0.7: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimist@1.2.8: {} From 2fd4bdfd66e540d36c3c1f18c28d0afd49941687 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:33:17 +0100 Subject: [PATCH 48/78] chore: regenerate schema types from the current API swagger (#105) * fix(deps): resolve three new transitive security advisories pnpm audit --audit-level moderate started failing CI on every branch, including a clean dev - these advisories were published after dev's last green run, so nothing in the tree had to change to break it. - brace-expansion: the existing override pinned 5.0.8, which GHSA-rgw5-rvv9-x895 now covers (vulnerable <5.0.9). Range and target bumped to 5.0.9. - fast-uri (via @modelcontextprotocol/sdk > ajv): GHSA-7p8r-x3mc-p8w7, pinned 3.1.5. - hono (via @modelcontextprotocol/sdk): GHSA-8j4g-w8fx-2239, pinned 4.12.34. All three pinned to the lowest patched version within their current major, following the existing exact-pin overrides. A first pass using open '>=' ranges pulled fast-uri 4.1.2 - a major jump inside ajv - for no benefit; these land on 3.1.5 and 4.12.34 instead. audit clean; lint, typecheck, build and all 194 tests pass. fast-uri and hono are both MCP SDK dependencies, so also smoke-tested dist/mcp/index.js over stdio: initialize and tools/list both return, all five tools registered. * chore: regenerate schema types from the current API swagger The generated types were stale by several releases (the compatibility example still showed latestVersion 2.1.0, defaultVersion 1.41.0) - they had not been regenerated since before the 2.5/2.6 additions. Regenerated with openapi-typescript against the swagger carrying Maestro 2.7.0/2.8.0, per the repo rule that this file is never hand-edited. Doc-comment and shape churn only; typecheck, build and the full test suite pass. --- src/types/generated/schema.types.ts | 4411 +++++++++++++++++++++------ 1 file changed, 3541 insertions(+), 870 deletions(-) diff --git a/src/types/generated/schema.types.ts b/src/types/generated/schema.types.ts index f1bcbc0..1fd27da 100644 --- a/src/types/generated/schema.types.ts +++ b/src/types/generated/schema.types.ts @@ -70,6 +70,22 @@ export interface paths { patch?: never; trace?: never; }; + "/uploads/getFlowUploadUrl": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["UploadsController_getFlowUploadUrl"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/uploads/flow": { parameters: { query?: never; @@ -86,6 +102,55 @@ export interface paths { patch?: never; trace?: never; }; + "/uploads/submitFlowTest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Client-direct flow submission (JSON). The flow zip has already been + * uploaded straight to storage via getFlowUploadUrl, so nothing is buffered + * in API memory here — we just create the uploads row from the stored + * reference and run the shared submission flow. + */ + post: operations["UploadsController_submitFlowTest"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/uploads/estimateMatrix": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Dry-run cost + cell-count estimate for a (possibly device-matrix) + * submission. Runs the exact same resolve → validate → fan-out → price core as + * the submit path (so the quote equals the charge) but never persists and + * never touches credits. Lets the CLI print the cell count and estimated cost, + * and surface validation errors, before uploading the flow ZIP. + * + * The dollar estimate is exact for non-Google-Play cells; a Google Play + * column's price is path-dependent until #1100 unifies the parallel and + * sequential Play tiering. + */ + post: operations["UploadsController_estimateMatrix"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/uploads/retryTest": { parameters: { query?: never; @@ -166,23 +231,23 @@ export interface paths { patch?: never; trace?: never; }; - "/results/{uploadId}": { + "/org/paddle-webhook": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["ResultsController_getResults"]; + get?: never; put?: never; - post?: never; + post: operations["OrgController_handlePaddleWebhook"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/results/{uploadId}/download": { + "/org/update-name": { parameters: { query?: never; header?: never; @@ -191,14 +256,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["ResultsController_getTestRunArtifacts"]; + post: operations["OrgController_updateOrgName"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/results/notify/{uploadId}": { + "/org/invite-team-member": { parameters: { query?: never; header?: never; @@ -207,114 +272,110 @@ export interface paths { }; get?: never; put?: never; - post: operations["ResultsController_notifyTestRunComplete"]; + post: operations["OrgController_inviteTeamMember"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/results/{uploadId}/report": { + "/org/accept-invite": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["ResultsController_downloadReport"]; + get?: never; put?: never; - post?: never; + post: operations["OrgController_acceptInvite"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/results/{uploadId}/html-report": { + "/org/revoke-invite": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["ResultsController_downloadHtmlReport"]; + get?: never; put?: never; - post?: never; + post: operations["OrgController_revokeInvite"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/results/{resultId}/html-report-single": { + "/org/change-team-member-role": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["ResultsController_downloadSingleHtmlReport"]; + get?: never; put?: never; - post?: never; + post: operations["OrgController_changeTeamMemberRole"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/results/compatibility/data": { + "/org/remove-team-member": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["ResultsController_getCompatibilityData"]; + get?: never; put?: never; - post?: never; + post: operations["OrgController_removeTeamMember"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/allure/{uploadId}/download": { + "/org/leave-team": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Download Allure report as HTML - * @description Downloads a single-file Allure report as HTML containing all test results. Report is generated once and stored in Supabase Storage for subsequent downloads. - */ - get: operations["AllureController_downloadAllureReport"]; + get?: never; put?: never; - post?: never; + post: operations["OrgController_leaveTeam"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/webhooks": { + "/org/delete-team": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["WebhooksController_getWebhook"]; + get?: never; put?: never; - post: operations["WebhooksController_setWebhook"]; - delete: operations["WebhooksController_deleteWebhook"]; + post: operations["OrgController_deleteTeam"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/webhooks/regenerate-secret": { + "/org/subscriptions": { parameters: { query?: never; header?: never; @@ -323,14 +384,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["WebhooksController_regenerateWebhookSecret"]; + post: operations["OrgController_getAllSubscriptions"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/webhooks/test": { + "/org/update-overage-limit": { parameters: { query?: never; header?: never; @@ -339,14 +400,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["WebhooksController_testWebhook"]; + post: operations["OrgController_updateOverageLimit"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/org/paddle-webhook": { + "/org/usage-history": { parameters: { query?: never; header?: never; @@ -355,14 +416,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["OrgController_handlePaddleWebhook"]; + post: operations["OrgController_getUsageHistory"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/org/update-name": { + "/org/update-gpu-retries": { parameters: { query?: never; header?: never; @@ -371,14 +432,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["OrgController_updateOrgName"]; + post: operations["OrgController_updateGpuRetries"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/org/invite-team-member": { + "/org/update-billing-email": { parameters: { query?: never; header?: never; @@ -387,14 +448,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["OrgController_inviteTeamMember"]; + post: operations["OrgController_updateBillingEmail"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/org/accept-invite": { + "/org/invoices": { parameters: { query?: never; header?: never; @@ -403,14 +464,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["OrgController_acceptInvite"]; + post: operations["OrgController_getInvoices"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/org/subscriptions": { + "/org/invoice-url": { parameters: { query?: never; header?: never; @@ -419,14 +480,14 @@ export interface paths { }; get?: never; put?: never; - post: operations["OrgController_getAllSubscriptions"]; + post: operations["OrgController_getInvoiceUrl"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/org/update-overage-limit": { + "/org/bill-daily-overages": { parameters: { query?: never; header?: never; @@ -435,30 +496,30 @@ export interface paths { }; get?: never; put?: never; - post: operations["OrgController_updateOverageLimit"]; + post: operations["OrgController_billDailyOverages"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/org/usage-history": { + "/results/{uploadId}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + get: operations["ResultsController_getResults"]; put?: never; - post: operations["OrgController_getUsageHistory"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/frontend/check-domain-saml": { + "/results/{uploadId}/download": { parameters: { query?: never; header?: never; @@ -467,37 +528,37 @@ export interface paths { }; get?: never; put?: never; - post: operations["FrontendController_checkDomainSaml"]; + post: operations["ResultsController_getTestRunArtifacts"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/frontend/validate-email": { + "/results/{uploadId}/report": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + get: operations["ResultsController_downloadReport"]; put?: never; - post: operations["FrontendController_validateEmail"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/health": { + "/results/{uploadId}/html-report": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["HealthController_health"]; + get: operations["ResultsController_downloadHtmlReport"]; put?: never; post?: never; delete?: never; @@ -506,30 +567,36 @@ export interface paths { patch?: never; trace?: never; }; - "/billing/create-subscription": { + "/results/{resultId}/html-report-single": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + get: operations["ResultsController_downloadSingleHtmlReport"]; put?: never; - post: operations["BillingController_createSubscription"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/stats/marketing": { + "/results/{uploadId}/artifacts-bundle": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["StatsController_getMarketingStats"]; + /** + * CDN-Worker bundle manifests (dcd#1137). These return a signed manifest the + * client POSTs to the Worker, which streams the ZIP from B2 — the artifact + * bytes bypass the API. A `501` means the CDN Worker isn't configured on this + * deployment; the client falls back to the inline download endpoints. + */ + get: operations["ResultsController_getArtifactsBundleManifest"]; put?: never; post?: never; delete?: never; @@ -538,887 +605,3582 @@ export interface paths { patch?: never; trace?: never; }; -} -export type webhooks = Record; -export interface components { - schemas: { - IDBResult: { - binary_upload_id: string; - cost: number | null; - created_at: string; - env: Record; - id: number; - org_id: number; - platform: string; - simulator_name: string; - status: string; - test_file_name: string; - test_upload_id: string; - }; - IGetBinaryUploadUrlArgs: { - /** - * @description Platform for the binary upload (ios or android) - * @enum {string} - */ - platform: "ios" | "android"; - /** @description File size in bytes (optional, for Backblaze upload strategy) */ - fileSize?: number; - /** @description Whether client uses TUS resumable uploads (true for new clients, undefined/false for legacy) */ - useTus?: boolean; + "/results/{uploadId}/report-bundle": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - B2SimpleUpload: { - uploadUrl: string; - authorizationToken: string; + get: operations["ResultsController_getReportBundleManifest"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/results/{resultId}/report-bundle-single": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - B2UploadPartUrl: { - uploadUrl: string; - authorizationToken: string; + get: operations["ResultsController_getSingleReportBundleManifest"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/results/compatibility/data": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - B2LargeUpload: { - fileId: string; - fileName: string; - uploadPartUrls: components["schemas"]["B2UploadPartUrl"][]; + get: operations["ResultsController_getCompatibilityData"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/allure/{uploadId}/download": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - B2UploadStrategy: { - /** @enum {string} */ - strategy: "simple" | "large"; - simple?: components["schemas"]["B2SimpleUpload"]; - large?: components["schemas"]["B2LargeUpload"]; + /** + * Download Allure report as HTML + * @description Downloads a single-file Allure report as HTML containing all test results. Report is generated once and stored in Supabase Storage for subsequent downloads. + */ + get: operations["AllureController_downloadAllureReport"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - IGetBinaryUploadUrlResponse: { - /** @description Temporary upload path in uploads/ folder for TUS upload */ - path: string; - /** @description Temporary upload path (same as path) */ - tempPath: string; - /** @description Final path where file will be moved after upload completes */ - finalPath: string; - /** @description Upload ID */ - id: string; - /** @description Backblaze upload strategy if configured */ + get: operations["WebhooksController_getWebhook"]; + put?: never; + post: operations["WebhooksController_setWebhook"]; + delete: operations["WebhooksController_deleteWebhook"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks/regenerate-secret": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["WebhooksController_regenerateWebhookSecret"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks/test": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["WebhooksController_testWebhook"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/slack/oauth/start": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SlackController_oauthStart"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/slack": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SlackController_getConnection"]; + put?: never; + post?: never; + delete: operations["SlackController_disconnect"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/slack/channels": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SlackController_getChannels"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/slack/config": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SlackController_setConfig"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/slack/test": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SlackController_test"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/github/oauth/start": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["GithubController_oauthStart"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/github": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["GithubController_getConnection"]; + put?: never; + post?: never; + delete: operations["GithubController_disconnect"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/notices": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Active notify notices (deprecation/warn/info/marketing) for the calling + * client. Block notices are never returned here — they are enforced + * server-side at submit time. The consumer applies any `match` gating it alone + * can evaluate (e.g. the selected device version). + */ + get: operations["NoticesController_getNotices"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api-keys": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["ApiKeysController_list"]; + put?: never; + post: operations["ApiKeysController_create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api-keys/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["ApiKeysController_revoke"]; + options?: never; + head?: never; + patch: operations["ApiKeysController_update"]; + trace?: never; + }; + "/api-keys/{id}/rotate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["ApiKeysController_rotate"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ip-addresses": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["NetworkController_getIpAddresses"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/check-domain-saml": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["FrontendController_checkDomainSaml"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/validate-email": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["FrontendController_validateEmail"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/binary-download-url": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["FrontendController_getBinaryDownloadUrl"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/binaries/{binaryId}/download": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["FrontendController_downloadBinary"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/artifact-download-url": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["FrontendController_getArtifactDownloadUrl"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/result-detail/{resultId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["FrontendController_getResultDetail"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/logs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["FrontendController_ingestLogs"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/frontend/logs/anon": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["FrontendController_ingestLogsAnonymous"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["HealthController_health"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/billing/create-subscription": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["BillingController_createSubscription"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/billing/update-subscription": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["BillingController_updateSubscription"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/stats/marketing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["StatsController_getMarketingStats"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/flows": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["FlowsController_getFlows"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/flows/runs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["FlowsController_getFlowRuns"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/live": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["LiveController_createSession"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/live/{identifier}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["LiveController_getSession"]; + put?: never; + post?: never; + delete: operations["LiveController_stopSession"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/live/{identifier}/exec": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["LiveController_execTest"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/live/{identifier}/commands/{commandId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["LiveController_getCommandStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/live/{identifier}/keepalive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["LiveController_keepalive"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/live/{identifier}/install": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["LiveController_installBinary"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me/orgs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["MeController_listOrgs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me/sessions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** The caller's active auth sessions (for the Settings "Active sessions" panel). */ + get: operations["MeController_listSessions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me/sessions/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * Revoke one of the caller's active auth sessions (per-row "Sign out" in the panel). JWT-authed; + * the service scopes the delete to the caller's own id, so you can only kill your own sessions. + * Throttled since it mutates auth state. + */ + delete: operations["MeController_revokeSession"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me/personal-team": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Provision a personal team for the caller — backs the "Create a personal team" button on the + * no-teams empty state (a user who left/was removed from their last org). Throttled. + */ + post: operations["MeController_createPersonalTeam"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me/team": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create an additional, named team for the caller — backs the "+ New team" org-switcher action. + * Always creates (vs /personal-team which is idempotent). Throttled. + */ + post: operations["MeController_createTeam"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me/delete": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Irreversibly delete the caller's own account. JWT-authed; the body must echo the + * account email as a defence-in-depth confirmation (the UI also type-confirms). Throttled + * hard since it's destructive. + */ + post: operations["MeController_deleteAccount"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/cli-login/handoff": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["CliLoginController_handoff"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/cli-login/claim": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["CliLoginController_claim"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/cli/logs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["CliLogsController_ingestLogs"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/email-change/start": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["EmailChangeController_start"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/email-change/verify-current": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["EmailChangeController_verifyCurrent"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/email-change/verify-new": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["EmailChangeController_verifyNew"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/email-change/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["EmailChangeController_cancel"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + IDBResult: { + binary_upload_id: string; + cost: number | null; + created_at: string; + env: Record; + id: number; + org_id: number; + platform: string; + simulator_name: string; + status: string; + test_file_name: string; + test_upload_id: string; + }; + IGetBinaryUploadUrlArgs: { + /** + * @description Platform for the binary upload (ios or android) + * @enum {string} + */ + platform: "ios" | "android"; + /** @description File size in bytes (optional, for Backblaze upload strategy) */ + fileSize?: number; + /** @description Whether client uses TUS resumable uploads (true for new clients, undefined/false for legacy) */ + useTus?: boolean; + }; + B2SimpleUpload: { + uploadUrl: string; + authorizationToken: string; + }; + B2UploadPartUrl: { + uploadUrl: string; + authorizationToken: string; + }; + B2LargeUpload: { + fileId: string; + fileName: string; + uploadPartUrls: components["schemas"]["B2UploadPartUrl"][]; + }; + B2UploadStrategy: { + /** @enum {string} */ + strategy: "simple" | "large"; + simple?: components["schemas"]["B2SimpleUpload"]; + large?: components["schemas"]["B2LargeUpload"]; + }; + IGetBinaryUploadUrlResponse: { + /** + * @description Temporary upload path in uploads/ folder for TUS upload + * @example uploads/123e4567-e89b-12d3-a456-426614174000/123e4567-e89b-12d3-a456-426614174000.apk + */ + path: string; + /** + * @description Temporary upload path (same as path) + * @example uploads/123e4567-e89b-12d3-a456-426614174000/123e4567-e89b-12d3-a456-426614174000.apk + */ + tempPath: string; + /** + * @description Final path where file will be moved after upload completes + * @example 1/binaries/android/123e4567-e89b-12d3-a456-426614174000.apk + */ + finalPath: string; + /** + * @description Upload ID + * @example 123e4567-e89b-12d3-a456-426614174000 + */ + id: string; + /** @description Backblaze upload strategy if configured */ b2?: components["schemas"]["B2UploadStrategy"]; /** @description Signed upload URL token for legacy clients (deprecated) */ token?: string; }; - ICheckForExistingUploadArgs: { - /** @description SHA-256 hash of the binary file */ - sha: string; + ICheckForExistingUploadArgs: { + /** @description SHA-256 hash of the binary file as uploaded. Required unless `encrypted` is true, in which case `shaPlain` is the lookup key (the ciphertext hash is not yet known at dedup time). */ + sha?: string; + /** @description SHA-256 hash of the PLAINTEXT binary. The lookup key when `encrypted` is true (#1168): encrypted uploads wrap under a fresh random DEK, so the ciphertext hash differs on every upload of identical input and cannot dedup. */ + shaPlain?: string; + /** + * @description Whether the caller intends to upload an encrypted binary. When true the lookup uses `shaPlain` and only ever matches rows that carry an encryption envelope, so an encrypting client can never be handed back a plaintext binary. + * @default false + */ + encrypted: boolean; + }; + ICheckForExistingUploadResponse: { + appBinaryId: string; + exists: boolean; + /** + * @description Whether the matched binary is stored encrypted (#1168). Lets an encrypting + * client assert the invariant it cares about client-side instead of trusting + * the server to have applied the right predicate — so an older or misbehaving + * deployment cannot quietly hand it a plaintext binary. + */ + encrypted?: boolean; + }; + IFinaliseUploadArgs: { + /** @description Unique upload identifier */ + id: string; + /** @description Storage path for the uploaded file */ + path: string; + /** @description File metadata (bundle ID, package name, platform) - required for new clients */ + metadata?: Record; + /** @description SHA-256 hash of the file - required for new clients */ + sha?: string; + /** @description SHA-256 hash of the PLAINTEXT file. Sent only by clients uploading an encrypted binary (#1168), where `sha` is the ciphertext hash; persisted as binaries.sha_plain so later encrypted uploads of the same input can dedup. */ + shaPlain?: string; + /** + * @description Whether the Supabase upload was successful + * @default true + */ + supabaseSuccess: boolean; + /** + * @description Whether the Backblaze upload was successful + * @default false + */ + backblazeSuccess: boolean; + /** @description Whether client uses TUS resumable uploads (true for new clients, undefined/false for legacy) */ + useTus?: boolean; + /** @description File size in bytes */ + bytes?: number; + }; + IFinaliseUploadResponse: Record; + IFinishLargeFileArgs: { + /** + * @description The Backblaze file ID from the large file upload + * @example abc123xyz + */ + fileId: string; + /** + * @description Array of SHA1 hashes for each uploaded part + * @example [ + * "sha1hash1", + * "sha1hash2" + * ] + */ + partSha1Array: string[]; + }; + IFinishLargeFileResponse: { + success: boolean; + result: Record; + }; + IGetFlowUploadUrlArgs: { + /** @description File size in bytes (optional, for Backblaze upload strategy) */ + fileSize?: number; + /** @description Whether client uses TUS resumable uploads (true for new clients, undefined/false for legacy) */ + useTus?: boolean; + }; + ICreateTestUploadArgs: { + testFileNames?: string; + sequentialFlows?: string; + /** @enum {string} */ + androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36"; + /** @enum {string} */ + androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet"; + apiKey?: string; + apiUrl?: string; + appBinaryId: string; + appFile?: string; + env: string; + /** @enum {string} */ + iOSVersion?: "16" | "17" | "18" | "26"; + /** @enum {string} */ + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; + platform?: string; + googlePlay?: boolean; + config: string; + name?: string; + /** @enum {string} */ + runnerType?: "m4" | "m1" | "default" | "gpu1" | "cpu1"; + metadata?: string; + workspaceConfig?: string; + flowMetadata?: string; + testFileOverrides?: string; + /** @description JSON array of explicit device configs forming the upload device matrix. Every flow that does not name its own device runs once per entry. Each entry names exactly one validated cell and must match the binary platform — there is no cross-product expansion. iOS: {"iOSDevice":"iphone-16","iOSVersion":"18"}. Android: {"androidDevice":"pixel-7","androidApiLevel":"34","googlePlay":true}. Omit for single-device (legacy) behaviour. */ + deviceMatrix?: string; + /** @description SHA-256 hash of the flow ZIP file */ + sha?: string; + /** @description JSON-encoded envelope { v, kek, wrapped_key } when the flow ZIP was client-side encrypted (#1151). Stored as uploads.metadata.enc; the flow zip carries its own per-upload DEK, distinct from the binary. */ + enc?: string; + /** @description Size of the flow ZIP file in bytes */ + bytes?: number; + /** + * Format: binary + * @description This file must be a zip file + */ + file: string; + }; + ISubmitFlowTestArgs: { + testFileNames?: string; + sequentialFlows?: string; + /** @enum {string} */ + androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36"; + /** @enum {string} */ + androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet"; + apiKey?: string; + apiUrl?: string; + appBinaryId: string; + appFile?: string; + env: string; + /** @enum {string} */ + iOSVersion?: "16" | "17" | "18" | "26"; + /** @enum {string} */ + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; + platform?: string; + googlePlay?: boolean; + config: string; + name?: string; + /** @enum {string} */ + runnerType?: "m4" | "m1" | "default" | "gpu1" | "cpu1"; + metadata?: string; + workspaceConfig?: string; + flowMetadata?: string; + testFileOverrides?: string; + /** @description JSON array of explicit device configs forming the upload device matrix. Every flow that does not name its own device runs once per entry. Each entry names exactly one validated cell and must match the binary platform — there is no cross-product expansion. iOS: {"iOSDevice":"iphone-16","iOSVersion":"18"}. Android: {"androidDevice":"pixel-7","androidApiLevel":"34","googlePlay":true}. Omit for single-device (legacy) behaviour. */ + deviceMatrix?: string; + /** @description SHA-256 hash of the flow ZIP file */ + sha?: string; + /** @description JSON-encoded envelope { v, kek, wrapped_key } when the flow ZIP was client-side encrypted (#1151). Stored as uploads.metadata.enc; the flow zip carries its own per-upload DEK, distinct from the binary. */ + enc?: string; + /** @description Size of the flow ZIP file in bytes */ + bytes?: number; + /** @description Flow upload identifier returned by getFlowUploadUrl */ + id: string; + /** @description Storage path where the flow zip was uploaded (tempPath for TUS, finalPath for legacy signed URL) */ + path: string; + /** + * @description Whether the Supabase upload was successful + * @default true + */ + supabaseSuccess: boolean; + /** + * @description Whether the Backblaze upload was successful + * @default false + */ + backblazeSuccess: boolean; + /** @description Whether client uses TUS resumable uploads (true for new clients, undefined/false for legacy) */ + useTus?: boolean; + }; + IFlowTestParams: { + testFileNames?: string; + sequentialFlows?: string; + /** @enum {string} */ + androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36"; + /** @enum {string} */ + androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet"; + apiKey?: string; + apiUrl?: string; + appBinaryId: string; + appFile?: string; + env: string; + /** @enum {string} */ + iOSVersion?: "16" | "17" | "18" | "26"; + /** @enum {string} */ + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; + platform?: string; + googlePlay?: boolean; + config: string; + name?: string; + /** @enum {string} */ + runnerType?: "m4" | "m1" | "default" | "gpu1" | "cpu1"; + metadata?: string; + workspaceConfig?: string; + flowMetadata?: string; + testFileOverrides?: string; + /** @description JSON array of explicit device configs forming the upload device matrix. Every flow that does not name its own device runs once per entry. Each entry names exactly one validated cell and must match the binary platform — there is no cross-product expansion. iOS: {"iOSDevice":"iphone-16","iOSVersion":"18"}. Android: {"androidDevice":"pixel-7","androidApiLevel":"34","googlePlay":true}. Omit for single-device (legacy) behaviour. */ + deviceMatrix?: string; + /** @description SHA-256 hash of the flow ZIP file */ + sha?: string; + /** @description JSON-encoded envelope { v, kek, wrapped_key } when the flow ZIP was client-side encrypted (#1151). Stored as uploads.metadata.enc; the flow zip carries its own per-upload DEK, distinct from the binary. */ + enc?: string; + /** @description Size of the flow ZIP file in bytes */ + bytes?: number; + }; + IRetryTestArgs: { + /** @description ID of a specific result to retry. Either resultId or uploadId must be provided, but not both. */ + resultId?: number; + /** @description ID of an upload to retry all failed tests for. Either resultId or uploadId must be provided, but not both. */ + uploadId?: string; + }; + ICancelTestArgs: { + /** @description ID of a specific result to cancel. Either resultId or uploadId must be provided, but not both. */ + resultId?: number; + /** @description ID of an upload to cancel all pending results for. Either resultId or uploadId must be provided, but not both. */ + uploadId?: string; + }; + UpdateOrgNameDto: { + /** + * @description Organization ID + * @example 123 + */ + orgId: number; + /** + * @description Organization name + * @example Acme Corporation + */ + name: string; + }; + InviteTeamMemberDto: { + /** + * @description Email address to invite + * @example teammate@example.com + */ + inviteEmail: string; + /** + * @description Invite acceptance link shown in the email + * @example https://app.devicecloud.dev/login?invite_email=... + */ + link: string; + /** + * @description Organization ID + * @example 1 + */ + orgId: string; + /** @description Organization name shown in the email */ + orgName: string; + }; + AcceptInviteDto: { + /** + * @description Organization ID + * @example 1 + */ + orgId: string; + /** + * @description User email address + * @example user@example.com + */ + email: string; + }; + RevokeInviteDto: { + /** + * @description Organization ID + * @example 1 + */ + orgId: string; + /** + * @description Email of the pending invite to revoke + * @example teammate@example.com + */ + email: string; + }; + ChangeRoleDto: { + /** + * @description Organization ID + * @example 1 + */ + orgId: string; + /** + * @description Email of the team member whose role is being changed + * @example teammate@example.com + */ + email: string; + /** + * @description New role to assign + * @example admin + * @enum {string} + */ + newRole: "admin" | "standard" | "owner"; + }; + RemoveMemberDto: { + /** + * @description Organization ID + * @example 1 + */ + orgId: string; + /** + * @description Email of the team member to remove + * @example teammate@example.com + */ + email: string; + }; + LeaveTeamDto: { + /** + * @description Organization ID to leave + * @example 1 + */ + orgId: string; + }; + DeleteTeamDto: { + /** + * @description Organization ID to delete + * @example 1 + */ + orgId: string; + }; + TResultResponse: { + id: number; + test_file_name: string; + status: string; + retry_of?: number; + fail_reason?: string; + duration_seconds?: number; + simulator_name?: string; + config?: Record; + }; + TFlowSummaryResponse: { + flow_name: string; + file_name: string; + last_run_at: string; + total_runs: number; + passed_runs: number; + failed_runs: number; + pass_rate: number; + avg_duration: number; + daily_data: Record; + tags: string[]; + }; + TFlowRunItem: { + id: number; + status: string; + createdAt: string; + durationSeconds: number | null; + failReason: string | null; + testUploadId: string; + uploadName: string; + }; + HandoffDto: { + /** @description Opaque state token minted by the CLI. */ + state: string; + /** @description base64url(sha256(code_verifier)) — the PKCE S256 challenge. */ + code_challenge: string; + /** @description The browser session's Supabase access token (JWT), used only to verify the user's identity. */ + access_token: string; + /** @description The browser session's refresh token. Sent only for SAML SSO sessions, which cannot be minted a dedicated CLI session server-side. */ + refresh_token?: string; + }; + ClaimDto: { + /** @description Opaque state token minted by the CLI. */ + state: string; + /** @description base64url(random bytes) — the PKCE verifier. */ + code_verifier: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + UploadsController_getBinaryUploadUrl: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IGetBinaryUploadUrlArgs"]; + }; + }; + responses: { + /** @description The url has been successfully created. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IGetBinaryUploadUrlResponse"]; + }; + }; + }; + }; + UploadsController_checkForExistingUpload: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ICheckForExistingUploadArgs"]; + }; + }; + responses: { + /** @description The url has been successfully created. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ICheckForExistingUploadResponse"]; + }; + }; + }; + }; + UploadsController_finaliseUpload: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IFinaliseUploadArgs"]; + }; + }; + responses: { + /** @description The upload has been completed. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IFinaliseUploadResponse"]; + }; + }; + }; + }; + UploadsController_finishLargeFile: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IFinishLargeFileArgs"]; + }; + }; + responses: { + /** @description The large file upload has been completed. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IFinishLargeFileResponse"]; + }; + }; + }; + }; + UploadsController_getFlowUploadUrl: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IGetFlowUploadUrlArgs"]; + }; + }; + responses: { + /** @description The url has been successfully created. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IGetBinaryUploadUrlResponse"]; + }; + }; + }; + }; + UploadsController_createTest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["ICreateTestUploadArgs"]; + }; + }; + responses: { + /** @description The record has been successfully created. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + message?: string; + results?: components["schemas"]["IDBResult"][]; + }; + }; + }; + }; + }; + UploadsController_submitFlowTest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ISubmitFlowTestArgs"]; + }; + }; + responses: { + /** @description The record has been successfully created. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + message?: string; + results?: components["schemas"]["IDBResult"][]; + }; + }; + }; + }; + }; + UploadsController_estimateMatrix: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IFlowTestParams"]; + }; + }; + responses: { + /** @description Estimated cell count and cost for a device-matrix submission. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + cellCount?: number; + totalCost?: number; + excludedFlows?: string[]; + columns?: { + deviceName?: string; + osVersion?: string; + googlePlay?: boolean; + flowCount?: number; + cost?: number; + }[]; + }; + }; + }; + }; + }; + UploadsController_retryTest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IRetryTestArgs"]; + }; + }; + responses: { + /** @description Retry started. Provide resultId to retry a single test (returns the new result id), or uploadId to retry all failed tests in an upload (returns retriedCount). */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + message?: string; + id?: number; + success?: boolean; + retriedCount?: number; + }; + }; + }; + }; + }; + UploadsController_cancelTest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ICancelTestArgs"]; + }; + }; + responses: { + /** @description The record has been successfully cancelled. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + message?: string; + success?: boolean; + cancelledCount?: number; + }; + }; + }; + }; + }; + UploadsController_getUploadStatus: { + parameters: { + query?: { + /** @description Upload ID to get status for */ + uploadId?: string; + /** @description Upload name to get status for */ + name?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Upload status */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "uploadId": "upload-123", + * "status": "PENDING", + * "tests": [ + * { + * "id": 1, + * "test_file_name": "test-flow.yaml", + * "status": "PENDING" + * } + * ] + * } + */ + "application/json": Record; + }; + }; + }; + }; + UploadsController_listUploads: { + parameters: { + query?: { + /** @description Filter by upload name (supports * wildcard) */ + name?: string; + /** @description Filter uploads created on or after this date (ISO 8601) */ + from?: string; + /** @description Filter uploads created on or before this date (ISO 8601) */ + to?: string; + /** @description Maximum number of uploads to return (default: 20) */ + limit?: number; + /** @description Number of uploads to skip (default: 0) */ + offset?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of flow uploads. Use GET /uploads/status for detailed test results. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "uploads": [ + * { + * "id": "upload-123", + * "name": "Test Upload", + * "created_at": "2024-01-01T00:00:00Z", + * "consoleUrl": "https://console.devicecloud.dev/results/upload-123" + * } + * ], + * "total": 1, + * "limit": 20, + * "offset": 0 + * } + */ + "application/json": { + uploads?: { + id?: string; + name?: string | null; + created_at?: string; + consoleUrl?: string; + }[]; + total?: number; + limit?: number; + offset?: number; + }; + }; + }; + }; + }; + UploadsController_deleteUpload: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The upload has been successfully deleted. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + success?: boolean; + message?: string; + }; + }; + }; + }; + }; + OrgController_handlePaddleWebhook: { + parameters: { + query?: never; + header: { + "paddle-signature": string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Paddle webhook handler. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string; + }; + }; + }; + }; + OrgController_updateOrgName: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateOrgNameDto"]; + }; + }; + responses: { + /** @description Organization name updated successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_inviteTeamMember: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["InviteTeamMemberDto"]; + }; + }; + responses: { + /** @description Team member invited successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_acceptInvite: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AcceptInviteDto"]; + }; + }; + responses: { + /** @description Team invite accepted successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_revokeInvite: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RevokeInviteDto"]; + }; + }; + responses: { + /** @description Team invite revoked successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_changeTeamMemberRole: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ChangeRoleDto"]; + }; + }; + responses: { + /** @description Team member role updated successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_removeTeamMember: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RemoveMemberDto"]; + }; + }; + responses: { + /** @description Team member removed successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_leaveTeam: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LeaveTeamDto"]; + }; + }; + responses: { + /** @description Left the team successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_deleteTeam: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DeleteTeamDto"]; + }; + }; + responses: { + /** @description Team deleted successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": boolean; + }; + }; + }; + }; + OrgController_getAllSubscriptions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + orgId: string; + }; + }; + }; + responses: { + /** @description All subscription data fetched successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + OrgController_updateOverageLimit: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + orgId: string; + overageLimit: number; + }; + }; + }; + responses: { + /** @description Overage limit updated successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + OrgController_getUsageHistory: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + orgId: string; + /** @enum {string} */ + format?: "json" | "csv"; + /** Format: date-time */ + startDate?: string; + /** Format: date-time */ + endDate?: string; + }; + }; + }; + responses: { + /** @description Usage history fetched successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown[]; + }; + }; + }; + }; + OrgController_updateGpuRetries: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + orgId: number; + gpuRetries: boolean; + }; + }; + }; + responses: { + /** @description GPU retries setting updated successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + OrgController_updateBillingEmail: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + orgId: number; + billingEmail: string; + }; + }; + }; + responses: { + /** @description Billing email updated successfully. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + OrgController_getInvoices: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + orgId: number; + }; + }; + }; + responses: { + /** @description List of Paddle invoices for the organization. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + OrgController_getInvoiceUrl: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + orgId: number; + transactionId: string; + }; + }; + }; + responses: { + /** @description Hosted PDF URL for a single invoice. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + OrgController_billDailyOverages: { + parameters: { + query?: never; + header: { + "x-cron-secret": string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Daily overage billing processed. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + ResultsController_getResults: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The record has been successfully created. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + statusCode?: number; + results?: components["schemas"]["TResultResponse"][]; + }; + }; + }; + }; + }; + ResultsController_getTestRunArtifacts: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + ResultsController_downloadReport: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Download combined JUNIT test report (report.xml) for the upload */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string; + }; + }; + }; + }; + ResultsController_downloadHtmlReport: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Download combined HTML test report with assets (report.zip) for the upload */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string; + }; + }; + }; + }; + ResultsController_downloadSingleHtmlReport: { + parameters: { + query?: never; + header?: never; + path: { + resultId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Download HTML test report with assets (report.zip) for a single result */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string; + }; + }; + }; + }; + ResultsController_getArtifactsBundleManifest: { + parameters: { + query: { + results: string; + }; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + ResultsController_getReportBundleManifest: { + parameters: { + query?: never; + header?: never; + path: { + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + ResultsController_getSingleReportBundleManifest: { + parameters: { + query?: never; + header?: never; + path: { + resultId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + ResultsController_getCompatibilityData: { + parameters: { + query?: never; + header: { + "x-dcd-cli-version": string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Device compatibility lookup data including Maestro versions */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "statusCode": 200, + * "data": { + * "ios": { + * "iphone-14": { + * "name": "iPhone 14", + * "versions": [ + * "16", + * "17", + * "18" + * ], + * "deprecated": false + * }, + * "iphone-15": { + * "name": "iPhone 15", + * "versions": [ + * "17" + * ], + * "deprecated": false + * }, + * "iphone-16": { + * "name": "iPhone 16", + * "versions": [ + * "18", + * "26" + * ], + * "deprecated": false + * }, + * "iphone-16-plus": { + * "name": "iPhone 16 Plus", + * "versions": [ + * "26" + * ], + * "deprecated": false + * }, + * "iphone-16-pro": { + * "name": "iPhone 16 Pro", + * "versions": [ + * "18", + * "26" + * ], + * "deprecated": false + * }, + * "iphone-16-pro-max": { + * "name": "iPhone 16 Pro Max", + * "versions": [ + * "18", + * "26" + * ], + * "deprecated": false + * }, + * "ipad-pro-6th-gen": { + * "name": "iPad Pro (6th gen)", + * "versions": [ + * "18", + * "26" + * ], + * "deprecated": false + * } + * }, + * "android": { + * "pixel-6": { + * "name": "Pixel 6", + * "apiLevels": [ + * "29", + * "30", + * "31", + * "32", + * "33", + * "34", + * "35", + * "36" + * ], + * "deprecated": false + * }, + * "pixel-6-pro": { + * "name": "Pixel 6 Pro", + * "apiLevels": [ + * "33", + * "34", + * "35", + * "36" + * ], + * "deprecated": false + * }, + * "pixel-7": { + * "name": "Pixel 7", + * "apiLevels": [ + * "33", + * "34", + * "35", + * "36" + * ], + * "deprecated": false + * }, + * "pixel-7-pro": { + * "name": "Pixel 7 Pro", + * "apiLevels": [ + * "33", + * "34", + * "35", + * "36" + * ], + * "deprecated": false + * }, + * "generic-tablet": { + * "name": "Generic Tablet", + * "apiLevels": [ + * "33", + * "34", + * "35", + * "36" + * ], + * "deprecated": false + * } + * }, + * "androidPlay": { + * "pixel-6": { + * "name": "Pixel 6 (Google Play)", + * "apiLevels": [ + * "34", + * "35", + * "36" + * ], + * "deprecated": false + * }, + * "pixel-7": { + * "name": "Pixel 7 (Google Play)", + * "apiLevels": [ + * "34", + * "35", + * "36" + * ], + * "deprecated": false + * } + * }, + * "maestro": { + * "supportedVersions": [ + * "2.0.4", + * "2.0.9", + * "2.1.0", + * "2.2.0", + * "2.5.0", + * "2.5.1", + * "2.6.0", + * "2.6.1", + * "2.7.0", + * "2.8.0" + * ], + * "defaultVersion": "2.2.0", + * "latestVersion": "2.8.0" + * } + * } + * } + */ + "application/json": { + statusCode?: number; + data?: { + ios?: Record; + android?: Record; + androidPlay?: Record; + maestro?: { + supportedVersions?: string[]; + defaultVersion?: string; + latestVersion?: string; + }; + }; + }; + }; + }; + }; + }; + AllureController_downloadAllureReport: { + parameters: { + query?: never; + header?: never; + path: { + /** @description The upload ID to generate Allure report for */ + uploadId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Allure report HTML file download */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/html": string; + }; + }; + /** @description Upload not found or no results available */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + WebhooksController_getWebhook: { + parameters: { + query?: { + /** @description Set to true to return full secret instead of masked version */ + show_secret?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current webhook configuration */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + webhook_url?: string; + /** @description Full secret (only when show_secret=true) */ + secret_key?: string; + /** @description Masked secret (default) */ + secret_key_masked?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }; + }; + }; + }; + }; + WebhooksController_setWebhook: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: uri + * @example https://api.example.com/webhook + */ + url: string; + }; + }; + }; + responses: { + /** @description Webhook URL set successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + WebhooksController_deleteWebhook: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Webhook configuration deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + WebhooksController_regenerateWebhookSecret: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Webhook secret regenerated successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + WebhooksController_testWebhook: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** Format: uri */ + url?: string; + }; + }; + }; + responses: { + /** @description Test webhook sent successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; + SlackController_oauthStart: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - ICheckForExistingUploadResponse: { - appBinaryId: string; - exists: boolean; + requestBody?: never; + responses: { + /** @description Slack authorize URL to redirect the user to */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; - IFinaliseUploadArgs: { - /** @description Unique upload identifier */ - id: string; - /** @description Storage path for the uploaded file */ - path: string; - /** @description File metadata (bundle ID, package name, platform) - required for new clients */ - metadata?: Record; - /** @description SHA-256 hash of the file - required for new clients */ - sha?: string; - /** - * @description Whether the Supabase upload was successful - * @default true - */ - supabaseSuccess: boolean; - /** - * @description Whether the Backblaze upload was successful - * @default false - */ - backblazeSuccess: boolean; - /** @description Whether client uses TUS resumable uploads (true for new clients, undefined/false for legacy) */ - useTus?: boolean; - /** @description File size in bytes */ - bytes?: number; + }; + SlackController_getConnection: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - IFinaliseUploadResponse: Record; - IFinishLargeFileArgs: { - /** - * @description The Backblaze file ID from the large file upload - * @example abc123xyz - */ - fileId: string; - /** - * @description Array of SHA1 hashes for each uploaded part - * @example [ - * "sha1hash1", - * "sha1hash2" - * ] - */ - partSha1Array: string[]; + requestBody?: never; + responses: { + /** @description Current Slack connection for the org (or null) */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; - IFinishLargeFileResponse: { - success: boolean; - result: Record; + }; + SlackController_disconnect: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - ICreateTestUploadArgs: { - /** - * Format: binary - * @description This file must be a zip file - */ - file: string; - testFileNames?: string; - sequentialFlows?: string; - /** @enum {string} */ - androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36"; - /** @enum {string} */ - androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet"; - apiKey?: string; - apiUrl?: string; - appBinaryId: string; - appFile?: string; - env: string; - /** @enum {string} */ - iOSVersion?: "16" | "17" | "18" | "26"; - /** @enum {string} */ - iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; - platform?: string; - googlePlay: boolean; - config: string; - name?: string; - /** @enum {string} */ - runnerType?: "m4" | "m1" | "default" | "gpu1" | "cpu1"; - metadata?: string; - workspaceConfig?: string; - flowMetadata?: string; - testFileOverrides?: string; - /** @description SHA-256 hash of the flow ZIP file */ - sha?: string; + requestBody?: never; + responses: { + /** @description Slack disconnected and connection removed */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; - IRetryTestArgs: { - resultId: number; + }; + SlackController_getChannels: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Channels the bot can see, for the picker */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SlackController_setConfig: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Slack channel / preferences updated */ + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SlackController_test: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Test message sent to the configured channel */ + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + GithubController_oauthStart: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GitHub App install URL to redirect the user to */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + GithubController_getConnection: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current GitHub App connection for the org (or null) */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + GithubController_disconnect: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GitHub connection removed for the org */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + NoticesController_getNotices: { + parameters: { + query: { + surface: string; + platform: string; + }; + header: { + "x-dcd-cli-version": string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Active notices for the calling client. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + statusCode?: number; + data?: { + id?: string; + slug?: string | null; + /** @enum {string} */ + level?: "deprecation" | "warn" | "info" | "marketing"; + title?: string; + body?: string; + learnMoreUrl?: string | null; + dismissible?: boolean; + match?: Record | null; + }[]; + }; + }; + }; + }; + }; + ApiKeysController_list: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List the org API keys (no secrets) */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; - ICancelTestArgs: { - /** @description ID of a specific result to cancel. Either resultId or uploadId must be provided, but not both. */ - resultId?: number; - /** @description ID of an upload to cancel all pending results for. Either resultId or uploadId must be provided, but not both. */ - uploadId?: string; + }; + ApiKeysController_create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - TResultResponse: { - id: number; - test_file_name: string; - status: string; - retry_of?: number; - fail_reason?: string; - duration_seconds?: number; + requestBody?: never; + responses: { + /** @description Issue a new key; returns the raw key once */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; }; - UpdateOrgNameDto: { - /** - * @description Organization ID - * @example 123 - */ - orgId: number; - /** - * @description Organization name - * @example Acme Corporation - */ - name: string; + }; + ApiKeysController_revoke: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; }; - AcceptInviteDto: { - /** - * @description Organization ID - * @example 1 - */ - orgId: string; - /** - * @description User email address - * @example user@example.com - */ - email: string; + requestBody?: never; + responses: { + /** @description Revoke (soft-delete) a key */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - UploadsController_getBinaryUploadUrl: { + ApiKeysController_update: { parameters: { query?: never; - header: { - "x-app-api-key": string; + header?: never; + path: { + id: number; }; - path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["IGetBinaryUploadUrlArgs"]; + requestBody?: never; + responses: { + /** @description Update a key name/description/expiry */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + ApiKeysController_rotate: { + parameters: { + query?: never; + header?: never; + path: { + id: number; }; + cookie?: never; }; + requestBody?: never; responses: { - /** @description The url has been successfully created. */ + /** @description Rotate a key; returns the new raw key once */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; 201: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["IGetBinaryUploadUrlResponse"]; + "application/json": Record; }; }; }; }; - UploadsController_checkForExistingUpload: { + NetworkController_getIpAddresses: { parameters: { query?: never; - header: { - "x-app-api-key": string; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current DeviceCloud test-runner egress IP addresses. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "updatedAt": "2026-07-06", + * "ipAddresses": [ + * "46.17.215.144", + * "46.17.215.145", + * "83.217.174.249" + * ], + * "ranges": [ + * { + * "cidr": "46.17.215.144/32", + * "platforms": [ + * "android", + * "ios" + * ], + * "type": "egress", + * "description": "Test runner egress" + * } + * ] + * } + */ + "application/json": { + /** @example 2026-07-06 */ + updatedAt?: string; + /** + * @example [ + * "46.17.215.144", + * "46.17.215.145", + * "83.217.174.249" + * ] + */ + ipAddresses?: string[]; + ranges?: { + /** @example 46.17.215.144/32 */ + cidr?: string; + platforms?: ("android" | "ios")[]; + /** @enum {string} */ + type?: "egress"; + description?: string; + }[]; + }; + }; }; + }; + }; + FrontendController_checkDomainSaml: { + parameters: { + query?: never; + header?: never; path?: never; cookie?: never; }; + /** @description Domain to check for SAML configuration */ requestBody: { content: { - "application/json": components["schemas"]["ICheckForExistingUploadArgs"]; + "application/json": { + /** @example example.com */ + domain: string; + }; }; }; responses: { - /** @description The url has been successfully created. */ + /** @description SAML status for the domain */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + forceSaml?: boolean; + }; + }; + }; 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - invalid domain or API error */ + 400: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ICheckForExistingUploadResponse"]; + "application/json": { + error?: string; + }; }; }; }; }; - UploadsController_finaliseUpload: { + FrontendController_validateEmail: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; + header?: never; path?: never; cookie?: never; }; + /** @description Email address to validate */ requestBody: { content: { - "application/json": components["schemas"]["IFinaliseUploadArgs"]; + "application/json": { + /** @example user@example.com */ + email: string; + }; }; }; responses: { - /** @description The upload has been completed. */ + /** @description Email validation result */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + valid?: boolean; + reason?: string; + }; + }; + }; 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - invalid email or API error */ + 400: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["IFinaliseUploadResponse"]; + "application/json": { + error?: string; + }; }; }; }; }; - UploadsController_finishLargeFile: { + FrontendController_getBinaryDownloadUrl: { parameters: { query?: never; header: { - "x-app-api-key": string; + authorization: string; }; path?: never; cookie?: never; }; + /** @description Get a signed download URL for a binary */ requestBody: { content: { - "application/json": components["schemas"]["IFinishLargeFileArgs"]; + "application/json": { + binaryId: string; + orgId: number; + }; }; }; responses: { - /** @description The large file upload has been completed. */ + /** @description Signed download URL for the binary; `dek` (base64) is present when the binary is encrypted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + url?: string; + encrypted?: boolean; + dek?: string; + }; + }; + }; 201: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["IFinishLargeFileResponse"]; + "application/json": Record; + }; + }; + /** @description Binary not found or not accessible */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error?: string; + }; }; }; }; }; - UploadsController_createTest: { + FrontendController_downloadBinary: { parameters: { - query?: never; + query: { + orgId: number; + }; header: { - "x-app-api-key": string; + authorization: string; }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["ICreateTestUploadArgs"]; + path: { + binaryId: string; }; + cookie?: never; }; + requestBody?: never; responses: { - /** @description The record has been successfully created. */ - 201: { + /** @description Binary bytes (decrypted if encrypted) */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Binary not found or not accessible */ + 400: { headers: { [name: string]: unknown; }; - content: { - "application/json": { - message?: string; - results?: components["schemas"]["IDBResult"][]; - }; - }; + content?: never; }; }; }; - UploadsController_retryTest: { + FrontendController_getArtifactDownloadUrl: { parameters: { query?: never; header: { - "x-app-api-key": string; + authorization: string; }; path?: never; cookie?: never; }; + /** @description Get a signed download URL for a single result artifact */ requestBody: { content: { - "application/json": components["schemas"]["IRetryTestArgs"]; + "application/json": { + resultId: number; + /** @description Supabase storage path; must be one of the result's recorded files */ + path: string; + /** @description Optional filename override for the saved file */ + download?: string; + }; }; }; responses: { - /** @description The record has been successfully created. */ - 201: { + /** @description Signed download URL for the artifact (CDN/B2 or Supabase) */ + 200: { headers: { [name: string]: unknown; }; content: { "application/json": { - message?: string; - results?: components["schemas"]["IDBResult"][]; + url?: string; }; }; }; - }; - }; - UploadsController_cancelTest: { - parameters: { - query?: never; - header: { - "x-app-api-key": string; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["ICancelTestArgs"]; - }; - }; - responses: { - /** @description The record has been successfully cancelled. */ 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Artifact not found or not accessible */ + 400: { headers: { [name: string]: unknown; }; content: { "application/json": { - message?: string; - success?: boolean; - cancelledCount?: number; + error?: string; }; }; }; }; }; - UploadsController_getUploadStatus: { + FrontendController_getResultDetail: { parameters: { - query?: { - /** @description Upload ID to get status for */ - uploadId?: string; - /** @description Upload name to get status for */ - name?: string; - }; + query?: never; header: { - "x-app-api-key": string; + authorization: string; + }; + path: { + resultId: number; }; - path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Upload status */ + /** @description Bundled result detail: row + result_files + binary + signed media URLs + parsed log in a single round-trip */ 200: { headers: { [name: string]: unknown; }; - content: { - /** - * @example { - * "uploadId": "upload-123", - * "status": "PENDING", - * "tests": [ - * { - * "id": 1, - * "test_file_name": "test-flow.yaml", - * "status": "PENDING" - * } - * ] - * } - */ - "application/json": Record; + content?: never; + }; + /** @description Result not found or caller not authorized */ + 400: { + headers: { + [name: string]: unknown; }; + content?: never; }; }; }; - UploadsController_listUploads: { + FrontendController_ingestLogs: { parameters: { - query?: { - /** @description Filter by upload name (supports * wildcard) */ - name?: string; - /** @description Filter uploads created on or after this date (ISO 8601) */ - from?: string; - /** @description Filter uploads created on or before this date (ISO 8601) */ - to?: string; - /** @description Maximum number of uploads to return (default: 20) */ - limit?: number; - /** @description Number of uploads to skip (default: 0) */ - offset?: number; - }; + query?: never; header: { - "x-app-api-key": string; + authorization: string; + "x-dcd-org": string; }; path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description List of flow uploads. Use GET /uploads/status for detailed test results. */ - 200: { + /** @description Batch accepted for forwarding to Axiom */ + 202: { headers: { [name: string]: unknown; }; content: { - /** - * @example { - * "uploads": [ - * { - * "id": "upload-123", - * "name": "Test Upload", - * "created_at": "2024-01-01T00:00:00Z", - * "consoleUrl": "https://console.devicecloud.dev/results/upload-123" - * } - * ], - * "total": 1, - * "limit": 20, - * "offset": 0 - * } - */ "application/json": { - uploads?: { - id?: string; - name?: string | null; - created_at?: string; - consoleUrl?: string; - }[]; - total?: number; - limit?: number; - offset?: number; + accepted?: number; }; }; }; + /** @description Malformed batch payload */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; - UploadsController_deleteUpload: { + FrontendController_ingestLogsAnonymous: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; - path: { - uploadId: string; - }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description The upload has been successfully deleted. */ - 201: { + /** @description Batch accepted for forwarding to Axiom */ + 202: { headers: { [name: string]: unknown; }; content: { "application/json": { - success?: boolean; - message?: string; + accepted?: number; }; }; }; + /** @description Malformed batch payload */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; - ResultsController_getResults: { + HealthController_health: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; - path: { - uploadId: string; - }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description The record has been successfully created. */ + /** @description Health check endpoint */ 200: { headers: { [name: string]: unknown; }; content: { + /** + * @example { + * "status": "ok" + * } + */ "application/json": { - statusCode?: number; - results?: components["schemas"]["TResultResponse"][]; + /** @example ok */ + status?: string; }; }; }; }; }; - ResultsController_getTestRunArtifacts: { + BillingController_createSubscription: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; - path: { - uploadId: string; - }; + header?: never; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + items: { + price_id?: string; + quantity?: number; + }[]; + customer_email?: string; + custom_data?: { + userId?: string; + }; + billing_details?: { + enable_checkout?: boolean; + }; + }; + }; + }; responses: { + /** @description Subscription created successfully. */ 201: { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": Record; + }; }; }; }; - ResultsController_notifyTestRunComplete: { + BillingController_updateSubscription: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; - path: { - uploadId: string; - }; + header?: never; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + price_id: string; + }; + }; + }; responses: { - /** @description Send results summary email. */ + /** @description Subscription updated successfully. */ 201: { headers: { [name: string]: unknown; }; content: { - "application/json": string; + "application/json": Record; }; }; }; }; - ResultsController_downloadReport: { + StatsController_getMarketingStats: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; - path: { - uploadId: string; - }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Download combined JUNIT test report (report.xml) for the upload */ + /** @description Public marketing statistics */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": string; + "application/json": { + /** @example 150000 */ + total_count?: number; + }; }; }; }; }; - ResultsController_downloadHtmlReport: { + FlowsController_getFlows: { parameters: { - query?: never; - header: { - "x-app-api-key": string; + query?: { + platform?: "android" | "ios"; + appId?: string; + days?: number; + /** @description ISO 8601 date string (e.g. 2026-01-01). Overrides days when provided. */ + startDate?: string; + /** @description ISO 8601 date string (e.g. 2026-01-31). Defaults to now when startDate is set. */ + endDate?: string; + /** @description Comma-separated tag filter. Returns flows that have any of the given tags (e.g. smoke,critical). */ + tags?: string; }; - path: { - uploadId: string; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Aggregated flow statistics for the last N days. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + statusCode?: number; + flows?: components["schemas"]["TFlowSummaryResponse"][]; + }; + }; + }; + }; + }; + FlowsController_getFlowRuns: { + parameters: { + query: { + fileName: string; + platform?: "android" | "ios"; + appId?: string; + limit?: number; + /** @description ISO 8601 date string (e.g. 2026-01-01). */ + startDate?: string; + /** @description ISO 8601 date string (e.g. 2026-01-31). */ + endDate?: string; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Download combined HTML test report with assets (report.zip) for the upload */ + /** @description Individual run history for a specific flow file. */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": string; + "application/json": { + statusCode?: number; + runs?: components["schemas"]["TFlowRunItem"][]; + }; }; }; }; }; - ResultsController_downloadSingleHtmlReport: { + LiveController_createSession: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; - path: { - resultId: string; - }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Download HTML test report with assets (report.zip) for a single result */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": string; + "application/json": Record; }; }; }; }; - ResultsController_getCompatibilityData: { + LiveController_getSession: { parameters: { query?: never; - header: { - "x-app-api-key": string; + header?: never; + path: { + identifier: string; }; - path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Device compatibility lookup data including Maestro versions */ 200: { headers: { [name: string]: unknown; }; content: { - /** - * @example { - * "statusCode": 200, - * "data": { - * "ios": { - * "iphone-14": { - * "name": "iPhone 14", - * "versions": [ - * "16", - * "17", - * "18" - * ], - * "deprecated": false - * }, - * "iphone-15": { - * "name": "iPhone 15", - * "versions": [ - * "17" - * ], - * "deprecated": false - * }, - * "iphone-16": { - * "name": "iPhone 16", - * "versions": [ - * "18", - * "26" - * ], - * "deprecated": false - * }, - * "iphone-16-plus": { - * "name": "iPhone 16 Plus", - * "versions": [ - * "18", - * "26" - * ], - * "deprecated": false - * }, - * "iphone-16-pro": { - * "name": "iPhone 16 Pro", - * "versions": [ - * "18", - * "26" - * ], - * "deprecated": false - * }, - * "iphone-16-pro-max": { - * "name": "iPhone 16 Pro Max", - * "versions": [ - * "18", - * "26" - * ], - * "deprecated": false - * }, - * "ipad-pro-6th-gen": { - * "name": "iPad Pro (6th gen)", - * "versions": [ - * "18", - * "26" - * ], - * "deprecated": false - * } - * }, - * "android": { - * "pixel-6": { - * "name": "Pixel 6", - * "apiLevels": [ - * "29", - * "30", - * "31", - * "32", - * "33", - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * }, - * "pixel-6-pro": { - * "name": "Pixel 6 Pro", - * "apiLevels": [ - * "33", - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * }, - * "pixel-7": { - * "name": "Pixel 7", - * "apiLevels": [ - * "33", - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * }, - * "pixel-7-pro": { - * "name": "Pixel 7 Pro", - * "apiLevels": [ - * "33", - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * }, - * "generic-tablet": { - * "name": "Generic Tablet", - * "apiLevels": [ - * "33", - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * } - * }, - * "androidPlay": { - * "pixel-6": { - * "name": "Pixel 6 (Google Play)", - * "apiLevels": [ - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * }, - * "pixel-7": { - * "name": "Pixel 7 (Google Play)", - * "apiLevels": [ - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * } - * }, - * "maestro": { - * "supportedVersions": [ - * "1.39.0", - * "1.39.2", - * "1.39.5", - * "1.39.7", - * "1.40.3", - * "1.41.0", - * "2.0.2", - * "2.0.3", - * "2.0.4", - * "2.0.9", - * "2.1.0" - * ], - * "defaultVersion": "1.41.0", - * "latestVersion": "2.1.0" - * } - * } - * } - */ - "application/json": { - statusCode?: number; - data?: { - ios?: Record; - android?: Record; - androidPlay?: Record; - maestro?: { - supportedVersions?: string[]; - defaultVersion?: string; - latestVersion?: string; - }; - }; - }; + "application/json": Record; }; }; }; }; - AllureController_downloadAllureReport: { + LiveController_stopSession: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; + header?: never; path: { - /** @description The upload ID to generate Allure report for */ - uploadId: string; + identifier: string; }; cookie?: never; }; requestBody?: never; responses: { - /** @description Allure report HTML file download */ 200: { - headers: { - [name: string]: unknown; - }; - content: { - "text/html": string; - }; - }; - /** @description Upload not found or no results available */ - 404: { headers: { [name: string]: unknown; }; @@ -1426,64 +4188,40 @@ export interface operations { }; }; }; - WebhooksController_getWebhook: { + LiveController_execTest: { parameters: { - query?: { - /** @description Set to true to return full secret instead of masked version */ - show_secret?: boolean; - }; - header: { - "x-app-api-key": string; + query?: never; + header?: never; + path: { + identifier: string; }; - path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Current webhook configuration */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": { - webhook_url?: string; - /** @description Full secret (only when show_secret=true) */ - secret_key?: string; - /** @description Masked secret (default) */ - secret_key_masked?: string; - /** Format: date-time */ - created_at?: string; - /** Format: date-time */ - updated_at?: string; - }; + "application/json": Record; }; }; }; }; - WebhooksController_setWebhook: { + LiveController_getCommandStatus: { parameters: { query?: never; - header: { - "x-app-api-key": string; + header?: never; + path: { + identifier: string; + commandId: string; }; - path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": { - /** - * Format: uri - * @example https://api.example.com/webhook - */ - url: string; - }; - }; - }; + requestBody?: never; responses: { - /** @description Webhook URL set successfully */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -1493,160 +4231,123 @@ export interface operations { }; }; }; - WebhooksController_deleteWebhook: { + LiveController_keepalive: { parameters: { query?: never; - header: { - "x-app-api-key": string; + header?: never; + path: { + identifier: string; }; - path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Webhook configuration deleted successfully */ - 200: { + 201: { headers: { [name: string]: unknown; }; - content: { - "application/json": Record; - }; + content?: never; }; }; }; - WebhooksController_regenerateWebhookSecret: { + LiveController_installBinary: { parameters: { query?: never; - header: { - "x-app-api-key": string; + header?: never; + path: { + identifier: string; }; - path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Webhook secret regenerated successfully */ 201: { headers: { [name: string]: unknown; }; - content: { - "application/json": Record; - }; + content?: never; }; }; }; - WebhooksController_testWebhook: { + MeController_listOrgs: { parameters: { query?: never; header: { - "x-app-api-key": string; + authorization: string; }; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": { - /** Format: uri */ - url?: string; - }; - }; - }; + requestBody?: never; responses: { - /** @description Test webhook sent successfully */ - 201: { + 200: { headers: { [name: string]: unknown; }; - content: { - "application/json": Record; - }; + content?: never; }; }; }; - OrgController_handlePaddleWebhook: { + MeController_listSessions: { parameters: { query?: never; header: { - "paddle-signature": string; + authorization: string; }; path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Paddle webhook handler. */ - 201: { + 200: { headers: { [name: string]: unknown; }; - content: { - "application/json": string; - }; + content?: never; }; }; }; - OrgController_updateOrgName: { + MeController_revokeSession: { parameters: { query?: never; header: { - "x-app-api-key": string; + authorization: string; }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["UpdateOrgNameDto"]; + path: { + id: string; }; + cookie?: never; }; + requestBody?: never; responses: { - /** @description Organization name updated successfully. */ - 201: { + 200: { headers: { [name: string]: unknown; }; - content: { - "application/json": boolean; - }; + content?: never; }; }; }; - OrgController_inviteTeamMember: { + MeController_createPersonalTeam: { parameters: { query?: never; header: { - "x-app-api-key": string; + authorization: string; }; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": { - inviteEmail: string; - requesterEmail: string; - link: string; - orgId: string; - orgName: string; - }; - }; - }; + requestBody?: never; responses: { - /** @description Team member invited successfully. */ - 201: { + 200: { headers: { [name: string]: unknown; }; - content: { - "application/json": boolean; - }; + content?: never; }; }; }; - OrgController_acceptInvite: { + MeController_createTeam: { parameters: { query?: never; header: { @@ -1655,292 +4356,262 @@ export interface operations { path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["AcceptInviteDto"]; - }; - }; + requestBody?: never; responses: { - /** @description Team invite accepted successfully. */ - 201: { + 200: { headers: { [name: string]: unknown; }; - content: { - "application/json": boolean; - }; + content?: never; }; }; }; - OrgController_getAllSubscriptions: { + MeController_deleteAccount: { parameters: { query?: never; header: { - "x-app-api-key": string; + authorization: string; }; path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": { - orgId: string; - }; - }; - }; + requestBody?: never; responses: { - /** @description All subscription data fetched successfully. */ - 201: { + 200: { headers: { [name: string]: unknown; }; - content: { - "application/json": Record; - }; + content?: never; }; }; }; - OrgController_updateOverageLimit: { + CliLoginController_handoff: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; + header?: never; path?: never; cookie?: never; }; requestBody: { content: { - "application/json": { - orgId: string; - overageLimit: number; - }; + "application/json": components["schemas"]["HandoffDto"]; }; }; responses: { - /** @description Overage limit updated successfully. */ - 201: { + 204: { headers: { [name: string]: unknown; }; - content: { - "application/json": Record; - }; + content?: never; }; }; }; - OrgController_getUsageHistory: { + CliLoginController_claim: { parameters: { query?: never; - header: { - "x-app-api-key": string; - }; + header?: never; path?: never; cookie?: never; }; requestBody: { content: { - "application/json": { - orgId: string; - /** @enum {string} */ - format?: "json" | "csv"; - /** Format: date-time */ - startDate?: string; - /** Format: date-time */ - endDate?: string; - }; + "application/json": components["schemas"]["ClaimDto"]; }; }; responses: { - /** @description Usage history fetched successfully. */ - 201: { + /** @description Returns the Supabase session on successful claim. */ + 200: { headers: { [name: string]: unknown; }; - content: { - "application/json": unknown[]; - }; + content?: never; }; }; }; - FrontendController_checkDomainSaml: { + CliLogsController_ingestLogs: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** @description Domain to check for SAML configuration */ - requestBody: { - content: { - "application/json": { - /** @example example.com */ - domain: string; - }; - }; - }; + requestBody?: never; responses: { - /** @description SAML status for the domain */ + /** @description Batch accepted for forwarding to Axiom */ 200: { headers: { [name: string]: unknown; }; content: { "application/json": { - forceSaml?: boolean; + accepted?: number; }; }; }; - 201: { + 202: { headers: { [name: string]: unknown; }; content?: never; }; - /** @description Bad request - invalid domain or API error */ + /** @description Malformed batch payload */ 400: { headers: { [name: string]: unknown; }; - content: { - "application/json": { - error?: string; - }; - }; + content?: never; }; }; }; - FrontendController_validateEmail: { + EmailChangeController_start: { parameters: { query?: never; - header?: never; + header: { + authorization: string; + }; path?: never; cookie?: never; }; - /** @description Email address to validate */ + /** @description New email address to change to */ requestBody: { content: { "application/json": { /** @example user@example.com */ - email: string; + newEmail: string; }; }; }; responses: { - /** @description Email validation result */ + /** @description Verification code sent to the current address */ 200: { headers: { [name: string]: unknown; }; content: { "application/json": { - valid?: boolean; - reason?: string; + ok?: boolean; }; }; }; - 201: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Bad request - invalid email or API error */ + /** @description Invalid or missing newEmail */ 400: { headers: { [name: string]: unknown; }; content: { "application/json": { - error?: string; + message?: string; }; }; }; }; }; - HealthController_health: { + EmailChangeController_verifyCurrent: { parameters: { query?: never; - header?: never; + header: { + authorization: string; + }; path?: never; cookie?: never; }; - requestBody?: never; + /** @description Code emailed to the current address */ + requestBody: { + content: { + "application/json": { + /** @example 123456 */ + code: string; + }; + }; + }; responses: { - /** @description Health check endpoint */ + /** @description Current address verified; code sent to the new address */ 200: { headers: { [name: string]: unknown; }; content: { - /** - * @example { - * "status": "ok" - * } - */ "application/json": { - /** @example ok */ - status?: string; + ok?: boolean; + }; + }; + }; + /** @description Invalid or expired code */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + message?: string; }; }; }; }; }; - BillingController_createSubscription: { + EmailChangeController_verifyNew: { parameters: { query?: never; - header?: never; + header: { + authorization: string; + }; path?: never; cookie?: never; }; + /** @description Code emailed to the new address */ requestBody: { content: { "application/json": { - items: { - price_id?: string; - quantity?: number; - }[]; - customer_email?: string; - custom_data?: { - orgId?: string; - userId?: string; - }; - billing_details?: { - enable_checkout?: boolean; - }; + /** @example 123456 */ + code: string; }; }; }; responses: { - /** @description Subscription created successfully. */ - 201: { + /** @description Email change applied */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": Record; + "application/json": { + ok?: boolean; + email?: string; + }; + }; + }; + /** @description Invalid or expired code, or current email not verified */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + message?: string; + }; }; }; }; }; - StatsController_getMarketingStats: { + EmailChangeController_cancel: { parameters: { query?: never; - header?: never; + header: { + authorization: string; + }; path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description Public marketing statistics */ + /** @description In-progress email change cancelled (idempotent) */ 200: { headers: { [name: string]: unknown; }; content: { "application/json": { - /** @example 150000 */ - total_count?: number; + ok?: boolean; }; }; }; From 3d2443533119f9a41fe1aa46967c941818584c73 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:56:10 +0100 Subject: [PATCH 49/78] feat(device): add Android API level 37 (Android 17) (#107) Adds 37 to EAndroidApiLevels; the --android-api-level flag description and cloud.ts validation both derive from the enum, so they pick it up for free. schema.types.ts regenerated from the API's swagger. Also corrects two stale runnerType notices that would now actively mislead: gpu1 claimed "API Level 34 or 35" (it has run 36 for a while and now 37), and m1 claimed "Pixel 7, API Level 34" when it runs the full device matrix at 34-36. m1 is capped at 36 API-side until the Mac fleet's AVDs are provisioned for 37. --- src/commands/cloud.ts | 4 ++-- src/types/domain/device.types.ts | 1 + src/types/generated/schema.types.ts | 37 ++++++++++------------------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 52cefbb..632a2b2 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -416,7 +416,7 @@ export const cloudCommand = defineCommand({ if (runnerType === 'm1') { out( ui.info( - 'runnerType m1 is experimental and currently supports Android (Pixel 7, API Level 34) only.', + 'runnerType m1 is experimental and currently supports Android only (all devices, API level 34-36).', ), ); } @@ -424,7 +424,7 @@ export const cloudCommand = defineCommand({ if (runnerType === 'gpu1') { out( ui.info( - 'runnerType gpu1 is Android-only (all devices, API Level 34 or 35), available to all users.', + 'runnerType gpu1 is Android-only (all devices, API level 34+), available to all users.', ), ); } diff --git a/src/types/domain/device.types.ts b/src/types/domain/device.types.ts index 45e2632..4f1ce7d 100644 --- a/src/types/domain/device.types.ts +++ b/src/types/domain/device.types.ts @@ -35,6 +35,7 @@ export enum EAndroidApiLevels { 'thirtyFive' = '35', 'thirtyFour' = '34', 'thirtyOne' = '31', + 'thirtySeven' = '37', 'thirtySix' = '36', 'thirtyThree' = '33', 'thirtyTwo' = '32', diff --git a/src/types/generated/schema.types.ts b/src/types/generated/schema.types.ts index 1fd27da..574e604 100644 --- a/src/types/generated/schema.types.ts +++ b/src/types/generated/schema.types.ts @@ -1617,7 +1617,7 @@ export interface components { testFileNames?: string; sequentialFlows?: string; /** @enum {string} */ - androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36"; + androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36" | "37"; /** @enum {string} */ androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet"; apiKey?: string; @@ -1657,7 +1657,7 @@ export interface components { testFileNames?: string; sequentialFlows?: string; /** @enum {string} */ - androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36"; + androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36" | "37"; /** @enum {string} */ androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet"; apiKey?: string; @@ -1708,7 +1708,7 @@ export interface components { testFileNames?: string; sequentialFlows?: string; /** @enum {string} */ - androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36"; + androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36" | "37"; /** @enum {string} */ androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet"; apiKey?: string; @@ -2997,7 +2997,8 @@ export interface operations { * "33", * "34", * "35", - * "36" + * "36", + * "37" * ], * "deprecated": false * }, @@ -3005,9 +3006,7 @@ export interface operations { * "name": "Pixel 6 Pro", * "apiLevels": [ * "33", - * "34", - * "35", - * "36" + * "35" * ], * "deprecated": false * }, @@ -3017,7 +3016,8 @@ export interface operations { * "33", * "34", * "35", - * "36" + * "36", + * "37" * ], * "deprecated": false * }, @@ -3027,37 +3027,24 @@ export interface operations { * "33", * "34", * "35", - * "36" + * "36", + * "37" * ], * "deprecated": false * }, * "generic-tablet": { * "name": "Generic Tablet", * "apiLevels": [ - * "33", - * "34", - * "35", - * "36" + * "33" * ], * "deprecated": false * } * }, * "androidPlay": { - * "pixel-6": { - * "name": "Pixel 6 (Google Play)", - * "apiLevels": [ - * "34", - * "35", - * "36" - * ], - * "deprecated": false - * }, * "pixel-7": { * "name": "Pixel 7 (Google Play)", * "apiLevels": [ - * "34", - * "35", - * "36" + * "34" * ], * "deprecated": false * } From 4af1ddbc60d3d33f85bb265e56e9bb1084480dab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:03:18 +0100 Subject: [PATCH 50/78] ci: bump pnpm/action-setup from 6 to 6.0.9 in the actions group (#103) Bumps the actions group with 1 update: [pnpm/action-setup](https://github.com/pnpm/action-setup). Updates `pnpm/action-setup` from 6 to 6.0.9 - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v6...v6.0.9) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-version: 6.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cli-ci.yml | 2 +- .github/workflows/npm-publish.yml | 2 +- .github/workflows/release-binaries.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 46f035b..abee162 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -77,7 +77,7 @@ jobs: /api/swagger.json - name: Setup pnpm - uses: pnpm/action-setup@v6 + uses: pnpm/action-setup@v6.0.9 with: version: 10 run_install: false diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 22481f7..17873d4 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v7 # Setup .npmrc file to publish to npm - name: Setup pnpm - uses: pnpm/action-setup@v6 + uses: pnpm/action-setup@v6.0.9 with: run_install: false diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index bfa1bf6..cb4b85c 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v6 + uses: pnpm/action-setup@v6.0.9 with: run_install: false From 3038ed829212bd2b946ff0aa2df6fbe172b95adc Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:39:09 +0100 Subject: [PATCH 51/78] chore(dev): release 5.2.0-beta.4 (#91) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Co-authored-by: finalerock44 <77282157+finalerock44@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index b3b3a94..5b0b170 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.2.0-beta.3" + ".": "5.2.0-beta.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6965596..d64f2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## [5.2.0-beta.4](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.3...v5.2.0-beta.4) (2026-08-06) + + +### Features + +* **artifacts:** prefer server-assembled bundle delivery for downloads ([#93](https://github.com/devicecloud-dev/dcd-cli/issues/93)) ([f6fcfaf](https://github.com/devicecloud-dev/dcd-cli/commit/f6fcfafa4ce5935decd15952107cd3ac1ebaad7b)) +* client-side envelope encryption of binaries, flow zips & env vars ([#94](https://github.com/devicecloud-dev/dcd-cli/issues/94)) ([a34d9d4](https://github.com/devicecloud-dev/dcd-cli/commit/a34d9d4516a985e92e4e07ec54f703358af45f6e)) +* **device:** add Android API level 37 (Android 17) ([#107](https://github.com/devicecloud-dev/dcd-cli/issues/107)) ([3d24435](https://github.com/devicecloud-dev/dcd-cli/commit/3d2443533119f9a41fe1aa46967c941818584c73)) +* **upload:** dedup encrypted binaries on the plaintext hash ([#101](https://github.com/devicecloud-dev/dcd-cli/issues/101)) ([6e244a9](https://github.com/devicecloud-dev/dcd-cli/commit/6e244a90dedff47c0bba5b1041fa6da4fe2e9d77)) + + +### Bug Fixes + +* **deps:** resolve pnpm audit failures in transitive dependencies ([#89](https://github.com/devicecloud-dev/dcd-cli/issues/89)) ([cc5ee4d](https://github.com/devicecloud-dev/dcd-cli/commit/cc5ee4d4595fd4b3e6abd9e3ad0d50b2a7db84ca)) +* **deps:** resolve three new transitive security advisories ([#106](https://github.com/devicecloud-dev/dcd-cli/issues/106)) ([f7934b0](https://github.com/devicecloud-dev/dcd-cli/commit/f7934b0743a4bf561f876082f124a87acc41041a)) + + +### Dependencies + +* bump chalk from 5.6.2 to 6.0.0 ([#98](https://github.com/devicecloud-dev/dcd-cli/issues/98)) ([1b29d2d](https://github.com/devicecloud-dev/dcd-cli/commit/1b29d2d5ec58d391c04dc29ea2e4c989a0a66e0c)) +* bump the minor-and-patch group across 1 directory with 5 updates ([#100](https://github.com/devicecloud-dev/dcd-cli/issues/100)) ([47b9d90](https://github.com/devicecloud-dev/dcd-cli/commit/47b9d905daa49d4d8491b1a8d710545e05822bb0)) +* bump the minor-and-patch group across 1 directory with 7 updates ([#92](https://github.com/devicecloud-dev/dcd-cli/issues/92)) ([f333be9](https://github.com/devicecloud-dev/dcd-cli/commit/f333be97d848e2cd40cbff124584de5af4f89a88)) +* patch js-yaml and brace-expansion DoS advisories ([#95](https://github.com/devicecloud-dev/dcd-cli/issues/95)) ([a4f11ff](https://github.com/devicecloud-dev/dcd-cli/commit/a4f11ff8d771de213b5acdb4f6fcf40f665e5d7b)) + + +### Code Refactoring + +* **cloud:** remove mitmproxy flags ([#102](https://github.com/devicecloud-dev/dcd-cli/issues/102)) ([3888fc5](https://github.com/devicecloud-dev/dcd-cli/commit/3888fc5cb0ea06828ed94c2d672e0215492f5147)) + ## [5.2.0-beta.3](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.2...v5.2.0-beta.3) (2026-07-13) diff --git a/package.json b/package.json index 83e841f..d719cc7 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.2.0-beta.3", + "version": "5.2.0-beta.4", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From 13d01eee5d0146049373011f304be2647e7f510b Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:49:34 +0100 Subject: [PATCH 52/78] fix(cloud): exclude config-shaped files from flow discovery (#114) Flow discovery excluded config files by filename: literally config.yaml / config.yml, plus the exact --config target. A second workspace config sharing the folder (config_build.yml alongside config_update.yml, as when one folder serves several CI workflows) survived discovery, got parsed as a flow, and failed with "Expected an array of steps". Add isWorkspaceConfigFile(), which classifies by shape rather than filename: no `---` separator, a top-level map, and at least one workspace-config-only key. Requiring a recognised key keeps a flow that is merely missing its `---` separator loud rather than silently dropped. Applied in plan() after applyFlowGlobs so it covers both the glob and default branches, and in planSingleFile so a custom-named config passed as the input gets the existing "pass the workspace folder path" error. Also fixes the sibling path-matching bugs in applyFlowGlobs, which are the same defect family. --config is now resolved once in plan() and both branches compare normalised absolute paths: - the default branch used `file.endsWith(configFile)`, so `--config ./x.yml` failed to match and `--config g.yml` would have dropped `flow-config.yml` - the glob branch compared a workspace-relative match against `path.basename(configFile)`, which only matched when the config sat at the workspace root - the config.yaml / config.yml name checks are anchored to the basename, so a genuine flow named my-config.yaml is no longer dropped; a real config with that name is still excluded, by shape The `--exclude-flows` workaround is no longer needed. The hand-rolled extension and .app-bundle checks in the glob branch are replaced with the existing isFlowFile helper, which does exactly that. Closes #99 --- src/services/execution-plan.service.ts | 105 ++++++++++++------ src/services/execution-plan.utils.ts | 47 ++++++++ test/integration/cloud.integration.test.ts | 121 +++++++++++++++++++++ 3 files changed, 237 insertions(+), 36 deletions(-) diff --git a/src/services/execution-plan.service.ts b/src/services/execution-plan.service.ts index 777f72b..9fa6c72 100644 --- a/src/services/execution-plan.service.ts +++ b/src/services/execution-plan.service.ts @@ -4,6 +4,7 @@ import * as path from 'node:path'; import { getFlowsToRunInSequence, isFlowFile, + isWorkspaceConfigFile, processDependencies, readDirectory, readTestYamlFileAsJson, @@ -198,16 +199,18 @@ function extractDeviceCloudOverrides( /** * Generate execution plan for a single flow file * @param normalizedInput - Normalized path to the flow file - * @param configFile - Optional custom config file path + * @param resolvedConfigFile - Optional absolute path to a custom config file * @returns Execution plan for the single file with dependencies */ async function planSingleFile( normalizedInput: string, - configFile?: string, + resolvedConfigFile?: string, ): Promise { + const inputBasename = path.basename(normalizedInput); if ( - normalizedInput.endsWith('config.yaml') || - normalizedInput.endsWith('config.yml') + inputBasename === 'config.yaml' || + inputBasename === 'config.yml' || + isWorkspaceConfigFile(normalizedInput) ) { throw new Error( 'If using config.yaml, pass the workspace folder path, not the config file or a custom path via --config', @@ -224,13 +227,14 @@ async function planSingleFile( } let workspaceConfig: IWorkspaceConfig | undefined; - if (configFile) { - const configFilePath = path.resolve(process.cwd(), configFile); - if (!fs.existsSync(configFilePath)) { - throw new Error(`Config file does not exist: ${configFilePath}`); + if (resolvedConfigFile) { + if (!fs.existsSync(resolvedConfigFile)) { + throw new Error(`Config file does not exist: ${resolvedConfigFile}`); } - workspaceConfig = readYamlFileAsJson(configFilePath) as IWorkspaceConfig; + workspaceConfig = readYamlFileAsJson( + resolvedConfigFile, + ) as IWorkspaceConfig; } const checkedDependancies = await checkDependencies(normalizedInput); @@ -249,7 +253,7 @@ async function planSingleFile( * @param workspaceConfig - Workspace configuration containing flow globs * @param normalizedInput - Normalized path to the workspace directory * @param unfilteredFlowFiles - List of all discovered flow files - * @param configFile - Optional custom config file path + * @param resolvedConfigFile - Optional absolute path to a custom config file * @param excludeFlows - --exclude-flows patterns to re-apply to glob matches * @returns Filtered list of flow file paths matching the globs */ @@ -257,9 +261,24 @@ async function applyFlowGlobs( workspaceConfig: IWorkspaceConfig, normalizedInput: string, unfilteredFlowFiles: string[], - configFile?: string, + resolvedConfigFile?: string, excludeFlows?: string[], ): Promise { + // Both branches compare absolute paths, so `--config ./x.yml` and + // `--config /abs/x.yml` behave identically, and a same-named file in a + // sibling directory is never mistaken for the active config. + const activeConfig = resolvedConfigFile + ? path.normalize(resolvedConfigFile) + : undefined; + const isExcludedConfig = (absolutePath: string): boolean => { + const base = path.basename(absolutePath); + if (base === 'config.yaml' || base === 'config.yml') return true; + return ( + activeConfig !== undefined && + path.normalize(absolutePath) === activeConfig + ); + }; + if (workspaceConfig.flows) { const globs = workspaceConfig.flows.map((g) => g); // fs.globSync lands in Node 22; the CLI's `engines.node` already requires it. @@ -273,31 +292,19 @@ async function applyFlowGlobs( } }); + // Resolve before filtering: glob matches are relative to normalizedInput, + // so comparing them against the config path only ever worked when the + // config happened to sit at the workspace root. const globbedFlowFiles = matchedFiles - .filter((file: string) => { - if (file === 'config.yaml' || file === 'config.yml') return false; - if (configFile && file === path.basename(configFile)) return false; - if (!file.endsWith('.yaml') && !file.endsWith('.yml')) return false; - const pathParts = file.split(path.sep); - for (const part of pathParts) { - if (part.endsWith('.app')) return false; - } - - return true; - }) - .map((file) => path.resolve(normalizedInput, file)); + .map((file) => path.resolve(normalizedInput, file)) + .filter((file) => !isExcludedConfig(file) && isFlowFile(file)); // Re-globbing from disk bypasses the earlier --exclude-flows filter, so // re-apply it here or excluded flows sneak back in via `flows:` globs. return filterFlowFiles(globbedFlowFiles, excludeFlows); } - return unfilteredFlowFiles.filter( - (file) => - !file.endsWith('config.yaml') && - !file.endsWith('config.yml') && - (!configFile || !file.endsWith(configFile)), - ); + return unfilteredFlowFiles.filter((file) => !isExcludedConfig(file)); } /** @@ -382,6 +389,9 @@ export async function plan(options: PlanOptions): Promise { } = options; const normalizedInput = path.normalize(input); const flowMetadata: Record> = {}; + const resolvedConfigFile = configFile + ? path.resolve(process.cwd(), configFile) + : undefined; if (!fs.existsSync(normalizedInput)) { throw new Error( @@ -390,7 +400,7 @@ export async function plan(options: PlanOptions): Promise { } if (fs.lstatSync(normalizedInput).isFile()) { - return planSingleFile(normalizedInput, configFile); + return planSingleFile(normalizedInput, resolvedConfigFile); } let unfilteredFlowFiles = await readDirectory(normalizedInput, isFlowFile); @@ -405,13 +415,14 @@ export async function plan(options: PlanOptions): Promise { unfilteredFlowFiles = filterFlowFiles(unfilteredFlowFiles, excludeFlows); let workspaceConfig: IWorkspaceConfig; - if (configFile) { - const configFilePath = path.resolve(process.cwd(), configFile); - if (!fs.existsSync(configFilePath)) { - throw new Error(`Config file does not exist: ${configFilePath}`); + if (resolvedConfigFile) { + if (!fs.existsSync(resolvedConfigFile)) { + throw new Error(`Config file does not exist: ${resolvedConfigFile}`); } - workspaceConfig = readYamlFileAsJson(configFilePath) as IWorkspaceConfig; + workspaceConfig = readYamlFileAsJson( + resolvedConfigFile, + ) as IWorkspaceConfig; } else { workspaceConfig = getWorkspaceConfig(normalizedInput, unfilteredFlowFiles); } @@ -420,10 +431,32 @@ export async function plan(options: PlanOptions): Promise { workspaceConfig, normalizedInput, unfilteredFlowFiles, - configFile, + resolvedConfigFile, excludeFlows, ); + // The exclusions above are filename-based: they only catch + // `config.yaml`/`config.yml` and the active --config target. Any other + // workspace config sharing the folder (a second CI workflow's, say) is still + // on the list and would be parsed as a flow, so drop config-shaped files by + // shape — see dcd-cli#99. + const configShapedFiles = new Set( + unfilteredFlowFiles.filter((file) => isWorkspaceConfigFile(file)), + ); + if (configShapedFiles.size > 0) { + if (debug) { + console.log( + `[DEBUG] Skipping ${configShapedFiles.size} workspace config file(s): ${[ + ...configShapedFiles, + ].join(', ')}`, + ); + } + + unfilteredFlowFiles = unfilteredFlowFiles.filter( + (file) => !configShapedFiles.has(file), + ); + } + if (unfilteredFlowFiles.length === 0) { const error = workspaceConfig.flows ? new Error( diff --git a/src/services/execution-plan.utils.ts b/src/services/execution-plan.utils.ts index b117759..3b7f24b 100644 --- a/src/services/execution-plan.utils.ts +++ b/src/services/execution-plan.utils.ts @@ -60,6 +60,53 @@ export function isFlowFile(filePath: string): boolean { return filePath.endsWith('.yaml') || filePath.endsWith('.yml'); } +/** + * Top-level keys that only ever appear in a workspace config (see + * IWorkspaceConfig in execution-plan.service.ts). Deliberately excludes keys + * Maestro also allows in flow front matter — appId, name, tags, env, + * onFlowStart, onFlowComplete, jsEngine. + */ +const WORKSPACE_CONFIG_KEYS = new Set([ + 'excludeTags', + 'executionOrder', + 'flows', + 'includeTags', + 'local', + 'notifications', + 'platform', +]); + +/** + * True when a YAML file is a workspace config rather than a runnable flow. + * + * A flow is either `front matter --- steps` or a bare steps array; a + * single-document top-level map carrying workspace-config keys is neither, and + * left in the flow list it blows up processDependencies with "Expected an array + * of steps". Detection is by shape, not filename, so several named configs can + * coexist in one folder (dcd-cli#99). Requiring a recognised config key — not + * just "single document, top-level map" — keeps a flow that is merely *missing* + * its `---` separator loud rather than silently dropped. + * + * @param filePath - Path to the YAML file to classify + * @returns Whether the file is a workspace config rather than a flow + */ +export function isWorkspaceConfigFile(filePath: string): boolean { + let parsed; + try { + parsed = readTestYamlFileAsJson(filePath); + } catch { + // Unparseable — leave it in the flow list so the existing error path reports it. + return false; + } + + const { config, testSteps } = parsed; + if (config !== null) return false; // has `---` front matter → flow + if (Array.isArray(testSteps)) return false; // bare steps array → flow + if (!testSteps || typeof testSteps !== 'object') return false; + + return Object.keys(testSteps).some((key) => WORKSPACE_CONFIG_KEYS.has(key)); +} + export const readYamlFileAsJson = (filePath: string) => { try { const normalizedPath = path.normalize(filePath); diff --git a/test/integration/cloud.integration.test.ts b/test/integration/cloud.integration.test.ts index 7d052af..8ac4c93 100644 --- a/test/integration/cloud.integration.test.ts +++ b/test/integration/cloud.integration.test.ts @@ -287,6 +287,127 @@ appId: com.example.app }); }); + // Regression cover for dcd-cli#99: config files were excluded from flow + // discovery by *filename* (literal config.yaml/config.yml plus the exact + // --config target), so a second workspace config sharing the folder was + // parsed as a flow and blew up with "Expected an array of steps". + // These must pass the flow *directory* — the --config tests below pass a + // single file, which short-circuits into planSingleFile and never reaches + // flow discovery. + describe('workspace config discovery', () => { + let workspaceDir: string; + let buildConfig: string; + let updateConfig: string; + let globConfig: string; + + before(() => { + workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dcd-test-ws-')); + + fs.writeFileSync( + path.join(workspaceDir, 'flow.yaml'), + `appId: com.example.app +--- +- launchApp +- tapOn: "Login" +`, + ); + + // A genuine flow whose name merely ends in "config.yaml". The old + // unanchored endsWith() dropped it; it must run. + fs.writeFileSync( + path.join(workspaceDir, 'smoke-config.yaml'), + `appId: com.example.app +name: smoke-config +--- +- launchApp +`, + ); + + // Two sibling workspace configs with custom names, as a folder serving + // several CI workflows would have. Neither is a flow. + buildConfig = path.join(workspaceDir, 'config_build.yml'); + fs.writeFileSync( + buildConfig, + `flows: + - ./**/*.yaml +includeTags: + - build +`, + ); + + // Deliberately carries no tag or flow filtering, so when it is the active + // --config it can't mask the bug by filtering its sibling away first. + updateConfig = path.join(workspaceDir, 'config_update.yml'); + fs.writeFileSync( + updateConfig, + `platform: + android: + disableAnimations: true +`, + ); + + globConfig = path.join(workspaceDir, 'config_glob.yml'); + fs.writeFileSync( + globConfig, + `flows: + - ./**/*.yaml + - ./**/*.yml +`, + ); + }); + + after(() => { + if (fs.existsSync(workspaceDir)) { + fs.rmSync(workspaceDir, { force: true, recursive: true }); + } + }); + + it('should ignore sibling config files not named in --config', async () => { + const command = `${CLI} cloud ${androidAppFile} "${workspaceDir}" --api-key ${mockApiKey} --api-url ${mockApiUrl} --config "${updateConfig}" --dry-run`; + + const { stdout } = await exec(command, { timeout: 15_000 }); + expect(stdout).to.include('The following tests would have been run'); + expect(stdout).to.include('flow.yaml'); + expect(stdout).to.not.include('config_build.yml'); + expect(stdout).to.not.include('config_glob.yml'); + expect(stdout).to.not.include('Expected an array of steps'); + }); + + it('should ignore config-shaped files when no --config is passed', async () => { + const command = `${CLI} cloud ${androidAppFile} "${workspaceDir}" --api-key ${mockApiKey} --api-url ${mockApiUrl} --debug --dry-run`; + + const { stdout } = await exec(command, { timeout: 15_000 }); + expect(stdout).to.include('flow.yaml'); + expect(stdout).to.include('[DEBUG] Skipping 3 workspace config file(s)'); + expect(stdout).to.not.include('Expected an array of steps'); + }); + + it('should ignore config-shaped files matched by a flows glob', async () => { + const command = `${CLI} cloud ${androidAppFile} "${workspaceDir}" --api-key ${mockApiKey} --api-url ${mockApiUrl} --config "${globConfig}" --dry-run`; + + const { stdout } = await exec(command, { timeout: 15_000 }); + expect(stdout).to.include('flow.yaml'); + expect(stdout).to.not.include('config_build.yml'); + expect(stdout).to.not.include('config_update.yml'); + expect(stdout).to.not.include('Expected an array of steps'); + }); + + it('should run a flow whose filename merely ends in config.yaml', async () => { + const command = `${CLI} cloud ${androidAppFile} "${workspaceDir}" --api-key ${mockApiKey} --api-url ${mockApiUrl} --config "${updateConfig}" --dry-run`; + + const { stdout } = await exec(command, { timeout: 15_000 }); + expect(stdout).to.include('smoke-config.yaml'); + }); + + it('should reject a custom-named config passed as the flow input', async () => { + const command = `${CLI} cloud ${androidAppFile} "${buildConfig}" --api-key ${mockApiKey} --api-url ${mockApiUrl} --dry-run`; + + const { output } = await runExpectingFailure(command); + expect(output).to.include('pass the workspace folder path'); + expect(output).to.not.include('Expected an array of steps'); + }); + }); + describe('file and binary management', () => { it('should support app binary ID instead of file', async () => { const command = `${CLI} cloud --app-binary-id test-binary-123 ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --dry-run`; From 0c70928d8c00ca177d4fa13b301aa20746edaf4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:00:15 +0100 Subject: [PATCH 53/78] chore: bump eslint-plugin-unicorn from 72.0.0 to 73.0.0 (#113) Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 72.0.0 to 73.0.0. - [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases) - [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v72.0.0...v73.0.0) --- updated-dependencies: - dependency-name: eslint-plugin-unicorn dependency-version: 73.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: finalerock44 <77282157+finalerock44@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 79 +++++++++++++++++++++++++------------------------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index d719cc7..7dbcc89 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "chai": "^6.2.2", "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-unicorn": "^72.0.0", + "eslint-plugin-unicorn": "^73.0.0", "husky": "^9.1.7", "mocha": "^11.7.6", "prettier": "^3.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a75f3d2..a0c985a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,8 +109,8 @@ importers: specifier: ^10.1.8 version: 10.1.8(eslint@10.8.0) eslint-plugin-unicorn: - specifier: ^72.0.0 - version: 72.0.0(eslint@10.8.0) + specifier: ^73.0.0 + version: 73.0.0(eslint@10.8.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -574,8 +574,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.11.4: - resolution: {integrity: sha512-s4+sLr9mZ/CyqeRritFeYV/Zx73OAtmaHn6kkBS1XRoJn1hrg3xIDUcpicAEX68tkcIN0iBCgti31C8zxtkhsQ==} + baseline-browser-mapping@2.11.13: + resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -602,8 +602,8 @@ packages: browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - browserslist@4.28.7: - resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -634,8 +634,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001806: - resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -705,8 +705,9 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} @@ -761,8 +762,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.396: - resolution: {integrity: sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==} + electron-to-chromium@1.5.403: + resolution: {integrity: sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -815,8 +816,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-unicorn@72.0.0: - resolution: {integrity: sha512-hqO6ksoOHO+ZhdseTuKRVQbx9U7PRO/cv8qAR1mctwzdVO2hYud8uS9luAhp43RJgziYgHAph8eHyipT8GL0ng==} + eslint-plugin-unicorn@73.0.0: + resolution: {integrity: sha512-V0YatLe9nkGhXEXKe2Qljb1EY0sJHwDV0HUF1NKFwtsHh/fU7qGHDgv+6fchzZcgU2/7noHo2gdjnmo0P2uDPw==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -1010,8 +1011,8 @@ packages: 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 hasBin: true - globals@17.8.0: - resolution: {integrity: sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==} + globals@17.9.0: + resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} engines: {node: '>=18'} gopd@1.2.0: @@ -1305,8 +1306,8 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} - node-releases@2.0.51: - resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} engines: {node: '>=18'} node-stream-zip@1.16.0: @@ -1700,8 +1701,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2186,7 +2187,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.11.4: {} + baseline-browser-mapping@2.11.13: {} big-integer@1.6.52: {} @@ -2218,13 +2219,13 @@ snapshots: browser-stdout@1.3.1: {} - browserslist@4.28.7: + browserslist@4.28.8: dependencies: - baseline-browser-mapping: 2.11.4 - caniuse-lite: 1.0.30001806 - electron-to-chromium: 1.5.396 - node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.7) + baseline-browser-mapping: 2.11.13 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.403 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) buffer-crc32@1.0.0: {} @@ -2246,7 +2247,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001806: {} + caniuse-lite@1.0.30001809: {} chai@6.2.2: {} @@ -2298,9 +2299,9 @@ snapshots: cookie@0.7.2: {} - core-js-compat@3.49.0: + core-js-compat@3.50.0: dependencies: - browserslist: 4.28.7 + browserslist: 4.28.8 cors@2.8.6: dependencies: @@ -2349,7 +2350,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.396: {} + electron-to-chromium@1.5.403: {} emoji-regex@8.0.0: {} @@ -2410,19 +2411,19 @@ snapshots: dependencies: eslint: 10.8.0 - eslint-plugin-unicorn@72.0.0(eslint@10.8.0): + eslint-plugin-unicorn@73.0.0(eslint@10.8.0): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) '@eslint/css-tree': 4.0.5 - browserslist: 4.28.7 + browserslist: 4.28.8 change-case: 5.4.4 ci-info: 4.4.0 - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 detect-indent: 7.0.2 entities: 4.5.0 eslint: 10.8.0 find-up-simple: 1.0.1 - globals: 17.8.0 + globals: 17.9.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 is-identifier: 1.1.0 @@ -2683,7 +2684,7 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - globals@17.8.0: {} + globals@17.9.0: {} gopd@1.2.0: {} @@ -2931,7 +2932,7 @@ snapshots: node-forge@1.4.0: {} - node-releases@2.0.51: {} + node-releases@2.0.53: {} node-stream-zip@1.16.0: {} @@ -3308,9 +3309,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.7): + update-browserslist-db@1.3.1(browserslist@4.28.8): dependencies: - browserslist: 4.28.7 + browserslist: 4.28.8 escalade: 3.2.0 picocolors: 1.1.1 From 2223dd48bcb8e19d28a842df807939f5bc9879c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:07:34 +0100 Subject: [PATCH 54/78] deps: bump the minor-and-patch group with 5 updates (#112) Bumps the minor-and-patch group with 5 updates: | Package | From | To | | --- | --- | --- | | [@supabase/supabase-js](https://github.com/supabase/supabase-js/tree/HEAD/packages/core/supabase-js) | `2.111.0` | `2.112.2` | | [js-yaml](https://github.com/nodeca/js-yaml) | `5.2.2` | `5.2.3` | | [mocha](https://github.com/mochajs/mocha) | `11.7.6` | `11.8.0` | | [tsx](https://github.com/privatenumber/tsx) | `4.23.1` | `4.23.11` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.65.0` | `8.66.0` | Updates `@supabase/supabase-js` from 2.111.0 to 2.112.2 - [Release notes](https://github.com/supabase/supabase-js/releases) - [Changelog](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/CHANGELOG.md) - [Commits](https://github.com/supabase/supabase-js/commits/v2.112.2/packages/core/supabase-js) Updates `js-yaml` from 5.2.2 to 5.2.3 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/5.2.2...5.2.3) Updates `mocha` from 11.7.6 to 11.8.0 - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/v11.8.0/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v11.7.6...v11.8.0) Updates `tsx` from 4.23.1 to 4.23.11 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.23.1...v4.23.11) Updates `typescript-eslint` from 8.65.0 to 8.66.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.66.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@supabase/supabase-js" dependency-version: 2.112.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: js-yaml dependency-version: 5.2.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: mocha dependency-version: 11.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: tsx dependency-version: 4.23.11 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: typescript-eslint dependency-version: 8.66.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: finalerock44 <77282157+finalerock44@users.noreply.github.com> --- pnpm-lock.yaml | 429 +++++++++++++++++++++++++------------------------ 1 file changed, 217 insertions(+), 212 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0c985a..3d6f08b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 1.30.0(zod@4.4.3) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.111.0 + version: 2.112.2 bplist-parser: specifier: ^0.3.2 version: 0.3.2 @@ -58,7 +58,7 @@ importers: version: 0.2.2 js-yaml: specifier: ^5.2.2 - version: 5.2.2 + version: 5.2.3 node-apk: specifier: ^1.2.1 version: 1.2.1 @@ -116,7 +116,7 @@ importers: version: 9.1.7 mocha: specifier: ^11.7.6 - version: 11.7.6 + version: 11.8.0 prettier: specifier: ^3.8.4 version: 3.9.6 @@ -125,13 +125,13 @@ importers: version: 0.4.0 tsx: specifier: ^4.22.4 - version: 4.23.1 + version: 4.23.11 typescript: specifier: ^6.0.3 version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.65.0(eslint@10.8.0)(typescript@6.0.3) + version: 8.66.0(eslint@10.8.0)(typescript@6.0.3) packages: @@ -143,158 +143,158 @@ packages: resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} engines: {node: '>= 20.12.0'} - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -402,32 +402,37 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@supabase/auth-js@2.111.0': - resolution: {integrity: sha512-hbRLgyQZEX0SDyF4LYXpv94qOIQyFATfpT5SIs2V0SisHnisVRkYgiPQWzv80l4S2mO3qof78rmoH9j5zoFsYQ==} + '@supabase/auth-js@2.112.2': + resolution: {integrity: sha512-l1InCp4j98d09LZ6+RgubgF4eVPGBGXcLEhFusLg1qUCHJ2IEkYu5FohKK+eaFmIOwEk0kqG/j/lycw5e15mcQ==} engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.111.0': - resolution: {integrity: sha512-RW/OCsd6MO592zU8ifzP8/f8XzxxIdpb+Up5XaOtE26Fw+3zTp475WX7+GuuktiD1WF8pFUDe6khUPbMp77RCw==} + '@supabase/functions-js@2.112.2': + resolution: {integrity: sha512-oMuSWN0ERmrG9S6kOM0bwhHmESGVl3kMtkZl2dNCU/r89hMiziX4GfD1omNo9QcBDele4N0GwSZ7hdbpuiA35A==} engines: {node: '>=22.0.0'} '@supabase/phoenix@0.4.5': resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} - '@supabase/postgrest-js@2.111.0': - resolution: {integrity: sha512-pcqeDsnWP0lx9GawduYxNZJHeuTm53O7L0SC8RF8tniV3GWIPY6me6OTdnwzdwNUmNy1dzUVtSyIfE6+OflzPQ==} + '@supabase/postgrest-js@2.112.2': + resolution: {integrity: sha512-ewhhtRny/HFRGhUTTg/PsqIatsl8OhW8Eha/Tz4S+SRAXBnuhKei9ZpsQTgL/3XcH9UEwuPQyQgQ9itq7nRQeg==} engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.111.0': - resolution: {integrity: sha512-6oRf/vZyRwg8f8GbFSJkrD2w4HAu/yTvyMViHXHS+H5hNJzdXCrUR7cP5oW7daT3YlRnzRPY9LcGSJKZAmfMSg==} + '@supabase/realtime-js@2.112.2': + resolution: {integrity: sha512-cd9/CEUJ6Go13FxtfiuC5rYELJtuQzVzTXlGG+XjSppjDS+anq+xo++WQe7ZRUNTuHOCeyKRwmx9Hw/OQJ04ig==} engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.111.0': - resolution: {integrity: sha512-UEViNmTzVOxE8dqUA81wls+n9xgmlvSFfhfwo6QxrO4kQOytCYyw3ciYFoi4XoD4Jl95NJ3jnndHN5iIudWzqw==} + '@supabase/storage-js@2.112.2': + resolution: {integrity: sha512-6jyBq/J1iXOHNpbjCZS7gFcDk49iM1MCJUVkDl71gLd/+XnLDzpUBs8icGebtwiHpl4kVszxIRDYAosbF4Rsig==} engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.111.0': - resolution: {integrity: sha512-9q0/AULthQnWeiDh1vGyjoJZbSY04bu6qHcWit70pqEYn5Kv/dkCPY62Ja1123jEnJbB9Vd2pjY7Kvk/lK3peA==} + '@supabase/supabase-js@2.112.2': + resolution: {integrity: sha512-UyI1epU9B4X51HvNpkmlwTdF20fEcz2vyvrcDKVzFN4jZN41f5iQRqsiIQjAY5OVJD6ljqA/1g9JQeOTvFHpkA==} engines: {node: '>=22.0.0'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -456,63 +461,63 @@ packages: '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} - '@typescript-eslint/eslint-plugin@8.65.0': - resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} + '@typescript-eslint/eslint-plugin@8.66.0': + resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.65.0 + '@typescript-eslint/parser': ^8.66.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.65.0': - resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} + '@typescript-eslint/parser@8.66.0': + resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.65.0': - resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} + '@typescript-eslint/project-service@8.66.0': + resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.65.0': - resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} + '@typescript-eslint/scope-manager@8.66.0': + resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.65.0': - resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} + '@typescript-eslint/tsconfig-utils@8.66.0': + resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.65.0': - resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} + '@typescript-eslint/type-utils@8.66.0': + resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.65.0': - resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} + '@typescript-eslint/types@8.66.0': + resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.65.0': - resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} + '@typescript-eslint/typescript-estree@8.66.0': + resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.65.0': - resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} + '@typescript-eslint/utils@8.66.0': + resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.65.0': - resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} + '@typescript-eslint/visitor-keys@8.66.0': + resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.10': @@ -794,8 +799,8 @@ packages: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} hasBin: true @@ -1157,8 +1162,8 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-yaml@5.2.2: - resolution: {integrity: sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==} + js-yaml@5.2.3: + resolution: {integrity: sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==} hasBin: true jsesc@3.1.0: @@ -1281,8 +1286,8 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - mocha@11.7.6: - resolution: {integrity: sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==} + mocha@11.8.0: + resolution: {integrity: sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true @@ -1517,8 +1522,8 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} - serialize-javascript@7.0.6: - resolution: {integrity: sha512-ATTK5Q4gFVg0YDp1my2vqygyvhcklD/UV5GIlYHooGTn/NogJqIzpetkD6E5kmuVULqz/S9inUL25XcAgDRJQg==} + serialize-javascript@7.1.0: + resolution: {integrity: sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==} engines: {node: '>=20.0.0'} serve-static@2.2.1: @@ -1661,8 +1666,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.23.1: - resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} + tsx@4.23.11: + resolution: {integrity: sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==} engines: {node: '>=18.0.0'} hasBin: true @@ -1682,8 +1687,8 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript-eslint@8.65.0: - resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} + typescript-eslint@8.66.0: + resolution: {integrity: sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1805,82 +1810,82 @@ snapshots: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@esbuild/aix-ppc64@0.28.1': + '@esbuild/aix-ppc64@0.28.2': optional: true - '@esbuild/android-arm64@0.28.1': + '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/android-arm@0.28.1': + '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-x64@0.28.1': + '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.28.1': + '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.28.1': + '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.28.1': + '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.28.1': + '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/linux-arm64@0.28.1': + '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/linux-arm@0.28.1': + '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-ia32@0.28.1': + '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-loong64@0.28.1': + '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.28.1': + '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-ppc64@0.28.1': + '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-riscv64@0.28.1': + '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-s390x@0.28.1': + '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-x64@0.28.1': + '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.28.1': + '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.28.1': + '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.28.1': + '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.28.1': + '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.28.1': + '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/sunos-x64@0.28.1': + '@esbuild/sunos-x64@0.28.2': optional: true - '@esbuild/win32-arm64@0.28.1': + '@esbuild/win32-arm64@0.28.2': optional: true - '@esbuild/win32-ia32@0.28.1': + '@esbuild/win32-ia32@0.28.2': optional: true - '@esbuild/win32-x64@0.28.1': + '@esbuild/win32-x64@0.28.2': optional: true '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0)': @@ -1992,37 +1997,37 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@supabase/auth-js@2.111.0': + '@supabase/auth-js@2.112.2': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.111.0': + '@supabase/functions-js@2.112.2': dependencies: tslib: 2.8.1 '@supabase/phoenix@0.4.5': {} - '@supabase/postgrest-js@2.111.0': + '@supabase/postgrest-js@2.112.2': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.111.0': + '@supabase/realtime-js@2.112.2': dependencies: '@supabase/phoenix': 0.4.5 tslib: 2.8.1 - '@supabase/storage-js@2.111.0': + '@supabase/storage-js@2.112.2': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.111.0': + '@supabase/supabase-js@2.112.2': dependencies: - '@supabase/auth-js': 2.111.0 - '@supabase/functions-js': 2.111.0 - '@supabase/postgrest-js': 2.111.0 - '@supabase/realtime-js': 2.111.0 - '@supabase/storage-js': 2.111.0 + '@supabase/auth-js': 2.112.2 + '@supabase/functions-js': 2.112.2 + '@supabase/postgrest-js': 2.112.2 + '@supabase/realtime-js': 2.112.2 + '@supabase/storage-js': 2.112.2 '@types/chai@5.2.3': dependencies: @@ -2049,14 +2054,14 @@ snapshots: dependencies: '@types/node': 26.1.2 - '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.65.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/parser': 8.66.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/type-utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.66.0 eslint: 10.8.0 ignore: 7.0.6 natural-compare: 1.4.0 @@ -2065,41 +2070,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.65.0(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.66.0(eslint@10.8.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.66.0 debug: 4.4.3(supports-color@8.1.1) eslint: 10.8.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.65.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.66.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) + '@typescript-eslint/types': 8.66.0 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.65.0': + '@typescript-eslint/scope-manager@8.66.0': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 - '@typescript-eslint/tsconfig-utils@8.65.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.66.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.65.0(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.66.0(eslint@10.8.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) eslint: 10.8.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2107,14 +2112,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.65.0': {} + '@typescript-eslint/types@8.66.0': {} - '@typescript-eslint/typescript-estree@8.65.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.66.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.65.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/project-service': 8.66.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.3 semver: 7.8.5 @@ -2124,20 +2129,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.66.0(eslint@10.8.0)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) eslint: 10.8.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.65.0': + '@typescript-eslint/visitor-keys@8.66.0': dependencies: - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/types': 8.66.0 eslint-visitor-keys: 5.0.1 '@xmldom/xmldom@0.9.10': {} @@ -2372,34 +2377,34 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.28.1: + esbuild@0.28.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 + '@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 escalade@3.2.0: {} @@ -2785,7 +2790,7 @@ snapshots: js-base64@3.7.8: {} - js-yaml@5.2.2: + js-yaml@5.2.3: dependencies: argparse: 2.0.1 @@ -2894,7 +2899,7 @@ snapshots: dependencies: minipass: 7.1.3 - mocha@11.7.6: + mocha@11.8.0: dependencies: browser-stdout: 1.3.1 chokidar: 4.0.3 @@ -2905,12 +2910,12 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 5.2.2 + js-yaml: 5.2.3 log-symbols: 4.1.0 minimatch: 9.0.7 ms: 2.1.3 picocolors: 1.1.1 - serialize-javascript: 7.0.6 + serialize-javascript: 7.1.0 strip-json-comments: 3.1.1 supports-color: 8.1.1 workerpool: 9.3.4 @@ -3118,7 +3123,7 @@ snapshots: transitivePeerDependencies: - supports-color - serialize-javascript@7.0.6: {} + serialize-javascript@7.1.0: {} serve-static@2.2.1: dependencies: @@ -3264,9 +3269,9 @@ snapshots: tslib@2.8.1: {} - tsx@4.23.1: + tsx@4.23.11: dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 @@ -3292,12 +3297,12 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typescript-eslint@8.65.0(eslint@10.8.0)(typescript@6.0.3): + typescript-eslint@8.66.0(eslint@10.8.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.65.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.66.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) eslint: 10.8.0 typescript: 6.0.3 transitivePeerDependencies: From 96147dfe56ea3b024da52f1f6f282b7a0884e63b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:10:31 +0100 Subject: [PATCH 55/78] ci: bump pnpm/action-setup from 6.0.9 to 6.0.10 in the actions group (#111) Bumps the actions group with 1 update: [pnpm/action-setup](https://github.com/pnpm/action-setup). Updates `pnpm/action-setup` from 6.0.9 to 6.0.10 - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v6.0.9...v6.0.10) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-version: 6.0.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: finalerock44 <77282157+finalerock44@users.noreply.github.com> --- .github/workflows/cli-ci.yml | 2 +- .github/workflows/npm-publish.yml | 2 +- .github/workflows/release-binaries.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index abee162..0af984a 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -77,7 +77,7 @@ jobs: /api/swagger.json - name: Setup pnpm - uses: pnpm/action-setup@v6.0.9 + uses: pnpm/action-setup@v6.0.10 with: version: 10 run_install: false diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 17873d4..4611165 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v7 # Setup .npmrc file to publish to npm - name: Setup pnpm - uses: pnpm/action-setup@v6.0.9 + uses: pnpm/action-setup@v6.0.10 with: run_install: false diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index cb4b85c..219d6fc 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v6.0.9 + uses: pnpm/action-setup@v6.0.10 with: run_install: false From 6858220e3dc54ae5e505e4dd84aa3fedf3ca2e6e Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:13:28 +0100 Subject: [PATCH 56/78] chore: pin the next dev beta to 5.3.1-beta.1 (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retargets the pending dev beta release from 5.2.0-beta.5 to 5.3.1-beta.1. The beta line is still numbered off 5.2.0 because the stable 5.3.0 release never completed: the promote PR (#108) merged with `Release-As: 5.3.0`, but the release-please PR it generated (#109) is still open, so no v5.3.0 tag exists and `.release-please-manifest-beta.json` sat at 5.2.0-beta.4. Release-please therefore proposed 5.2.0-beta.5 (#115) for the flow-discovery fix in #114, which is behind the stable line rather than ahead of it. The stable line is 5.3.0, so the next beta belongs on the 5.3.1 patch series. This commit is intentionally empty — the payload is the footer below, which release-please reads to pin the exact version of the next dev release. It applies once and needs no follow-up cleanup, unlike a `release-as` key in release-please-config-beta.json, which would pin every subsequent dev release until removed. Release-As: 5.3.1-beta.1 From 57711c691fcfdd9a5b92c0c016ad072d73556550 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:19:43 +0100 Subject: [PATCH 57/78] chore(dev): release 5.3.1-beta.1 (#115) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index 5b0b170..c540ca5 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.2.0-beta.4" + ".": "5.3.1-beta.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d64f2cb..b39946b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [5.3.1-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.4...v5.3.1-beta.1) (2026-08-10) + + +### Bug Fixes + +* **cloud:** exclude config-shaped files from flow discovery ([#114](https://github.com/devicecloud-dev/dcd-cli/issues/114)) ([13d01ee](https://github.com/devicecloud-dev/dcd-cli/commit/13d01eee5d0146049373011f304be2647e7f510b)), closes [#99](https://github.com/devicecloud-dev/dcd-cli/issues/99) + + +### Dependencies + +* bump the minor-and-patch group with 5 updates ([#112](https://github.com/devicecloud-dev/dcd-cli/issues/112)) ([2223dd4](https://github.com/devicecloud-dev/dcd-cli/commit/2223dd48bcb8e19d28a842df807939f5bc9879c5)) + + +### Miscellaneous + +* pin the next dev beta to 5.3.1-beta.1 ([#116](https://github.com/devicecloud-dev/dcd-cli/issues/116)) ([6858220](https://github.com/devicecloud-dev/dcd-cli/commit/6858220e3dc54ae5e505e4dd84aa3fedf3ca2e6e)) + ## [5.2.0-beta.4](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.3...v5.2.0-beta.4) (2026-08-06) diff --git a/package.json b/package.json index 7dbcc89..c023004 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.2.0-beta.4", + "version": "5.3.1-beta.1", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From 1973a42a1c4c01d0db29a106aa9c6a8f82da6853 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:51:20 +0100 Subject: [PATCH 58/78] =?UTF-8?q?fix(cloud):=20reject=20malformed=20execut?= =?UTF-8?q?ionOrder=20instead=20of=20silently=20runni=E2=80=A6=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(cloud): reject malformed executionOrder instead of silently running in parallel A workspace config.yaml was yaml.load'ed and straight-cast to IWorkspaceConfig, so a wrong-shaped executionOrder was never checked. The intuitive bare-list form made executionOrder an Array, .flowsOrder came back undefined, resolveSequentialFlows returned [], and every flow ran in parallel - same cost, wrong semantics, green run. The only symptom was depends_on being null on every result row. Add a zod schema as the single source of truth for the config shape (src/services/workspace-config.schema.ts) and route all three former cast sites through one validated loader, loadWorkspaceConfig: - A malformed executionOrder is now fatal (exit 1), with a message showing what was found next to the expected shape. A bare list is not valid Maestro either, so there is nothing to accept - and a warning in CI logs is exactly what got missed. - Unrecognised top-level keys warn (and are preserved, since the config is forwarded to the API as fields.workspaceConfig), catching flowOrder, a top-level continueOnFailure, tags in place of includeTags, and flowTimeout. - executionOrder on a single-file input warns instead of being dropped: planSingleFile never sequences, so it was silently ignored even when well-formed. - continueOnFailure's real default (true) now lives in the schema instead of being re-specified at three read sites. - WORKSPACE_CONFIG_KEYS is derived from the schema so isWorkspaceConfigFile's detection set can no longer drift from it. - includeTags/excludeTags scalar coercion moves from readYamlFileAsJson into the schema, so the loader is a plain YAML read and the validator is pure. Warnings go through an injected callback: cloud.ts passes logger.warn (stderr, so it survives --json), the MCP tool passes logStderr since its stdout is the JSON-RPC channel. Also fixes two test fixtures that used a tags: key the CLI never read. Verified on dev: the bare-list form now exits 1 before anything is submitted, and a well-formed executionOrder chains depends_on null -> 36962 -> 36963 across results 36962-36964. Fixes #110 --- src/commands/cloud.ts | 3 + src/mcp/tools/run-cloud-test.ts | 2 + src/services/execution-plan.service.ts | 80 +++---- src/services/execution-plan.utils.ts | 57 +++-- src/services/workspace-config.schema.ts | 255 +++++++++++++++++++++ test/fixtures/basic-config.yaml | 9 +- test/fixtures/tag-filtering-config.yaml | 11 +- test/integration/cloud.integration.test.ts | 84 +++++++ 8 files changed, 412 insertions(+), 89 deletions(-) create mode 100644 src/services/workspace-config.schema.ts diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 632a2b2..04ef937 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -569,6 +569,9 @@ export const cloudCommand = defineCommand({ excludeFlows, configFile, debug, + // Not warnOut: config problems are worth surfacing even under --json, + // and logger.warn writes to stderr so stdout stays parseable. + warn: (m: string) => logger.warn(m), }); if (debug) { diff --git a/src/mcp/tools/run-cloud-test.ts b/src/mcp/tools/run-cloud-test.ts index 2cb7072..5c94dca 100644 --- a/src/mcp/tools/run-cloud-test.ts +++ b/src/mcp/tools/run-cloud-test.ts @@ -151,6 +151,8 @@ export function registerRunCloudTest(server: McpServer): void { excludeTags: args.excludeTags ?? [], excludeFlows: args.excludeFlows, configFile: args.configFile, + // stdout is the JSON-RPC channel — config warnings must go to stderr. + warn: logStderr, }); const commonRoot = computeCommonRoot( diff --git a/src/services/execution-plan.service.ts b/src/services/execution-plan.service.ts index 9fa6c72..9c1284c 100644 --- a/src/services/execution-plan.service.ts +++ b/src/services/execution-plan.service.ts @@ -5,45 +5,12 @@ import { getFlowsToRunInSequence, isFlowFile, isWorkspaceConfigFile, + loadWorkspaceConfig, processDependencies, readDirectory, readTestYamlFileAsJson, - readYamlFileAsJson, } from './execution-plan.utils.js'; - -/** Email notification configuration */ -interface INotificationsConfig { - email?: { - enabled?: boolean; - onSuccess?: boolean; - recipients?: string[]; - }; -} - -/** Workspace configuration from config.yaml */ -interface IWorkspaceConfig { - excludeTags?: null | string[]; - executionOrder?: IExecutionOrder | null; - flows?: null | string[]; - includeTags?: null | string[]; - local?: ILocal | null; - notifications?: INotificationsConfig; - platform?: { - android?: { disableAnimations?: boolean }; - ios?: { disableAnimations?: boolean }; - }; -} - -/** Local execution configuration */ -interface ILocal { - deterministicOrder: boolean | null; -} - -/** Sequential execution configuration */ -interface IExecutionOrder { - continueOnFailure: boolean; - flowsOrder: string[]; -} +import { IWorkspaceConfig } from './workspace-config.schema.js'; /** Options for execution plan generation */ export interface PlanOptions { @@ -53,6 +20,12 @@ export interface PlanOptions { excludeTags?: string[]; includeTags?: string[]; input: string; + /** + * Sink for non-fatal config problems. Injected rather than imported so the + * MCP server can route warnings to stderr — its stdout is the JSON-RPC + * channel. + */ + warn?: (message: string) => void; } /** Execution plan containing all flows to run with metadata and dependencies */ @@ -146,11 +119,13 @@ function filterFlowFiles( * Load workspace configuration from config.yaml/yml if present * @param input - Input directory path * @param unfilteredFlowFiles - List of discovered flow files + * @param warn - Sink for non-fatal config problems * @returns Workspace configuration object (empty if no config file found) */ function getWorkspaceConfig( input: string, unfilteredFlowFiles: string[], + warn: (message: string) => void, ): IWorkspaceConfig { const possibleConfigPaths = new Set( [path.join(input, 'config.yaml'), path.join(input, 'config.yml')].map((p) => @@ -162,11 +137,7 @@ function getWorkspaceConfig( possibleConfigPaths.has(path.normalize(file)), ); - const config = configFilePath - ? (readYamlFileAsJson(configFilePath) as IWorkspaceConfig) - : {}; - - return config; + return configFilePath ? loadWorkspaceConfig(configFilePath, warn) : {}; } /** @@ -199,11 +170,13 @@ function extractDeviceCloudOverrides( /** * Generate execution plan for a single flow file * @param normalizedInput - Normalized path to the flow file + * @param warn - Sink for non-fatal config problems * @param resolvedConfigFile - Optional absolute path to a custom config file * @returns Execution plan for the single file with dependencies */ async function planSingleFile( normalizedInput: string, + warn: (message: string) => void, resolvedConfigFile?: string, ): Promise { const inputBasename = path.basename(normalizedInput); @@ -232,9 +205,17 @@ async function planSingleFile( throw new Error(`Config file does not exist: ${resolvedConfigFile}`); } - workspaceConfig = readYamlFileAsJson( - resolvedConfigFile, - ) as IWorkspaceConfig; + workspaceConfig = loadWorkspaceConfig(resolvedConfigFile, warn); + + // Sequencing is resolved against a workspace's discovered flows, which a + // single-file input doesn't have — so executionOrder is ignored here. Say so + // rather than accepting a config that reads as if it applied (dcd-cli#110). + if (workspaceConfig.executionOrder?.flowsOrder.length) { + warn( + `Warning: \`executionOrder\` in ${resolvedConfigFile} is ignored when a single flow file is passed.\n` + + `Pass the workspace folder instead so the named flows can be discovered and sequenced.`, + ); + } } const checkedDependancies = await checkDependencies(normalizedInput); @@ -386,6 +367,7 @@ export async function plan(options: PlanOptions): Promise { excludeFlows, configFile, debug = false, + warn = (message: string) => console.warn(message), } = options; const normalizedInput = path.normalize(input); const flowMetadata: Record> = {}; @@ -400,7 +382,7 @@ export async function plan(options: PlanOptions): Promise { } if (fs.lstatSync(normalizedInput).isFile()) { - return planSingleFile(normalizedInput, resolvedConfigFile); + return planSingleFile(normalizedInput, warn, resolvedConfigFile); } let unfilteredFlowFiles = await readDirectory(normalizedInput, isFlowFile); @@ -420,11 +402,13 @@ export async function plan(options: PlanOptions): Promise { throw new Error(`Config file does not exist: ${resolvedConfigFile}`); } - workspaceConfig = readYamlFileAsJson( - resolvedConfigFile, - ) as IWorkspaceConfig; + workspaceConfig = loadWorkspaceConfig(resolvedConfigFile, warn); } else { - workspaceConfig = getWorkspaceConfig(normalizedInput, unfilteredFlowFiles); + workspaceConfig = getWorkspaceConfig( + normalizedInput, + unfilteredFlowFiles, + warn, + ); } unfilteredFlowFiles = await applyFlowGlobs( diff --git a/src/services/execution-plan.utils.ts b/src/services/execution-plan.utils.ts index 3b7f24b..dbb23ee 100644 --- a/src/services/execution-plan.utils.ts +++ b/src/services/execution-plan.utils.ts @@ -3,6 +3,12 @@ import * as yaml from 'js-yaml'; import * as fs from 'node:fs'; import * as path from 'node:path'; +import { + IWorkspaceConfig, + parseWorkspaceConfig, + WORKSPACE_CONFIG_KEYS, +} from './workspace-config.schema.js'; + const commandsThatRequireFiles = new Set(['addMedia', 'runFlow', 'runScript']); export function getFlowsToRunInSequence( @@ -60,22 +66,6 @@ export function isFlowFile(filePath: string): boolean { return filePath.endsWith('.yaml') || filePath.endsWith('.yml'); } -/** - * Top-level keys that only ever appear in a workspace config (see - * IWorkspaceConfig in execution-plan.service.ts). Deliberately excludes keys - * Maestro also allows in flow front matter — appId, name, tags, env, - * onFlowStart, onFlowComplete, jsEngine. - */ -const WORKSPACE_CONFIG_KEYS = new Set([ - 'excludeTags', - 'executionOrder', - 'flows', - 'includeTags', - 'local', - 'notifications', - 'platform', -]); - /** * True when a YAML file is a workspace config rather than a runnable flow. * @@ -112,20 +102,7 @@ export const readYamlFileAsJson = (filePath: string) => { const normalizedPath = path.normalize(filePath); const yamlText = fs.readFileSync(normalizedPath, 'utf8'); - const result = yaml.load(yamlText); - - // Ensure includeTags and excludeTags are always arrays if present - if (result && typeof result === 'object') { - if ('includeTags' in result && !Array.isArray(result.includeTags)) { - result.includeTags = result.includeTags ? [result.includeTags] : []; - } - - if ('excludeTags' in result && !Array.isArray(result.excludeTags)) { - result.excludeTags = result.excludeTags ? [result.excludeTags] : []; - } - } - - return result; + return yaml.load(yamlText); } catch (error) { throw new Error(`Error parsing YAML file ${filePath}: ${error}`, { cause: error, @@ -133,6 +110,26 @@ export const readYamlFileAsJson = (filePath: string) => { } }; +/** + * Load and validate a workspace config file. + * + * The single chokepoint for reading a config: every caller gets a + * runtime-validated object instead of an unchecked `as IWorkspaceConfig` cast. + * Scalar-to-array coercion for `includeTags`/`excludeTags` lives in the schema, + * so `readYamlFileAsJson` stays a plain YAML read. + * + * @param filePath - Path to the config file + * @param warn - Sink for non-fatal problems (unrecognised keys) + * @returns The validated workspace config + * @throws Error if the file is unparseable or the config is invalid + */ +export function loadWorkspaceConfig( + filePath: string, + warn: (message: string) => void, +): IWorkspaceConfig { + return parseWorkspaceConfig(readYamlFileAsJson(filePath), { filePath, warn }); +} + export const readTestYamlFileAsJson = (filePath: string) => { try { const normalizedPath = path.normalize(filePath); diff --git a/src/services/workspace-config.schema.ts b/src/services/workspace-config.schema.ts new file mode 100644 index 0000000..960cee9 --- /dev/null +++ b/src/services/workspace-config.schema.ts @@ -0,0 +1,255 @@ +import * as yaml from 'js-yaml'; +import { z } from 'zod'; + +/** + * Runtime schema for a workspace `config.yaml`. + * + * This is the single source of truth for the config's shape — the TypeScript + * type is inferred from it (`IWorkspaceConfig`) rather than declared alongside + * it, so the compile-time and runtime views cannot drift. Before this existed + * the config was `yaml.load`ed and straight-cast, which meant an + * `executionOrder` written in the wrong shape was silently ignored and every + * flow ran in parallel (dcd-cli#110). + */ + +/** + * Tags may be written as a bare scalar (`includeTags: smoke`) or a list. + * Scalars are wrapped, and primitive members are coerced to strings so a + * YAML-numeric tag (`includeTags: [2]`) still compares against flow tags + * instead of silently matching nothing. + */ +const tagList = z.preprocess((value) => { + if (value === null || value === undefined) return value; + const members = Array.isArray(value) ? value : [value]; + return members.map((member) => + typeof member === 'number' || typeof member === 'boolean' + ? String(member) + : member, + ); +}, z.array(z.string())); + +/** Sequential execution configuration */ +const ExecutionOrderSchema = z.object({ + // Defaults to true: a failing flow does not stop the rest of the sequence. + // Declared here so the default lives in one place instead of being + // re-specified at each read site. + continueOnFailure: z.boolean().default(true), + flowsOrder: z.array(z.string()), +}); + +/** + * `looseObject`, not `object`: unknown keys are reported as warnings but must + * survive parsing, because the whole config is forwarded to the API as + * `fields.workspaceConfig` and stripping keys would silently alter that + * payload. + */ +export const WorkspaceConfigSchema = z.looseObject({ + excludeTags: tagList.nullish(), + executionOrder: ExecutionOrderSchema.nullish(), + flows: z.array(z.string()).nullish(), + includeTags: tagList.nullish(), + local: z + .looseObject({ deterministicOrder: z.boolean().nullish() }) + .nullish(), + notifications: z + .looseObject({ + email: z + .looseObject({ + enabled: z.boolean().optional(), + onSuccess: z.boolean().optional(), + recipients: z.array(z.string()).optional(), + }) + .optional(), + }) + .nullish(), + platform: z + .looseObject({ + android: z + .looseObject({ disableAnimations: z.boolean().optional() }) + .optional(), + ios: z + .looseObject({ disableAnimations: z.boolean().optional() }) + .optional(), + }) + .nullish(), +}); + +/** Workspace configuration from config.yaml */ +export type IWorkspaceConfig = z.infer; + +/** + * Top-level keys that only ever appear in a workspace config, derived from the + * schema so the two can't drift. Deliberately excludes keys Maestro also allows + * in flow front matter — appId, name, tags, env, onFlowStart, onFlowComplete, + * jsEngine — because this set also drives config-vs-flow detection + * (`isWorkspaceConfigFile`). + */ +export const WORKSPACE_CONFIG_KEYS: ReadonlySet = new Set( + Object.keys(WorkspaceConfigSchema.shape), +); + +/** + * Near-misses that aren't just a casing slip on a real key. Keyed lowercase. + */ +const KEY_ALIASES: Record = { + continueonfailure: 'executionOrder.continueOnFailure', + excludetag: 'excludeTags', + floworder: 'executionOrder.flowsOrder', + flowsorder: 'executionOrder.flowsOrder', + includetag: 'includeTags', + tags: 'includeTags / excludeTags', +}; + +/** + * Suggest the key the author probably meant. + * @param unknownKey - Unrecognised top-level key from the config + * @returns The suggested key name, or undefined if there's no close match + */ +function suggestKey(unknownKey: string): string | undefined { + const lowered = unknownKey.toLowerCase(); + const casingSlip = [...WORKSPACE_CONFIG_KEYS].find( + (key) => key.toLowerCase() === lowered, + ); + + return casingSlip ?? KEY_ALIASES[lowered]; +} + +/** + * Describe how an `executionOrder` value is malformed, in prose. + * + * Split out so the most likely mistake — a bare list of flow names — gets a + * message showing the expected shape rather than a generic schema dump. + * + * @param value - The raw `executionOrder` value from the config + * @returns A description of the problem, or undefined if the shape is valid + */ +function describeExecutionOrderShape(value: unknown): string | undefined { + if (value === null || value === undefined) return undefined; + if (Array.isArray(value)) return 'a bare list of flow names'; + if (typeof value !== 'object') return `a ${typeof value} value`; + + const { flowsOrder } = value as Record; + if (flowsOrder === undefined) return 'a map with no `flowsOrder` key'; + if (!Array.isArray(flowsOrder)) { + return 'a map whose `flowsOrder` is not a list'; + } + + return undefined; +} + +/** Indent a YAML fragment so it reads as a block inside an error message. */ +function indentYaml(value: unknown): string { + return yaml + .dump(value, { lineWidth: -1 }) + .trimEnd() + .split('\n') + .map((line) => ` ${line}`) + .join('\n'); +} + +/** + * Build the error for a malformed `executionOrder`, showing what was found + * next to what was expected. + * @param filePath - Path to the config file, for the message + * @param problem - Prose description from describeExecutionOrderShape + * @param found - The raw `executionOrder` value that failed + * @returns The error to throw + */ +function executionOrderError( + filePath: string, + problem: string, + found: unknown, +): Error { + return new Error( + `Invalid \`executionOrder\` in ${filePath}\n\n` + + `\`executionOrder\` must be a map containing a \`flowsOrder\` list, but it is ${problem}.\n\n` + + `Found:\n${indentYaml({ executionOrder: found })}\n\n` + + `Expected:\n${indentYaml({ + executionOrder: { + continueOnFailure: true, + flowsOrder: ['first-flow', 'second-flow'], + }, + })}\n\n` + + `Without \`flowsOrder\` the flows are not sequenced — they all run in parallel.`, + ); +} + +/** + * Validate a parsed workspace config. + * + * Hard errors (thrown): the config isn't a map, or `executionOrder` is present + * but malformed. Both are unambiguous mistakes with no valid interpretation, so + * failing fast beats a warning that scrolls past in CI. + * + * Soft problems (warned): unknown top-level keys. These are preserved, not + * stripped, and warned about once. + * + * @param raw - The result of loading the YAML file + * @param options - Config file path (for messages) and a warning sink + * @returns The validated config, with defaults applied + * @throws Error if the config is not a map or `executionOrder` is malformed + */ +export function parseWorkspaceConfig( + raw: unknown, + options: { filePath: string; warn: (message: string) => void }, +): IWorkspaceConfig { + const { filePath, warn } = options; + + if (raw === null || raw === undefined) return {}; + + if (typeof raw !== 'object' || Array.isArray(raw)) { + throw new Error( + `Invalid workspace config in ${filePath}\n\n` + + `Expected a map of configuration keys (${[...WORKSPACE_CONFIG_KEYS].join( + ', ', + )}), but found ${Array.isArray(raw) ? 'a list' : `a ${typeof raw} value`}.`, + ); + } + + const rawConfig = raw as Record; + + // Checked ahead of the schema so this specific mistake gets a targeted + // message; the schema would otherwise report "expected object, received array". + const executionOrderProblem = describeExecutionOrderShape( + rawConfig.executionOrder, + ); + if (executionOrderProblem) { + throw executionOrderError( + filePath, + executionOrderProblem, + rawConfig.executionOrder, + ); + } + + const unknownKeys = Object.keys(rawConfig).filter( + (key) => !WORKSPACE_CONFIG_KEYS.has(key), + ); + if (unknownKeys.length > 0) { + const lines = unknownKeys.map((key) => { + const suggestion = suggestKey(key); + return suggestion + ? ` ${key} — did you mean ${suggestion}?` + : ` ${key}`; + }); + + warn( + `Warning: unrecognised key(s) in ${filePath} — these are ignored:\n` + + `${lines.join('\n')}\n\n` + + `Supported keys: ${[...WORKSPACE_CONFIG_KEYS].join(', ')}`, + ); + } + + const result = WorkspaceConfigSchema.safeParse(rawConfig); + if (!result.success) { + const issues = result.error.issues + .map((issue) => { + const location = issue.path.length > 0 ? issue.path.join('.') : '(root)'; + return ` ${location}: ${issue.message}`; + }) + .join('\n'); + + throw new Error(`Invalid workspace config in ${filePath}\n\n${issues}`); + } + + return result.data; +} diff --git a/test/fixtures/basic-config.yaml b/test/fixtures/basic-config.yaml index 0671d08..3c49ac4 100644 --- a/test/fixtures/basic-config.yaml +++ b/test/fixtures/basic-config.yaml @@ -1,8 +1,7 @@ flows: - ./**/*.yaml - ./*.yaml -tags: - include: - - smoke - exclude: - - slow \ No newline at end of file +includeTags: + - smoke +excludeTags: + - slow diff --git a/test/fixtures/tag-filtering-config.yaml b/test/fixtures/tag-filtering-config.yaml index c08d93c..bb28996 100644 --- a/test/fixtures/tag-filtering-config.yaml +++ b/test/fixtures/tag-filtering-config.yaml @@ -1,8 +1,7 @@ flows: - ./**/*.yaml -tags: - include: - - smoke - exclude: - - slow - - integration \ No newline at end of file +includeTags: + - smoke +excludeTags: + - slow + - integration diff --git a/test/integration/cloud.integration.test.ts b/test/integration/cloud.integration.test.ts index 8ac4c93..39d77d5 100644 --- a/test/integration/cloud.integration.test.ts +++ b/test/integration/cloud.integration.test.ts @@ -408,6 +408,90 @@ includeTags: }); }); + // dcd-cli#110: a `config.yaml` whose executionOrder was the wrong *shape* was + // silently ignored and every flow ran in parallel — same cost, wrong + // semantics, green run. These assert the shape is now validated. + describe('executionOrder validation', () => { + let workspaceDir: string; + + /** Point the run at a workspace whose config.yaml holds `configBody`. */ + const commandWithConfig = (configBody: string): string => { + fs.writeFileSync(path.join(workspaceDir, 'config.yaml'), configBody); + return `${CLI} cloud ${androidAppFile} "${workspaceDir}" --api-key ${mockApiKey} --api-url ${mockApiUrl} --dry-run`; + }; + + before(() => { + workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dcd-test-order-')); + + for (const name of ['a', 'b', 'c']) { + fs.writeFileSync( + path.join(workspaceDir, `${name}.yaml`), + `appId: com.example.app +--- +- launchApp +`, + ); + } + }); + + after(() => { + if (fs.existsSync(workspaceDir)) { + fs.rmSync(workspaceDir, { force: true, recursive: true }); + } + }); + + it('should reject a bare-list executionOrder instead of running in parallel', async () => { + const command = commandWithConfig(`executionOrder: + - a.yaml + - b.yaml + - c.yaml +`); + + const { output } = await runExpectingFailure(command); + expect(output).to.include('Invalid `executionOrder`'); + expect(output).to.include('flowsOrder'); + // The whole point: it must not quietly proceed to a parallel run. + expect(output).to.not.include('The following tests would have been run'); + }); + + it('should reject an executionOrder map with no flowsOrder', async () => { + const command = commandWithConfig(`executionOrder: + continueOnFailure: true +`); + + const { output } = await runExpectingFailure(command); + expect(output).to.include('Invalid `executionOrder`'); + expect(output).to.include('no `flowsOrder` key'); + }); + + it('should sequence flows for a well-formed executionOrder', async () => { + const command = commandWithConfig(`executionOrder: + continueOnFailure: true + flowsOrder: + - a.yaml + - b.yaml +`); + + const { stdout } = await exec(command, { timeout: 15_000 }); + expect(stdout).to.include('Sequential flows'); + expect(stdout).to.include('a.yaml'); + expect(stdout).to.include('b.yaml'); + }); + + it('should warn about unrecognised config keys without failing the run', async () => { + const command = commandWithConfig(`flowOrder: + - a.yaml +flowTimeout: 120000 +`); + + const { stdout, stderr } = await exec(command, { timeout: 15_000 }); + expect(stdout).to.include('The following tests would have been run'); + expect(stderr).to.include('flowOrder'); + expect(stderr).to.include('executionOrder.flowsOrder'); + expect(stderr).to.include('flowTimeout'); + }); + }); + describe('file and binary management', () => { it('should support app binary ID instead of file', async () => { const command = `${CLI} cloud --app-binary-id test-binary-123 ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --dry-run`; From 43e7324fe8505a242207cc497cc928d461bc711f Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:55:27 +0100 Subject: [PATCH 59/78] chore(dev): release 5.3.1-beta.2 (#118) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index c540ca5..0c1b0cf 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.3.1-beta.1" + ".": "5.3.1-beta.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b39946b..d7087ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [5.3.1-beta.2](https://github.com/devicecloud-dev/dcd-cli/compare/v5.3.1-beta.1...v5.3.1-beta.2) (2026-08-13) + + +### Bug Fixes + +* **cloud:** reject malformed executionOrder instead of silently runni… ([#117](https://github.com/devicecloud-dev/dcd-cli/issues/117)) ([1973a42](https://github.com/devicecloud-dev/dcd-cli/commit/1973a42a1c4c01d0db29a106aa9c6a8f82da6853)) +* **cloud:** reject malformed executionOrder instead of silently running in parallel ([1973a42](https://github.com/devicecloud-dev/dcd-cli/commit/1973a42a1c4c01d0db29a106aa9c6a8f82da6853)), closes [#110](https://github.com/devicecloud-dev/dcd-cli/issues/110) + ## [5.3.1-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.2.0-beta.4...v5.3.1-beta.1) (2026-08-10) diff --git a/package.json b/package.json index c023004..d42ca44 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "test": "node scripts/test-runner.mjs", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.3.1-beta.1", + "version": "5.3.1-beta.2", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From 1c07fc14a48e7cc29c6deb56f0ad4aa533794f25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:21:26 +0100 Subject: [PATCH 60/78] deps: bump the minor-and-patch group with 6 updates (#121) Bumps the minor-and-patch group with 6 updates: | Package | From | To | | --- | --- | --- | | [@supabase/supabase-js](https://github.com/supabase/supabase-js/tree/HEAD/packages/core/supabase-js) | `2.112.2` | `2.112.3` | | [js-yaml](https://github.com/nodeca/js-yaml) | `5.2.3` | `5.3.0` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.2` | `26.2.0` | | [eslint](https://github.com/eslint/eslint) | `10.8.0` | `10.8.1` | | [tsx](https://github.com/privatenumber/tsx) | `4.23.11` | `4.23.12` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.66.0` | `8.67.0` | Updates `@supabase/supabase-js` from 2.112.2 to 2.112.3 - [Changelog](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/CHANGELOG.md) - [Commits](https://github.com/supabase/supabase-js/commits/v2.112.3/packages/core/supabase-js) Updates `js-yaml` from 5.2.3 to 5.3.0 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/5.2.3...5.3.0) Updates `@types/node` from 26.1.2 to 26.2.0 - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `eslint` from 10.8.0 to 10.8.1 - [Commits](https://github.com/eslint/eslint/compare/v10.8.0...v10.8.1) Updates `tsx` from 4.23.11 to 4.23.12 - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.23.11...v4.23.12) Updates `typescript-eslint` from 8.66.0 to 8.67.0 - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@supabase/supabase-js" dependency-version: 2.112.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: js-yaml dependency-version: 5.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: "@types/node" dependency-version: 26.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: eslint dependency-version: 10.8.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: tsx dependency-version: 4.23.12 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: typescript-eslint dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 257 +++++++++++++++++++++++++------------------------ 1 file changed, 129 insertions(+), 128 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d6f08b..0efa3bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 1.30.0(zod@4.4.3) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.112.2 + version: 2.112.3 bplist-parser: specifier: ^0.3.2 version: 0.3.2 @@ -58,7 +58,7 @@ importers: version: 0.2.2 js-yaml: specifier: ^5.2.2 - version: 5.2.3 + version: 5.3.0 node-apk: specifier: ^1.2.1 version: 1.2.1 @@ -83,7 +83,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.8.0) + version: 10.0.1(eslint@10.8.1) '@types/chai': specifier: ^5.2.3 version: 5.2.3 @@ -95,7 +95,7 @@ importers: version: 10.0.10 '@types/node': specifier: ^26.0.0 - version: 26.1.2 + version: 26.2.0 '@types/yazl': specifier: ^3.3.1 version: 3.3.1 @@ -104,13 +104,13 @@ importers: version: 6.2.2 eslint: specifier: ^10.5.0 - version: 10.8.0 + version: 10.8.1 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.8.0) + version: 10.1.8(eslint@10.8.1) eslint-plugin-unicorn: specifier: ^73.0.0 - version: 73.0.0(eslint@10.8.0) + version: 73.0.0(eslint@10.8.1) husky: specifier: ^9.1.7 version: 9.1.7 @@ -125,13 +125,13 @@ importers: version: 0.4.0 tsx: specifier: ^4.22.4 - version: 4.23.11 + version: 4.23.12 typescript: specifier: ^6.0.3 version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.66.0(eslint@10.8.0)(typescript@6.0.3) + version: 8.67.0(eslint@10.8.1)(typescript@6.0.3) packages: @@ -402,31 +402,31 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@supabase/auth-js@2.112.2': - resolution: {integrity: sha512-l1InCp4j98d09LZ6+RgubgF4eVPGBGXcLEhFusLg1qUCHJ2IEkYu5FohKK+eaFmIOwEk0kqG/j/lycw5e15mcQ==} + '@supabase/auth-js@2.112.3': + resolution: {integrity: sha512-NA0rsgAlWZPvbhw8aUdmgfpHVgUAcd8zK5ov43l++o1bLIPXZhRiAlRobhwF5AatQuovpqxsMH50F4oyyV4XZw==} engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.112.2': - resolution: {integrity: sha512-oMuSWN0ERmrG9S6kOM0bwhHmESGVl3kMtkZl2dNCU/r89hMiziX4GfD1omNo9QcBDele4N0GwSZ7hdbpuiA35A==} + '@supabase/functions-js@2.112.3': + resolution: {integrity: sha512-gfv481mTOVWtZIJgXupxZpni2V2UWPf6jeF/jOK7HdMHdH+mt6sU0sHHwf0POsPip8ltlulu9OUHgwVzl5ddRw==} engines: {node: '>=22.0.0'} '@supabase/phoenix@0.4.5': resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} - '@supabase/postgrest-js@2.112.2': - resolution: {integrity: sha512-ewhhtRny/HFRGhUTTg/PsqIatsl8OhW8Eha/Tz4S+SRAXBnuhKei9ZpsQTgL/3XcH9UEwuPQyQgQ9itq7nRQeg==} + '@supabase/postgrest-js@2.112.3': + resolution: {integrity: sha512-+Mf6uCpzr00bqxwX8hTK2X2L9eAL/1vuOjdEjx6upz9ulb0RmQT16XeU/JkMUlVHw/B46ZnPa2busY4Kd9YCzw==} engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.112.2': - resolution: {integrity: sha512-cd9/CEUJ6Go13FxtfiuC5rYELJtuQzVzTXlGG+XjSppjDS+anq+xo++WQe7ZRUNTuHOCeyKRwmx9Hw/OQJ04ig==} + '@supabase/realtime-js@2.112.3': + resolution: {integrity: sha512-E6wljXWs7DUOloyIB69i3YFInWE6IyCvgTAbQ0KYxOHv26FdA1KzEXTuzxrYEdf70t406Z9BRwUlGyclGF2FXA==} engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.112.2': - resolution: {integrity: sha512-6jyBq/J1iXOHNpbjCZS7gFcDk49iM1MCJUVkDl71gLd/+XnLDzpUBs8icGebtwiHpl4kVszxIRDYAosbF4Rsig==} + '@supabase/storage-js@2.112.3': + resolution: {integrity: sha512-oSK61tzlUvg+BWPqpKQCu9qqonsO26btaoAR9D6Gest2aj7xUqToj9rKyaoYOJczkhg9BjqA1REbYy9tPI4bDA==} engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.112.2': - resolution: {integrity: sha512-UyI1epU9B4X51HvNpkmlwTdF20fEcz2vyvrcDKVzFN4jZN41f5iQRqsiIQjAY5OVJD6ljqA/1g9JQeOTvFHpkA==} + '@supabase/supabase-js@2.112.3': + resolution: {integrity: sha512-Jv1bxVQmEJNkjvPEhFaKjPzsh+Ozyew6lWGD+SoYcsclDEP1z7yEvKvfUQfzy0DkxRIQnZNxmmWtAzw5XLTQoA==} engines: {node: '>=22.0.0'} peerDependencies: '@opentelemetry/api': '>=1.0.0' @@ -455,74 +455,75 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@26.1.2': - resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} + '@types/node@26.2.0': + resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} - '@typescript-eslint/eslint-plugin@8.66.0': - resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==} + '@typescript-eslint/eslint-plugin@8.67.0': + resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.66.0 + '@typescript-eslint/parser': ^8.67.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.66.0': - resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==} + '@typescript-eslint/parser@8.67.0': + resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.66.0': - resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==} + '@typescript-eslint/project-service@8.67.0': + resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.66.0': - resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==} + '@typescript-eslint/scope-manager@8.67.0': + resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.66.0': - resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==} + '@typescript-eslint/tsconfig-utils@8.67.0': + resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.66.0': - resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==} + '@typescript-eslint/type-utils@8.67.0': + resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.66.0': - resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==} + '@typescript-eslint/types@8.67.0': + resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.66.0': - resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==} + '@typescript-eslint/typescript-estree@8.67.0': + resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.66.0': - resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==} + '@typescript-eslint/utils@8.67.0': + resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.66.0': - resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==} + '@typescript-eslint/visitor-keys@8.67.0': + resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.10': resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} engines: {node: '>=14.6'} + deprecated: this version has critical issues, please update to the latest version accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} @@ -839,8 +840,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.8.0: - resolution: {integrity: sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==} + eslint@10.8.1: + resolution: {integrity: sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -960,8 +961,8 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.4.3: - resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} @@ -1162,8 +1163,8 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-yaml@5.2.3: - resolution: {integrity: sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==} + js-yaml@5.3.0: + resolution: {integrity: sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==} hasBin: true jsesc@3.1.0: @@ -1666,8 +1667,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.23.11: - resolution: {integrity: sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==} + tsx@4.23.12: + resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} engines: {node: '>=18.0.0'} hasBin: true @@ -1687,8 +1688,8 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript-eslint@8.66.0: - resolution: {integrity: sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==} + typescript-eslint@8.67.0: + resolution: {integrity: sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1888,9 +1889,9 @@ snapshots: '@esbuild/win32-x64@0.28.2': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0)': + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.1)': dependencies: - eslint: 10.8.0 + eslint: 10.8.1 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -1916,9 +1917,9 @@ snapshots: mdn-data: 2.29.0 source-map-js: 1.2.1 - '@eslint/js@10.0.1(eslint@10.8.0)': + '@eslint/js@10.0.1(eslint@10.8.1)': optionalDependencies: - eslint: 10.8.0 + eslint: 10.8.1 '@eslint/object-schema@3.0.5': {} @@ -1997,37 +1998,37 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@supabase/auth-js@2.112.2': + '@supabase/auth-js@2.112.3': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.112.2': + '@supabase/functions-js@2.112.3': dependencies: tslib: 2.8.1 '@supabase/phoenix@0.4.5': {} - '@supabase/postgrest-js@2.112.2': + '@supabase/postgrest-js@2.112.3': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.112.2': + '@supabase/realtime-js@2.112.3': dependencies: '@supabase/phoenix': 0.4.5 tslib: 2.8.1 - '@supabase/storage-js@2.112.2': + '@supabase/storage-js@2.112.3': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.112.2': + '@supabase/supabase-js@2.112.3': dependencies: - '@supabase/auth-js': 2.112.2 - '@supabase/functions-js': 2.112.2 - '@supabase/postgrest-js': 2.112.2 - '@supabase/realtime-js': 2.112.2 - '@supabase/storage-js': 2.112.2 + '@supabase/auth-js': 2.112.3 + '@supabase/functions-js': 2.112.3 + '@supabase/postgrest-js': 2.112.3 + '@supabase/realtime-js': 2.112.3 + '@supabase/storage-js': 2.112.3 '@types/chai@5.2.3': dependencies: @@ -2046,23 +2047,23 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@26.1.2': + '@types/node@26.2.0': dependencies: undici-types: 8.3.0 '@types/yazl@3.3.1': dependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 - '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.66.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/type-utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.66.0 - eslint: 10.8.0 + '@typescript-eslint/parser': 8.67.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/type-utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.67.0 + eslint: 10.8.1 ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2070,56 +2071,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.66.0(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.67.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.66.0 + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.8.0 + eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.66.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.67.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) - '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.66.0': + '@typescript-eslint/scope-manager@8.67.0': dependencies: - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/visitor-keys': 8.66.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 - '@typescript-eslint/tsconfig-utils@8.66.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.66.0(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.8.0 + eslint: 10.8.1 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.66.0': {} + '@typescript-eslint/types@8.67.0': {} - '@typescript-eslint/typescript-estree@8.66.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.67.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.66.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/visitor-keys': 8.66.0 + '@typescript-eslint/project-service': 8.67.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.3 semver: 7.8.5 @@ -2129,20 +2130,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.66.0(eslint@10.8.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.67.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) - '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) - eslint: 10.8.0 + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.66.0': + '@typescript-eslint/visitor-keys@8.67.0': dependencies: - '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/types': 8.67.0 eslint-visitor-keys: 5.0.1 '@xmldom/xmldom@0.9.10': {} @@ -2412,13 +2413,13 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.8.0): + eslint-config-prettier@10.1.8(eslint@10.8.1): dependencies: - eslint: 10.8.0 + eslint: 10.8.1 - eslint-plugin-unicorn@73.0.0(eslint@10.8.0): + eslint-plugin-unicorn@73.0.0(eslint@10.8.1): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1) '@eslint/css-tree': 4.0.5 browserslist: 4.28.8 change-case: 5.4.4 @@ -2426,7 +2427,7 @@ snapshots: core-js-compat: 3.50.0 detect-indent: 7.0.2 entities: 4.5.0 - eslint: 10.8.0 + eslint: 10.8.1 find-up-simple: 1.0.1 globals: 17.9.0 indent-string: 5.0.0 @@ -2451,9 +2452,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.8.0: + eslint@10.8.1: dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.7.0 @@ -2625,12 +2626,12 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.3 + flatted: 3.4.4 keyv: 4.5.4 flat@5.0.2: {} - flatted@3.4.3: {} + flatted@3.4.4: {} foreground-child@3.3.1: dependencies: @@ -2790,7 +2791,7 @@ snapshots: js-base64@3.7.8: {} - js-yaml@5.2.3: + js-yaml@5.3.0: dependencies: argparse: 2.0.1 @@ -2910,7 +2911,7 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 5.2.3 + js-yaml: 5.3.0 log-symbols: 4.1.0 minimatch: 9.0.7 ms: 2.1.3 @@ -3269,7 +3270,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.23.11: + tsx@4.23.12: dependencies: esbuild: 0.28.2 optionalDependencies: @@ -3297,13 +3298,13 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typescript-eslint@8.66.0(eslint@10.8.0)(typescript@6.0.3): + typescript-eslint@8.67.0(eslint@10.8.1)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0)(typescript@6.0.3))(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.66.0(eslint@10.8.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0)(typescript@6.0.3) - eslint: 10.8.0 + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) + eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color From 2b89f3ad11076d5df654e670dc4db7a7fb850344 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:50:25 +0100 Subject: [PATCH 61/78] ci: stop reaching into the private dcd repo for the mock-api (#124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: stop reaching into the private dcd repo for the mock-api `lint-and-test` has failed on every same-repo PR since dcd#1036 deleted `mock-api/` from the private devicecloud-dev/dcd repo this morning. CI checked that directory out over an SSH deploy key and ran `pnpm install` in it; the sparse-checkout now matches nothing, so the job dies at that step — before the linter — and takes #120, #122 and #123 down with it. Rather than re-point at a mock, this removes the linkage. dcd-cli is PUBLIC and was holding `DCD_SSH_DEPLOY_KEY`, a credential granting read access to the private repo, and pulling the API's `swagger.json` onto the runner on every same-repo PR. Deleting the checkout drops both. * The `Checkout dcd (mock-api)` and `Install Mock API dependencies` steps are gone, along with the `HAS_PRIVATE_ACCESS` gate that existed only to keep them off fork and Dependabot PRs. Every PR now takes the same path, so forks stop being second-class. * CI runs `pnpm test:unit` — a new script that is the existing runner with `--unit`. `test/unit/*` is pure and needs no backend, so unit coverage is kept rather than dropped along with the integration suite. * `scripts/test-runner.mjs` no longer defaults `MOCK_API_DIR` to `../../dcd/mock-api`. With no mock available it degrades to the unit suite and says so, instead of the bare ENOENT it throws today. Set `MOCK_API_DIR` and the integration specs run exactly as before. `DCD_SSH_DEPLOY_KEY` can now be deleted from the repo's secrets — nothing reads it. That is a separate manual step, not something this commit can do. Two things are genuinely lost, both worth stating plainly rather than discovering later: * `test/integration/*` no longer runs anywhere automatically. * With it goes the CLI<->swagger contract-drift check. Drift used to surface as a Prism 422 — that is how the `googlePlay` multipart break and the `tempPath` missing-example break were both caught. Nothing replaces it yet. Verified locally: `pnpm test:unit` and a bare `pnpm test` both run the unit suite only and print the notice; 81 pass and the 7 `flow-paths` failures are Windows-only, asserting POSIX paths against win32 `path`. The same specs ran green on ubuntu in the last full CI run (job 94750122384, 2026-08-14), which is the platform CI uses. `pnpm lint`, `pnpm typecheck`, `pnpm build` and `pnpm audit --audit-level moderate` are all clean. * docs: align the contributor docs with the new CI behaviour Follow-up to 5a713f6, which changed how CI treats the mock-api but only updated CLAUDE.md's Commands section — leaving three descriptions of the machinery it removed. Flagged on #124 for CLAUDE.md; CONTRIBUTING.md and README.md carried the same claim and are the ones contributors actually read. * CLAUDE.md's Contributing bullet said integration tests need the private devicecloud-dev/dcd mock-api via DCD_SSH_DEPLOY_KEY, that `pnpm test` is skipped on fork/Dependabot PRs, and that a maintainer runs the full suite before merge. None of that is true now: every PR runs identical steps and nothing runs the integration suite. * CONTRIBUTING.md's "About the test suite" said the same, framed as forks being the special case. Rewritten around the actual split — test/unit/* everywhere, test/integration/* only with MOCK_API_DIR set — and the reason CI does not reach for a mock: this repo is public and holds no credentials for private infrastructure. * Both command tables and README's quickstart now list `pnpm test:unit` and note it is what CI runs. Each of the three states the consequence rather than burying it: a green PR says nothing about the integration suite. CONTRIBUTING.md asks contributors touching the API surface to flag it so a maintainer can exercise it before merge. Docs only — no workflow or script changes. --- .github/workflows/cli-ci.yml | 51 ++++++-------------- CLAUDE.md | 10 ++-- CONTRIBUTING.md | 28 +++++++---- README.md | 3 +- package.json | 1 + scripts/test-runner.mjs | 91 +++++++++++++++++++++--------------- 6 files changed, 97 insertions(+), 87 deletions(-) diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 0af984a..1012cae 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -45,37 +45,23 @@ jobs: lint-and-test: runs-on: ubuntu-latest - # The mock-api lives in the private devicecloud-dev/dcd repo, checked out via an - # SSH deploy key. GitHub does NOT expose secrets to pull_request workflows - # triggered from forks, so that checkout (and the integration tests that need - # it) can only run for same-repo events. Fork PRs still run lint/typecheck/build. + # This repo is PUBLIC and runs no step that reaches into the private + # devicecloud-dev/dcd repo. It used to check out that repo's mock-api over an + # SSH deploy key to run test/integration/*, which meant a private-repo + # credential lived in a public repo's secrets and the API's OpenAPI spec was + # pulled onto the runner on every same-repo PR. dcd#1036 deleted that mock-api; + # rather than re-point at it, the linkage is gone. # - # Dependabot PRs branch from this repo (so the fork check passes) but ALSO run - # without secrets — treat them like forks and skip the private checkout, or - # the mock-api clone fails with an empty DCD_SSH_DEPLOY_KEY. - env: - HAS_PRIVATE_ACCESS: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' }} - + # The consequence is deliberate: test/integration/* does NOT run here, and + # neither does the swagger contract-drift check it provided (spec drift used to + # surface as a Prism 422). Only test/unit/* runs — pure, no backend. To run the + # integration suite locally, point MOCK_API_DIR at a mock; see CLAUDE.md. steps: - name: Checkout CLI uses: actions/checkout@v7 with: path: cli - - name: Checkout dcd (mock-api) - if: env.HAS_PRIVATE_ACCESS == 'true' - uses: actions/checkout@v7 - with: - repository: devicecloud-dev/dcd - path: dcd - ssh-key: ${{ secrets.DCD_SSH_DEPLOY_KEY }} - # api/swagger.json is a file, which cone-mode sparse checkout rejects - # as of git 2.51 ("is not a directory") — use non-cone patterns. - sparse-checkout-cone-mode: false - sparse-checkout: | - /mock-api/ - /api/swagger.json - - name: Setup pnpm uses: pnpm/action-setup@v6.0.10 with: @@ -93,11 +79,6 @@ jobs: working-directory: ./cli run: pnpm install --frozen-lockfile - - name: Install Mock API dependencies - if: env.HAS_PRIVATE_ACCESS == 'true' - working-directory: ./dcd/mock-api - run: pnpm install --frozen-lockfile - - name: Run CLI linter working-directory: ./cli run: pnpm lint @@ -106,16 +87,12 @@ jobs: working-directory: ./cli run: pnpm typecheck - - name: Run CLI tests - if: env.HAS_PRIVATE_ACCESS == 'true' + - name: Run CLI unit tests working-directory: ./cli - env: - MOCK_API_DIR: ${{ github.workspace }}/dcd/mock-api - run: pnpm test + run: pnpm test:unit - - name: Skip integration tests (fork PR — no mock-api access) - if: env.HAS_PRIVATE_ACCESS != 'true' - run: echo "::notice::Integration tests skipped — the mock-api (private devicecloud-dev/dcd) is not accessible from fork PRs. Lint, typecheck, and build still ran." + - name: Note skipped integration tests + run: echo "::notice::Integration tests are not run in CI — they need a mock of the dcd API, and this public repo does not reach into the private one. Lint, typecheck, unit tests, build and audit all ran." - name: Build CLI working-directory: ./cli diff --git a/CLAUDE.md b/CLAUDE.md index 2cd76a9..a9bf70d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,8 +9,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - `pnpm build:binaries` — `scripts/build-binaries.mjs` produces the bun-compiled, self-contained `dcd--` binaries published to GitHub Releases (the install `dcd upgrade` self-updates). The platform/arch keys must stay in sync with `ASSET_BY_PLATFORM` in `src/commands/upgrade.ts`. - `pnpm lint` — ESLint over `src/` and `test/`. - `pnpm typecheck` — `tsc --noEmit -p tsconfig.test.json` over `src/` and `test/` (strict mode; `pnpm build` only compiles `src/`). Requires Node `>=22`. -- `pnpm test` — runs `scripts/test-runner.mjs`: builds the CLI, boots the mock API, then runs all `test/**/*.test.ts` via mocha. TypeScript is loaded by **tsx** (`.mocharc.json`'s `node-option: ["import=tsx"]`), *not* ts-node — Mocha 11 imports specs as ESM, which bypasses the `require: ts-node/register` hook. The mock API lives in the **sibling `dcd/` repo** (`../dcd/mock-api`, started via `npm run start:auth` on port 3001). Override its location with `MOCK_API_DIR=/path/to/mock-api`. The runner isolates `DCD_CONFIG_DIR` to a temp dir so tests never touch your real `dcd login` session. -- Tests split into `test/unit/*` (pure, no backend) and `test/integration/*` (drive the built CLI against the mock API). Run a single test: `pnpm mocha test/integration/cloud.integration.test.ts --timeout 60000` (picks up `.mocharc.json` which wires tsx; integration specs require the mock API already running on port 3001). +- `pnpm test` — runs `scripts/test-runner.mjs`: builds the CLI, boots the mock API if one is available, then runs mocha. TypeScript is loaded by **tsx** (`.mocharc.json`'s `node-option: ["import=tsx"]`), *not* ts-node — Mocha 11 imports specs as ESM, which bypasses the `require: ts-node/register` hook. The runner isolates `DCD_CONFIG_DIR` to a temp dir so tests never touch your real `dcd login` session. +- `pnpm test:unit` — the same runner with `--unit`: unit specs only, no mock API. **This is what CI runs.** +- Tests split into `test/unit/*` (pure, no backend) and `test/integration/*` (drive the built CLI against a Prism mock of the dcd API on port 3001). +- **There is no default mock API any more.** It used to live in the sibling private `dcd/` repo; dcd#1036 deleted it, and this repo — which is public — deliberately no longer reaches into that one (no deploy key, no `swagger.json` pull). So `pnpm test` with no `MOCK_API_DIR` set **silently degrades to the unit suite** and prints a notice. To run `test/integration/*`, stand up a Prism mock over the API's `swagger.json` and point `MOCK_API_DIR=/path/to/mock-api` at it (it needs a `start:auth` npm script serving port 3001). +- Consequence worth knowing: CI no longer catches **CLI↔swagger contract drift**, which used to surface as a Prism 422 from the integration specs. Nothing replaces that check yet. +- Run a single test: `pnpm mocha test/integration/cloud.integration.test.ts --timeout 60000` (picks up `.mocharc.json` which wires tsx; integration specs require the mock API already running on port 3001). ## Entry point @@ -57,7 +61,7 @@ Full guide in `CONTRIBUTING.md`; the operationally important parts (the ones tha - ⚠️ **A `!` (or `BREAKING CHANGE:` footer) bumps the MAJOR — do not use it casually.** The configs set `bump-minor-pre-major: true`, but that only applies **below 1.0.0**; we are on 5.x, so it is inert and a breaking marker means exactly what semver says. A `refactor(cloud)!:` PR title once produced a `6.0.0-beta.1` release PR for what was only a flag rename in an unconsumed beta. Because PRs are squash-merged, **the PR title IS the commit** — the `!` lands even if no branch commit carried it. - **Never hand-edit `package.json` version, `CHANGELOG.md`, or the `.release-please-manifest*.json` files** — release-please owns all of them. `src/types/generated/schema.types.ts` is likewise generated (openapi-typescript). - A first-time contributor must sign the CLA (the CLA Assistant bot comments on the first PR); the CLA check must be green to merge. -- **CI (`.github/workflows/cli-ci.yml`) runs on every PR** including forks: gitleaks secret scan, `pnpm lint`, `pnpm typecheck`, `pnpm build`, `pnpm audit --audit-level moderate`. The **integration tests need the private `devicecloud-dev/dcd` mock-api** (cloned via the `DCD_SSH_DEPLOY_KEY` secret), and GitHub withholds secrets from fork and Dependabot PRs — so `pnpm test` is **skipped there** and a maintainer runs the full suite before merge. gitleaks also runs as a pre-commit hook (allowlist in `.gitleaks.toml`); without the binary installed the hook self-skips and CI is the backstop. +- **CI (`.github/workflows/cli-ci.yml`) runs the same steps on every PR** — fork, Dependabot and same-repo alike, with no privileged path: gitleaks secret scan, `pnpm lint`, `pnpm typecheck`, `pnpm test:unit`, `pnpm build`, `pnpm audit --audit-level moderate`. **`test/integration/*` is not run by CI at all** (see the Commands section: this public repo no longer reaches into the private `devicecloud-dev/dcd` repo for a mock API), so a green PR says nothing about the integration suite — run it locally with `MOCK_API_DIR` set if a change touches the API surface. gitleaks also runs as a pre-commit hook (allowlist in `.gitleaks.toml`); without the binary installed the hook self-skips and CI is the backstop. ## Releases diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1761173..1560eac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,19 +37,29 @@ Useful scripts: | `pnpm lint` | ESLint over `src/` and `test/` | | `pnpm typecheck` | Strict `tsc --noEmit` over `src/` and `test/` | | `pnpm build` | Compile to `dist/` | -| `pnpm test` | Build + boot the mock API + run integration/unit tests | +| `pnpm test:unit` | Run the unit suite — no backend needed. **This is what CI runs.** | +| `pnpm test` | The same, plus the integration suite if `MOCK_API_DIR` points at a mock API | -**Before pushing, make sure `pnpm lint`, `pnpm typecheck`, and `pnpm build` -pass.** These run for every PR (including from forks) and are required to merge. +**Before pushing, make sure `pnpm lint`, `pnpm typecheck`, `pnpm test:unit`, and +`pnpm build` pass.** These run for every PR and are required to merge. ### About the test suite -`pnpm test` boots a **mock API that lives in a private repository**, so the full -integration suite only runs on branches inside this repo. **On pull requests from -forks the integration tests are automatically skipped** — you'll see a CI notice -saying so. That's expected: lint, typecheck, and build still run and gate your -PR, and a maintainer runs the full suite before merge. You don't need backend -access to contribute. +Tests split in two. `test/unit/*` is pure — no network, no backend — and runs +everywhere, in CI and locally. + +`test/integration/*` drives the built CLI against a Prism mock of the dcd API on +port 3001. **CI does not run it**, on any PR, from a fork or otherwise: this repo +is public and deliberately holds no credentials for, and makes no requests to, +our private infrastructure. There is no default mock API — set +`MOCK_API_DIR=/path/to/mock-api` (a package exposing a `start:auth` script on +port 3001) and `pnpm test` picks the integration suite up. Without it the runner +prints a notice and runs the unit suite alone. + +So every contributor, maintainers included, gets the same CI signal, and you +don't need backend access to contribute. The flip side is worth knowing: a green +PR says nothing about the integration suite, so if your change touches the API +surface, say so in the PR and a maintainer will exercise it before merge. ### Secret scanning diff --git a/README.md b/README.md index f0da192..fdb4804 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,8 @@ $ pnpm install # install deps, build, set up git hooks $ pnpm dcd # run the CLI from source $ pnpm lint # ESLint $ pnpm typecheck # strict tsc, no emit -$ pnpm test # build + boot mock API + integration/unit tests +$ pnpm test:unit # unit tests, no backend needed — what CI runs +$ pnpm test # the above, plus integration tests if MOCK_API_DIR is set ``` ### Secret scanning diff --git a/package.json b/package.json index d42ca44..25f936a 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "lint": "eslint src test --ext .ts", "prepare": "pnpm build && husky", "test": "node scripts/test-runner.mjs", + "test:unit": "node scripts/test-runner.mjs --unit", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, "version": "5.3.1-beta.2", diff --git a/scripts/test-runner.mjs b/scripts/test-runner.mjs index 33adcba..c6a4441 100755 --- a/scripts/test-runner.mjs +++ b/scripts/test-runner.mjs @@ -8,13 +8,20 @@ import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -// mock-api lives in the sibling dcd/ repo while the oclif→citty migration settles. -// Override with MOCK_API_DIR=/path/to/mock-api if it moves. -const mockApiDir = - process.env.MOCK_API_DIR ?? - path.resolve(__dirname, '../../dcd/mock-api'); +// The integration suite drives the built CLI against a Prism mock of the dcd API. +// That mock used to live in the sibling private dcd/ repo, which no longer ships +// one (dcd#1036), so there is no default location any more: point MOCK_API_DIR at +// a mock to run those specs. Without one — which includes CI, where this repo is +// public and deliberately does not reach into the private repo — the runner falls +// back to the unit suite, which is pure and needs no backend. +const mockApiDir = process.env.MOCK_API_DIR ?? null; const cliDir = path.resolve(__dirname, '..'); +const unitOnly = + process.argv.includes('--unit') || + mockApiDir === null || + !fs.existsSync(mockApiDir); + const MOCK_API_URL = 'http://localhost:3001/'; const READY_DEADLINE_MS = 30_000; const READY_POLL_INTERVAL_MS = 500; @@ -112,52 +119,62 @@ async function runTests() { }); }); - // Start mock API with authentication - console.log('Starting mock API with authentication...'); - mockApiProcess = spawn('npm', ['run', 'start:auth'], { - cwd: mockApiDir, - stdio: ['ignore', 'pipe', 'pipe'], - shell: true, - // Own process group on POSIX so killMockApi() can signal `npm run` - // *and* the server it spawns, not just the wrapper. - detached: process.platform !== 'win32', - }); + if (unitOnly) { + console.log( + 'Running the unit suite only — no mock API available. ' + + 'Set MOCK_API_DIR=/path/to/mock-api to include test/integration/*.' + ); + } else { + // Start mock API with authentication + console.log('Starting mock API with authentication...'); + mockApiProcess = spawn('npm', ['run', 'start:auth'], { + cwd: mockApiDir, + stdio: ['ignore', 'pipe', 'pipe'], + shell: true, + // Own process group on POSIX so killMockApi() can signal `npm run` + // *and* the server it spawns, not just the wrapper. + detached: process.platform !== 'win32', + }); - forwardOutput(mockApiProcess.stdout, (text) => process.stdout.write(text)); - forwardOutput(mockApiProcess.stderr, (text) => process.stderr.write(text)); + forwardOutput(mockApiProcess.stdout, (text) => process.stdout.write(text)); + forwardOutput(mockApiProcess.stderr, (text) => process.stderr.write(text)); - mockApiProcess.on('error', (error) => { - console.error('Mock API failed to start:', error); - if (!testsFinished) { - process.exit(1); - } - }); + mockApiProcess.on('error', (error) => { + console.error('Mock API failed to start:', error); + if (!testsFinished) { + process.exit(1); + } + }); - mockApiProcess.on('exit', (code, signal) => { - mockApiExited = true; - if (!testsFinished) { - console.error( - `Mock API exited before tests finished (code ${code}, signal ${signal})` - ); - process.exit(1); - } - }); + mockApiProcess.on('exit', (code, signal) => { + mockApiExited = true; + if (!testsFinished) { + console.error( + `Mock API exited before tests finished (code ${code}, signal ${signal})` + ); + process.exit(1); + } + }); - console.log('Waiting for mock API to be ready...'); - await waitForMockApi(); - console.log('Mock API is ready.'); + console.log('Waiting for mock API to be ready...'); + await waitForMockApi(); + console.log('Mock API is ready.'); + } // Run tests. Mocha + .mocharc.json handle TypeScript loading via `tsx` // (see `node-option: ["import=tsx"]` there). Mocha 11 imports files as // ESM, so the `require: ts-node/register` hook doesn't get applied; tsx // registers an ESM loader that resolves TS relative imports correctly. console.log('Running tests...'); - const testProcess = spawn('npx', [ + const mochaArgs = [ 'mocha', '--no-warnings', 'test/**/*.test.ts', '--timeout', '60000' - ], { + ]; + // Quoted so the shell hands mocha the literal glob instead of expanding it. + if (unitOnly) mochaArgs.push('--ignore', '"test/integration/**"'); + const testProcess = spawn('npx', mochaArgs, { cwd: cliDir, stdio: 'inherit', shell: true, From fcfa524ff5213f1ec8ff4b86ef61826c78d6e875 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:26:15 +0100 Subject: [PATCH 62/78] feat!: remove iOS 16 (#126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS 16 was removed from the platform on 2026-08-24 (dcd). Dropping it from the CLI's enum means `--ios-version 16` now fails client-side with a validation error naming the supported set, instead of travelling to the API for a 400. Old CLIs still get the server-side rejection, so this is defence in depth rather than the gate. The --help text at config/flags/device.flags.ts derives from this enum, so it follows automatically; it now reads "options: 18, 17, 26". Defaults were already iphone-14 / 17 in device-validation.service.ts and are unaffected — iPhone 14 keeps iOS 17 and 18. src/types/generated/schema.types.ts is regenerated against the new API swagger. As well as narrowing iOSVersion it picks up the API-side drift that had accumulated since the artifact was last built on 2026-08-06. Not touched: iphone-14-pro / iphone-15-pro are still in EiOSDevices despite having been removed from the platform in January. That is a separate drift with the same shape, and folding it in here would hide it inside an iOS 16 change. --- src/types/domain/device.types.ts | 1 - src/types/generated/schema.types.ts | 79 ++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/types/domain/device.types.ts b/src/types/domain/device.types.ts index 4f1ce7d..5c808a7 100644 --- a/src/types/domain/device.types.ts +++ b/src/types/domain/device.types.ts @@ -26,7 +26,6 @@ export enum EAndroidDevices { export enum EiOSVersions { 'eighteen' = '18', 'seventeen' = '17', - 'sixteen' = '16', 'twentySix' = '26', } diff --git a/src/types/generated/schema.types.ts b/src/types/generated/schema.types.ts index 574e604..a1dabd0 100644 --- a/src/types/generated/schema.types.ts +++ b/src/types/generated/schema.types.ts @@ -140,9 +140,10 @@ export interface paths { * never touches credits. Lets the CLI print the cell count and estimated cost, * and surface validation errors, before uploading the flow ZIP. * - * The dollar estimate is exact for non-Google-Play cells; a Google Play - * column's price is path-dependent until #1100 unifies the parallel and - * sequential Play tiering. + * The dollar estimate is exact for every cell, Google Play included: quote + * and charge both run buildBaseCostByTuple -> calculateCost -> the shared + * resolvePricingTier over the same typed device, and so do the parallel and + * sequential submit paths (#1100). */ post: operations["UploadsController_estimateMatrix"]; delete?: never; @@ -689,6 +690,29 @@ export interface paths { patch?: never; trace?: never; }; + "/webhooks/notify-on-retry": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Toggle re-delivery when a retry finishes on an already-reported run (#1277). + * + * Defaults to on, unlike the Slack and email equivalents: this endpoint exists + * for the consumer that would rather NOT be called twice, while automations + * that want the current state keep working untouched. + */ + post: operations["WebhooksController_setNotifyOnRetry"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/webhooks/regenerate-secret": { parameters: { query?: never; @@ -1626,7 +1650,7 @@ export interface components { appFile?: string; env: string; /** @enum {string} */ - iOSVersion?: "16" | "17" | "18" | "26"; + iOSVersion?: "17" | "18" | "26"; /** @enum {string} */ iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; platform?: string; @@ -1666,7 +1690,7 @@ export interface components { appFile?: string; env: string; /** @enum {string} */ - iOSVersion?: "16" | "17" | "18" | "26"; + iOSVersion?: "17" | "18" | "26"; /** @enum {string} */ iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; platform?: string; @@ -1717,7 +1741,7 @@ export interface components { appFile?: string; env: string; /** @enum {string} */ - iOSVersion?: "16" | "17" | "18" | "26"; + iOSVersion?: "17" | "18" | "26"; /** @enum {string} */ iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; platform?: string; @@ -2300,9 +2324,7 @@ export interface operations { OrgController_handlePaddleWebhook: { parameters: { query?: never; - header: { - "paddle-signature": string; - }; + header?: never; path?: never; cookie?: never; }; @@ -2933,7 +2955,6 @@ export interface operations { * "iphone-14": { * "name": "iPhone 14", * "versions": [ - * "16", * "17", * "18" * ], @@ -2997,8 +3018,7 @@ export interface operations { * "33", * "34", * "35", - * "36", - * "37" + * "36" * ], * "deprecated": false * }, @@ -3016,8 +3036,7 @@ export interface operations { * "33", * "34", * "35", - * "36", - * "37" + * "36" * ], * "deprecated": false * }, @@ -3027,8 +3046,7 @@ export interface operations { * "33", * "34", * "35", - * "36", - * "37" + * "36" * ], * "deprecated": false * }, @@ -3139,6 +3157,8 @@ export interface operations { secret_key?: string; /** @description Masked secret (default) */ secret_key_masked?: string; + /** @description Re-deliver when retried tests finish (default true) */ + notify_on_retry?: boolean; /** Format: date-time */ created_at?: string; /** Format: date-time */ @@ -3198,6 +3218,32 @@ export interface operations { }; }; }; + WebhooksController_setNotifyOnRetry: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + notifyOnRetry: boolean; + }; + }; + }; + responses: { + /** @description Retry re-delivery setting updated */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + }; + }; WebhooksController_regenerateWebhookSecret: { parameters: { query?: never; @@ -3882,7 +3928,6 @@ export interface operations { query?: never; header: { authorization: string; - "x-dcd-org": string; }; path?: never; cookie?: never; From a60611d388fd6c570a5548bd3ece997afc4ae926 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:46:18 +0100 Subject: [PATCH 63/78] chore: release the iOS 16 removal as 5.4.0, not 6.0.0 (#127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I wrote fcfa524f as `feat!: remove iOS 16`. The `!` is what made release-please propose 6.0.0-beta.2 in #122. That marker was wrong. Dropping '16' from EiOSVersions does not break anything that still worked: the platform removed iOS 16 server-side on 2026-08-24, so `--ios-version 16` already fails with a 400 on every existing 5.x CLI. The enum change only moves that same rejection client-side, with a better message. Nobody loses a working capability, which is the bar for a major. `Release-As: 5.4.0-beta.0` re-pins the pending beta release PR. Same mechanism this repo already used to hold #120 at 5.3.1 rather than let it roll. Minor rather than patch because the supported-version set changed, which is a behaviour change worth a minor even though the trigger was a removal. ⚠️ THIS ONLY FIXES THE BETA LINE. fcfa524f still carries `!` in history, and the production line reads the same commits — so when dev promotes to production, release-please will propose 6.0.0 on the stable line for exactly the same reason. That promotion needs its own `Release-As: 5.4.0` commit, or fcfa524f reworded before it lands there. production does not have the commit yet, so there is still a clean window to do the latter. Release-As: 5.4.0-beta.0 From 0aaa6521bb3fc6ab09c0dd6892b271edbe4852b6 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:51:22 +0100 Subject: [PATCH 64/78] chore(dev): release 5.4.0-beta.0 (#122) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index 0c1b0cf..bd81d99 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.3.1-beta.2" + ".": "5.4.0-beta.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d7087ac..2dcfc63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [5.4.0-beta.0](https://github.com/devicecloud-dev/dcd-cli/compare/v5.3.1-beta.2...v5.4.0-beta.0) (2026-08-24) + + +### ⚠ BREAKING CHANGES + +* remove iOS 16 ([#126](https://github.com/devicecloud-dev/dcd-cli/issues/126)) + +### Features + +* remove iOS 16 ([#126](https://github.com/devicecloud-dev/dcd-cli/issues/126)) ([fcfa524](https://github.com/devicecloud-dev/dcd-cli/commit/fcfa524ff5213f1ec8ff4b86ef61826c78d6e875)) + + +### Dependencies + +* bump the minor-and-patch group with 6 updates ([#121](https://github.com/devicecloud-dev/dcd-cli/issues/121)) ([1c07fc1](https://github.com/devicecloud-dev/dcd-cli/commit/1c07fc14a48e7cc29c6deb56f0ad4aa533794f25)) + + +### Miscellaneous + +* release the iOS 16 removal as 5.4.0, not 6.0.0 ([#127](https://github.com/devicecloud-dev/dcd-cli/issues/127)) ([a60611d](https://github.com/devicecloud-dev/dcd-cli/commit/a60611d388fd6c570a5548bd3ece997afc4ae926)) + ## [5.3.1-beta.2](https://github.com/devicecloud-dev/dcd-cli/compare/v5.3.1-beta.1...v5.3.1-beta.2) (2026-08-13) diff --git a/package.json b/package.json index 25f936a..02bfc74 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test:unit": "node scripts/test-runner.mjs --unit", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.3.1-beta.2", + "version": "5.4.0-beta.0", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From 133396051511997bd03efe0aeaf2930dd74c83de Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:53:38 +0100 Subject: [PATCH 65/78] feat(device): add iOS 27 and the iPhone 17 family (#131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors devicecloud-dev/dcd, where iOS 27 and iphone-17/-pro/-pro-max ship behind the IOS_27 rollout gate (on for dev, off in production until the fleet carries a 27 runtime). Nothing here needs gating. The flag descriptions and `cloud.ts` validation derive from these enums, so the CLI offers the values and the API's compatibility matrix is what actually accepts or rejects them — a user who asks for iOS 27 against production gets "iOS version '27' is not supported for device ...", which is the right error. `src/utils/compatibility.ts` already fetches the live matrix from GET /results/compatibility/data, so `dcd list devices` tracks the gate without a release. schema.types.ts regenerated from the API's swagger.json; the only delta is the three iOSVersion enums and the iOSDevice enum, confirming the committed types were otherwise in sync. Note the pre-existing drift in EiOSDevices, left alone here: it still carries iphone-14-pro and iphone-15-pro, which the API dropped. Worth its own cleanup rather than riding along with a feature change. --- src/types/domain/device.types.ts | 4 ++++ src/types/generated/schema.types.ts | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/types/domain/device.types.ts b/src/types/domain/device.types.ts index 5c808a7..1256ad6 100644 --- a/src/types/domain/device.types.ts +++ b/src/types/domain/device.types.ts @@ -13,6 +13,9 @@ export enum EiOSDevices { 'iphone-16-plus' = 'iphone-16-plus', 'iphone-16-pro' = 'iphone-16-pro', 'iphone-16-pro-max' = 'iphone-16-pro-max', + 'iphone-17' = 'iphone-17', + 'iphone-17-pro' = 'iphone-17-pro', + 'iphone-17-pro-max' = 'iphone-17-pro-max', } export enum EAndroidDevices { @@ -26,6 +29,7 @@ export enum EAndroidDevices { export enum EiOSVersions { 'eighteen' = '18', 'seventeen' = '17', + 'twentySeven' = '27', 'twentySix' = '26', } diff --git a/src/types/generated/schema.types.ts b/src/types/generated/schema.types.ts index a1dabd0..387b1c6 100644 --- a/src/types/generated/schema.types.ts +++ b/src/types/generated/schema.types.ts @@ -1650,9 +1650,9 @@ export interface components { appFile?: string; env: string; /** @enum {string} */ - iOSVersion?: "17" | "18" | "26"; + iOSVersion?: "17" | "18" | "26" | "27"; /** @enum {string} */ - iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-17-pro" | "iphone-17-pro-max" | "ipad-pro-6th-gen"; platform?: string; googlePlay?: boolean; config: string; @@ -1690,9 +1690,9 @@ export interface components { appFile?: string; env: string; /** @enum {string} */ - iOSVersion?: "17" | "18" | "26"; + iOSVersion?: "17" | "18" | "26" | "27"; /** @enum {string} */ - iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-17-pro" | "iphone-17-pro-max" | "ipad-pro-6th-gen"; platform?: string; googlePlay?: boolean; config: string; @@ -1741,9 +1741,9 @@ export interface components { appFile?: string; env: string; /** @enum {string} */ - iOSVersion?: "17" | "18" | "26"; + iOSVersion?: "17" | "18" | "26" | "27"; /** @enum {string} */ - iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-17-pro" | "iphone-17-pro-max" | "ipad-pro-6th-gen"; platform?: string; googlePlay?: boolean; config: string; From 3222ddaabc6a133eb88b73e768d992a36ae89118 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:48:03 +0100 Subject: [PATCH 66/78] fix(ci): point the CLA check at our node24 fork (#135) Upstream contributor-assistant/github-action is archived and still declares node20; GitHub's forced Node 24 migration makes the step succeed then exit non-zero, so the required check failed on every PR from 2026-09-02. devicecloud-dev/cla-assistant-action@v2.6.2 is a private fork whose only change is the runtime declaration. --- .github/workflows/cla.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index ba697bc..56a1ffa 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -2,10 +2,24 @@ name: CLA Assistant # Gates merges on a signed Contributor License Agreement. # -# Uses CLA Assistant Lite (contributor-assistant/github-action): signatures are -# stored as a JSON file committed to a branch of THIS repo (no third-party -# service holds the data). Contributors sign by commenting the configured phrase -# on their PR; the action records it and flips the check green. +# Uses CLA Assistant Lite: signatures are stored as a JSON file committed to a +# branch of THIS repo (no third-party service holds the data). Contributors sign +# by commenting the configured phrase on their PR; the action records it and +# flips the check green. +# +# ACTION SOURCE: devicecloud-dev/cla-assistant-action, a PRIVATE fork of the +# upstream contributor-assistant/github-action, which was archived read-only on +# 2026-03-23. We forked because GitHub's Node 20 deprecation began force-running +# node20 actions on Node 24, under which the upstream step does its work, logs +# "All contributors have signed the CLA", and THEN exits non-zero — failing a +# required check on every PR (first hit 2026-09-02, last green 2026-08-31). The +# fork's only change is `using: node24`; dist is unmodified. See its FORK.md. +# +# The fork is private, so it relies on Settings -> Actions -> Access -> +# "Accessible from repositories in the devicecloud-dev organization" being set +# on THAT repo. Resolution works for outside-contributor PRs because this +# workflow is `pull_request_target`, so it runs in this repo's context rather +# than the fork's. # # AUTH: mints a token from the shared automation GitHub App (the same App # release-please uses), so signature commits show as the bot and there's no @@ -50,7 +64,7 @@ jobs: with: app-id: ${{ secrets.BOT_APP_ID }} private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} - - uses: contributor-assistant/github-action@v2.6.1 + - uses: devicecloud-dev/cla-assistant-action@v2.6.2 if: env.HAS_APP == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 38f6dfa8c6724be9ce709ef6845e97911a4ee0da Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:59:04 +0100 Subject: [PATCH 67/78] feat(device): drop the iPhone 17 family (#134) Mirrors dcd. schema.types.ts regenerated from the API's swagger.json. --- src/types/domain/device.types.ts | 3 --- src/types/generated/schema.types.ts | 11 ++++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/types/domain/device.types.ts b/src/types/domain/device.types.ts index 1256ad6..b0629ed 100644 --- a/src/types/domain/device.types.ts +++ b/src/types/domain/device.types.ts @@ -13,9 +13,6 @@ export enum EiOSDevices { 'iphone-16-plus' = 'iphone-16-plus', 'iphone-16-pro' = 'iphone-16-pro', 'iphone-16-pro-max' = 'iphone-16-pro-max', - 'iphone-17' = 'iphone-17', - 'iphone-17-pro' = 'iphone-17-pro', - 'iphone-17-pro-max' = 'iphone-17-pro-max', } export enum EAndroidDevices { diff --git a/src/types/generated/schema.types.ts b/src/types/generated/schema.types.ts index 387b1c6..1271db8 100644 --- a/src/types/generated/schema.types.ts +++ b/src/types/generated/schema.types.ts @@ -1652,7 +1652,7 @@ export interface components { /** @enum {string} */ iOSVersion?: "17" | "18" | "26" | "27"; /** @enum {string} */ - iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-17-pro" | "iphone-17-pro-max" | "ipad-pro-6th-gen"; + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; platform?: string; googlePlay?: boolean; config: string; @@ -1692,7 +1692,7 @@ export interface components { /** @enum {string} */ iOSVersion?: "17" | "18" | "26" | "27"; /** @enum {string} */ - iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-17-pro" | "iphone-17-pro-max" | "ipad-pro-6th-gen"; + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; platform?: string; googlePlay?: boolean; config: string; @@ -1743,7 +1743,7 @@ export interface components { /** @enum {string} */ iOSVersion?: "17" | "18" | "26" | "27"; /** @enum {string} */ - iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "iphone-17" | "iphone-17-pro" | "iphone-17-pro-max" | "ipad-pro-6th-gen"; + iOSDevice?: "iphone-14" | "iphone-15" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen"; platform?: string; googlePlay?: boolean; config: string; @@ -3078,10 +3078,11 @@ export interface operations { * "2.6.0", * "2.6.1", * "2.7.0", - * "2.8.0" + * "2.8.0", + * "2.9.0" * ], * "defaultVersion": "2.2.0", - * "latestVersion": "2.8.0" + * "latestVersion": "2.9.0" * } * } * } From b90324149e0214916c99c52e11985ed41aca44db Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:01:53 +0100 Subject: [PATCH 68/78] docs(ci): the CLA action fork must stay public (#136) A public repo cannot resolve an action from a private one; the first attempt failed at resolution even with the org access policy set. --- .github/workflows/cla.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 56a1ffa..24fe627 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -7,19 +7,18 @@ name: CLA Assistant # by commenting the configured phrase on their PR; the action records it and # flips the check green. # -# ACTION SOURCE: devicecloud-dev/cla-assistant-action, a PRIVATE fork of the -# upstream contributor-assistant/github-action, which was archived read-only on +# ACTION SOURCE: devicecloud-dev/cla-assistant-action, our fork of the upstream +# contributor-assistant/github-action, which was archived read-only on # 2026-03-23. We forked because GitHub's Node 20 deprecation began force-running # node20 actions on Node 24, under which the upstream step does its work, logs # "All contributors have signed the CLA", and THEN exits non-zero — failing a # required check on every PR (first hit 2026-09-02, last green 2026-08-31). The # fork's only change is `using: node24`; dist is unmodified. See its FORK.md. # -# The fork is private, so it relies on Settings -> Actions -> Access -> -# "Accessible from repositories in the devicecloud-dev organization" being set -# on THAT repo. Resolution works for outside-contributor PRs because this -# workflow is `pull_request_target`, so it runs in this repo's context rather -# than the fork's. +# The fork must stay PUBLIC: this repo is public, and a public repo's workflow +# cannot resolve an action from a private one — it fails at resolution with +# "Unable to resolve action ... not found", before any CLA logic runs, even with +# the org access policy set. Do not flip it private. # # AUTH: mints a token from the shared automation GitHub App (the same App # release-please uses), so signature commits show as the bot and there's no From 0d2234a55965b4840c6de1a8ddcc3e4933aab237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:03:11 +0100 Subject: [PATCH 69/78] deps: bump the minor-and-patch group across 1 directory with 4 updates (#133) Bumps the minor-and-patch group with 4 updates in the / directory: [@supabase/supabase-js](https://github.com/supabase/supabase-js/tree/HEAD/packages/core/supabase-js), [js-yaml](https://github.com/nodeca/js-yaml), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `@supabase/supabase-js` from 2.112.3 to 2.112.4 - [Release notes](https://github.com/supabase/supabase-js/releases) - [Changelog](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/CHANGELOG.md) - [Commits](https://github.com/supabase/supabase-js/commits/v2.112.4/packages/core/supabase-js) Updates `js-yaml` from 5.3.0 to 5.4.1 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/5.3.0...5.4.1) Updates `@types/node` from 26.2.0 to 26.4.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `typescript-eslint` from 8.67.0 to 8.68.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.68.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@supabase/supabase-js" dependency-version: 2.112.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: js-yaml dependency-version: 5.4.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: "@types/node" dependency-version: 26.4.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: typescript-eslint dependency-version: 8.68.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 208 ++++++++++++++++++++++++------------------------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0efa3bf..442feda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 1.30.0(zod@4.4.3) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.112.3 + version: 2.112.4 bplist-parser: specifier: ^0.3.2 version: 0.3.2 @@ -58,7 +58,7 @@ importers: version: 0.2.2 js-yaml: specifier: ^5.2.2 - version: 5.3.0 + version: 5.4.1 node-apk: specifier: ^1.2.1 version: 1.2.1 @@ -95,7 +95,7 @@ importers: version: 10.0.10 '@types/node': specifier: ^26.0.0 - version: 26.2.0 + version: 26.4.0 '@types/yazl': specifier: ^3.3.1 version: 3.3.1 @@ -131,7 +131,7 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.67.0(eslint@10.8.1)(typescript@6.0.3) + version: 8.68.0(eslint@10.8.1)(typescript@6.0.3) packages: @@ -402,31 +402,31 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@supabase/auth-js@2.112.3': - resolution: {integrity: sha512-NA0rsgAlWZPvbhw8aUdmgfpHVgUAcd8zK5ov43l++o1bLIPXZhRiAlRobhwF5AatQuovpqxsMH50F4oyyV4XZw==} + '@supabase/auth-js@2.112.4': + resolution: {integrity: sha512-z8DesgwLzKM5PiT0yNmJU8VJyh1zAhYi+20Z7drdJQLXg/wWW4yGt/un+He5ERYUo94Vz66t5aeyr1DIDemI5A==} engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.112.3': - resolution: {integrity: sha512-gfv481mTOVWtZIJgXupxZpni2V2UWPf6jeF/jOK7HdMHdH+mt6sU0sHHwf0POsPip8ltlulu9OUHgwVzl5ddRw==} + '@supabase/functions-js@2.112.4': + resolution: {integrity: sha512-DQ0aVH8wSQAccVqNoEkec62qCu2QRNyoGN53RqsVZ1k6F1zq4/v8scrlR6LNT2RJmT97apiTmORijPVhErCS2g==} engines: {node: '>=22.0.0'} '@supabase/phoenix@0.4.5': resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} - '@supabase/postgrest-js@2.112.3': - resolution: {integrity: sha512-+Mf6uCpzr00bqxwX8hTK2X2L9eAL/1vuOjdEjx6upz9ulb0RmQT16XeU/JkMUlVHw/B46ZnPa2busY4Kd9YCzw==} + '@supabase/postgrest-js@2.112.4': + resolution: {integrity: sha512-uaubtPSeg2TR4wrtfQoQWgkTAe+a0qWX2KhmwvTfNl5mGN9+U7owiJt6abk3o/V6O899PSRD1yzxs5RlF4xTug==} engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.112.3': - resolution: {integrity: sha512-E6wljXWs7DUOloyIB69i3YFInWE6IyCvgTAbQ0KYxOHv26FdA1KzEXTuzxrYEdf70t406Z9BRwUlGyclGF2FXA==} + '@supabase/realtime-js@2.112.4': + resolution: {integrity: sha512-vZ+j079SKrM0Xiq7MJCvQKLDpaH2kfKfLY68xuQE1sqsCsMmx1CyrDBJHsxZ3cX01VOs5SI9igmoZAF3BmdZxw==} engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.112.3': - resolution: {integrity: sha512-oSK61tzlUvg+BWPqpKQCu9qqonsO26btaoAR9D6Gest2aj7xUqToj9rKyaoYOJczkhg9BjqA1REbYy9tPI4bDA==} + '@supabase/storage-js@2.112.4': + resolution: {integrity: sha512-lQ0JemuTlMIXVKgSci1qez8yPnM5hyDngeAfEBjZS2Om4D+Cus0EE5BE6glFobrxdyii1OF4UzWfF0zcQgDq5A==} engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.112.3': - resolution: {integrity: sha512-Jv1bxVQmEJNkjvPEhFaKjPzsh+Ozyew6lWGD+SoYcsclDEP1z7yEvKvfUQfzy0DkxRIQnZNxmmWtAzw5XLTQoA==} + '@supabase/supabase-js@2.112.4': + resolution: {integrity: sha512-UiCX1udlFY1fQQrO7Z3GU7obQsju0w5Vk9mOOwalfo/+Gy+tahWVenSSuu5E/GTy/q//HxvGv2IrCdW66/61kw==} engines: {node: '>=22.0.0'} peerDependencies: '@opentelemetry/api': '>=1.0.0' @@ -455,69 +455,69 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@26.2.0': - resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} + '@types/node@26.4.0': + resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} - '@typescript-eslint/eslint-plugin@8.67.0': - resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==} + '@typescript-eslint/eslint-plugin@8.68.0': + resolution: {integrity: sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.67.0 + '@typescript-eslint/parser': ^8.68.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.67.0': - resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} + '@typescript-eslint/parser@8.68.0': + resolution: {integrity: sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.67.0': - resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} + '@typescript-eslint/project-service@8.68.0': + resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.67.0': - resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} + '@typescript-eslint/scope-manager@8.68.0': + resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.67.0': - resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} + '@typescript-eslint/tsconfig-utils@8.68.0': + resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.67.0': - resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} + '@typescript-eslint/type-utils@8.68.0': + resolution: {integrity: sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.67.0': - resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} + '@typescript-eslint/types@8.68.0': + resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.67.0': - resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} + '@typescript-eslint/typescript-estree@8.68.0': + resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.67.0': - resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} + '@typescript-eslint/utils@8.68.0': + resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.67.0': - resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} + '@typescript-eslint/visitor-keys@8.68.0': + resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.10': @@ -1163,8 +1163,8 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-yaml@5.3.0: - resolution: {integrity: sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==} + js-yaml@5.4.1: + resolution: {integrity: sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==} hasBin: true jsesc@3.1.0: @@ -1399,8 +1399,8 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} pkce-challenge@5.0.1: @@ -1688,8 +1688,8 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript-eslint@8.67.0: - resolution: {integrity: sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==} + typescript-eslint@8.68.0: + resolution: {integrity: sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1998,37 +1998,37 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@supabase/auth-js@2.112.3': + '@supabase/auth-js@2.112.4': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.112.3': + '@supabase/functions-js@2.112.4': dependencies: tslib: 2.8.1 '@supabase/phoenix@0.4.5': {} - '@supabase/postgrest-js@2.112.3': + '@supabase/postgrest-js@2.112.4': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.112.3': + '@supabase/realtime-js@2.112.4': dependencies: '@supabase/phoenix': 0.4.5 tslib: 2.8.1 - '@supabase/storage-js@2.112.3': + '@supabase/storage-js@2.112.4': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.112.3': + '@supabase/supabase-js@2.112.4': dependencies: - '@supabase/auth-js': 2.112.3 - '@supabase/functions-js': 2.112.3 - '@supabase/postgrest-js': 2.112.3 - '@supabase/realtime-js': 2.112.3 - '@supabase/storage-js': 2.112.3 + '@supabase/auth-js': 2.112.4 + '@supabase/functions-js': 2.112.4 + '@supabase/postgrest-js': 2.112.4 + '@supabase/realtime-js': 2.112.4 + '@supabase/storage-js': 2.112.4 '@types/chai@5.2.3': dependencies: @@ -2047,22 +2047,22 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@26.2.0': + '@types/node@26.4.0': dependencies: undici-types: 8.3.0 '@types/yazl@3.3.1': dependencies: - '@types/node': 26.2.0 + '@types/node': 26.4.0 - '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/type-utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.67.0 + '@typescript-eslint/parser': 8.68.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.68.0 + '@typescript-eslint/type-utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.68.0 eslint: 10.8.1 ignore: 7.0.6 natural-compare: 1.4.0 @@ -2071,41 +2071,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.67.0(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/parser@8.68.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.67.0 + '@typescript-eslint/scope-manager': 8.68.0 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.68.0 debug: 4.4.3(supports-color@8.1.1) eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.67.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.68.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) - '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) + '@typescript-eslint/types': 8.68.0 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.67.0': + '@typescript-eslint/scope-manager@8.68.0': dependencies: - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/visitor-keys': 8.67.0 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 - '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.68.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.67.0(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.68.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) eslint: 10.8.1 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2113,14 +2113,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.67.0': {} + '@typescript-eslint/types@8.68.0': {} - '@typescript-eslint/typescript-estree@8.67.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.68.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.67.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/visitor-keys': 8.67.0 + '@typescript-eslint/project-service': 8.68.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.3 semver: 7.8.5 @@ -2130,20 +2130,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.67.0(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/utils@8.68.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1) - '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.68.0 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.67.0': + '@typescript-eslint/visitor-keys@8.68.0': dependencies: - '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/types': 8.68.0 eslint-visitor-keys: 5.0.1 '@xmldom/xmldom@0.9.10': {} @@ -2594,9 +2594,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 file-entry-cache@8.0.0: dependencies: @@ -2791,7 +2791,7 @@ snapshots: js-base64@3.7.8: {} - js-yaml@5.3.0: + js-yaml@5.4.1: dependencies: argparse: 2.0.1 @@ -2911,7 +2911,7 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 5.3.0 + js-yaml: 5.4.1 log-symbols: 4.1.0 minimatch: 9.0.7 ms: 2.1.3 @@ -3006,7 +3006,7 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.5: {} + picomatch@4.0.7: {} pkce-challenge@5.0.1: {} @@ -3255,8 +3255,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 to-regex-range@5.0.1: dependencies: @@ -3298,12 +3298,12 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typescript-eslint@8.67.0(eslint@10.8.1)(typescript@6.0.3): + typescript-eslint@8.68.0(eslint@10.8.1)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/parser': 8.68.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: From fc43dc6e62a265b9860b92b5a8ace750bcead393 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:23:54 +0100 Subject: [PATCH 70/78] chore: release 5.4.1-beta.1 (#137) Release-As: 5.4.1-beta.1 From 7ccb0c98e1834d2689f53c001383c8050c99e17f Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:35:25 +0100 Subject: [PATCH 71/78] fix(deps): refresh audit overrides to the patched versions (#141) fast-uri 3.1.5 -> 3.1.6, @xmldom/xmldom 0.9.10 -> 0.9.12, add qs >= 6.16.0, and lift the hono cap to 4.13.5 so Dependabot can resolve its security update. Co-authored-by: Claude Opus 5 (1M context) --- package.json | 7 ++++--- pnpm-lock.yaml | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 02bfc74..ddd4a0d 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "fast-xml-parser": ">=5.5.7", "flatted": ">=3.4.2", "lodash": ">=4.18.0", - "@xmldom/xmldom": ">=0.9.10", + "@xmldom/xmldom": ">=0.9.12", "minimatch@<3.1.4": "3.1.4", "minimatch@>=5.0.0 <5.1.8": "5.1.8", "minimatch@>=9.0.0 <9.0.7": "9.0.7", @@ -109,8 +109,9 @@ "brace-expansion@>=3.0.0 <5.0.9": "5.0.9", "ws@>=8.0.0 <8.21.0": "8.21.0", "esbuild@<0.28.1": ">=0.28.1", - "fast-uri@>=3.0.0 <3.1.5": "3.1.5", - "hono@>=4.0.0 <4.12.34": "4.12.34", + "qs@<6.16.0": "6.16.0", + "fast-uri@>=3.0.0 <3.1.6": "3.1.6", + "hono@>=4.0.0 <4.13.5": "4.13.5", "micromatch>picomatch": "^2.3.2", "tinyglobby>picomatch": "^4.0.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 442feda..24a49da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ overrides: fast-xml-parser: '>=5.5.7' flatted: '>=3.4.2' lodash: '>=4.18.0' - '@xmldom/xmldom': '>=0.9.10' + '@xmldom/xmldom': '>=0.9.12' minimatch@<3.1.4: 3.1.4 minimatch@>=5.0.0 <5.1.8: 5.1.8 minimatch@>=9.0.0 <9.0.7: 9.0.7 @@ -29,8 +29,9 @@ overrides: brace-expansion@>=3.0.0 <5.0.9: 5.0.9 ws@>=8.0.0 <8.21.0: 8.21.0 esbuild@<0.28.1: '>=0.28.1' - fast-uri@>=3.0.0 <3.1.5: 3.1.5 - hono@>=4.0.0 <4.12.34: 4.12.34 + qs@<6.16.0: 6.16.0 + fast-uri@>=3.0.0 <3.1.6: 3.1.6 + hono@>=4.0.0 <4.13.5: 4.13.5 micromatch>picomatch: ^2.3.2 tinyglobby>picomatch: ^4.0.4 @@ -346,7 +347,7 @@ packages: resolution: {integrity: sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==} engines: {node: '>=20'} peerDependencies: - hono: 4.12.34 + hono: 4.13.5 '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} @@ -520,10 +521,9 @@ packages: resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@xmldom/xmldom@0.9.10': - resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} + '@xmldom/xmldom@0.9.12': + resolution: {integrity: sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==} engines: {node: '>=14.6'} - deprecated: this version has critical issues, please update to the latest version accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} @@ -915,8 +915,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-uri@3.1.6: + resolution: {integrity: sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -1044,8 +1044,8 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hono@4.12.34: - resolution: {integrity: sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==} + hono@4.13.5: + resolution: {integrity: sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==} engines: {node: '>=16.9.0'} http-errors@2.0.1: @@ -1438,8 +1438,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} querystringify@2.2.0: @@ -1928,9 +1928,9 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@hono/node-server@2.0.12(hono@4.12.34)': + '@hono/node-server@2.0.12(hono@4.13.5)': dependencies: - hono: 4.12.34 + hono: 4.13.5 '@humanfs/core@0.19.2': dependencies: @@ -1963,7 +1963,7 @@ snapshots: '@modelcontextprotocol/sdk@1.30.0(zod@4.4.3)': dependencies: - '@hono/node-server': 2.0.12(hono@4.12.34) + '@hono/node-server': 2.0.12(hono@4.13.5) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -1973,7 +1973,7 @@ snapshots: eventsource-parser: 3.1.0 express: 5.2.1 express-rate-limit: 8.6.1(express@5.2.1) - hono: 4.12.34 + hono: 4.13.5 jose: 6.2.5 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -2146,7 +2146,7 @@ snapshots: '@typescript-eslint/types': 8.68.0 eslint-visitor-keys: 5.0.1 - '@xmldom/xmldom@0.9.10': {} + '@xmldom/xmldom@0.9.12': {} accepts@2.0.0: dependencies: @@ -2173,7 +2173,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.6 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -2205,7 +2205,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.0 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -2553,7 +2553,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.3 + qs: 6.16.0 range-parser: 1.3.0 router: 2.2.0 send: 1.2.1 @@ -2584,7 +2584,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.5: {} + fast-uri@3.1.6: {} fast-wrap-ansi@0.2.2: dependencies: @@ -2706,7 +2706,7 @@ snapshots: he@1.2.0: {} - hono@4.12.34: {} + hono@4.13.5: {} http-errors@2.0.1: dependencies: @@ -3012,7 +3012,7 @@ snapshots: plist@5.0.0: dependencies: - '@xmldom/xmldom': 0.9.10 + '@xmldom/xmldom': 0.9.12 xmlbuilder: 15.1.1 pluralize@8.0.0: {} @@ -3039,7 +3039,7 @@ snapshots: punycode@2.3.1: {} - qs@6.15.3: + qs@6.16.0: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 From df39281ebcc8f78bd6f46aa7f7dada4ca18119c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:42:43 +0100 Subject: [PATCH 72/78] chore: bump mocha from 11.8.0 to 12.0.0 (#140) Bumps [mocha](https://github.com/mochajs/mocha) from 11.8.0 to 12.0.0. - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v11.8.0...v12.0.0) --- updated-dependencies: - dependency-name: mocha dependency-version: 12.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 390 ++++++------------------------------------------- 2 files changed, 48 insertions(+), 344 deletions(-) diff --git a/package.json b/package.json index ddd4a0d..bcf6510 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-unicorn": "^73.0.0", "husky": "^9.1.7", - "mocha": "^11.7.6", + "mocha": "^12.0.0", "prettier": "^3.8.4", "shx": "^0.4.0", "tsx": "^4.22.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24a49da..13c1a2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,8 +116,8 @@ importers: specifier: ^9.1.7 version: 9.1.7 mocha: - specifier: ^11.7.6 - version: 11.8.0 + specifier: ^12.0.0 + version: 12.0.0 prettier: specifier: ^3.8.4 version: 3.9.6 @@ -369,10 +369,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -399,10 +395,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@supabase/auth-js@2.112.4': resolution: {integrity: sha512-z8DesgwLzKM5PiT0yNmJU8VJyh1zAhYi+20Z7drdJQLXg/wWW4yGt/un+He5ERYUo94Vz66t5aeyr1DIDemI5A==} engines: {node: '>=22.0.0'} @@ -553,22 +545,6 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -636,10 +612,6 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - caniuse-lite@1.0.30001809: resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} @@ -647,10 +619,6 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - chalk@6.0.0: resolution: {integrity: sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==} engines: {node: '>=22'} @@ -658,9 +626,9 @@ packages: change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} @@ -673,17 +641,6 @@ packages: citty@0.2.2: resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combine-errors@3.0.3: resolution: {integrity: sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==} @@ -739,10 +696,6 @@ packages: supports-color: optional: true - decamelize@4.0.0: - resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} - engines: {node: '>=10'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -762,21 +715,12 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} electron-to-chromium@1.5.403: resolution: {integrity: sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -957,17 +901,9 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - flatted@3.4.4: resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -988,10 +924,6 @@ packages: resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} engines: {node: '>=18'} - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1012,10 +944,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.5.0: - resolution: {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 - hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} globals@17.9.0: resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} @@ -1040,10 +971,6 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - hono@4.13.5: resolution: {integrity: sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==} engines: {node: '>=16.9.0'} @@ -1112,10 +1039,6 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1132,10 +1055,6 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -1154,9 +1073,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jose@6.2.5: resolution: {integrity: sha512-2E5L2yRp03FnwreJLJX8/r7mHiZICCf8kG7fAsTWkSQTDAcc46NIZoQLKy+EJ8sPoJlxyS4OQR5H70LjIZZlIQ==} @@ -1222,12 +1138,9 @@ packages: lodash.uniqby@4.5.0: resolution: {integrity: sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} make-asynchronous@1.1.0: resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} @@ -1272,10 +1185,6 @@ packages: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} - minimatch@9.0.7: - resolution: {integrity: sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==} - engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1287,9 +1196,9 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - mocha@11.8.0: - resolution: {integrity: sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + mocha@12.0.0: + resolution: {integrity: sha512-NYNh5IFt6WYqm9bi4601m7vix8MZdXC0DwS4gY6WhXO2RgJWhivhISVmq1oklCif18OSI9l+vx5Mdm5oh1XGiQ==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true ms@2.1.3: @@ -1363,9 +1272,6 @@ packages: resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} engines: {node: '>=14.16'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1385,9 +1291,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} @@ -1460,9 +1366,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} @@ -1472,10 +1378,6 @@ packages: resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} hasBin: true - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1523,8 +1425,8 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} - serialize-javascript@7.1.0: - resolution: {integrity: sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==} + serialize-javascript@7.1.1: + resolution: {integrity: sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==} engines: {node: '>=20.0.0'} serve-static@2.2.1: @@ -1579,10 +1481,6 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -1594,22 +1492,6 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - strip-eof@1.0.0: resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} engines: {node: '>=0.10.0'} @@ -1618,18 +1500,14 @@ packages: resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} engines: {node: '>=12'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} super-regex@1.1.0: resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} engines: {node: '>=18'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} @@ -1739,16 +1617,8 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - workerpool@9.3.4: - resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + workerpool@10.0.3: + resolution: {integrity: sha512-6z2Iis68Wqth93/G/wJP9u+R3O+d2XTlgWChGCwuT1qLbBsOYueGRZuJ++v3mtDP5KjYdy+WzvWC+VWETSVXJA==} wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -1757,10 +1627,6 @@ packages: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -1770,18 +1636,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} - - yargs@17.7.3: - resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} - engines: {node: '>=12'} - yazl@3.3.1: resolution: {integrity: sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng==} @@ -1948,15 +1802,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -1995,9 +1840,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@pkgjs/parseargs@0.11.0': - optional: true - '@supabase/auth-js@2.112.4': dependencies: tslib: 2.8.1 @@ -2177,16 +2019,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - argparse@2.0.1: {} assertion-error@2.0.1: {} @@ -2251,24 +2083,17 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - camelcase@6.3.0: {} - caniuse-lite@1.0.30001809: {} chai@6.2.2: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chalk@6.0.0: {} change-case@5.4.4: {} - chokidar@4.0.3: + chokidar@5.0.0: dependencies: - readdirp: 4.1.2 + readdirp: 5.1.1 chownr@3.0.0: {} @@ -2276,18 +2101,6 @@ snapshots: citty@0.2.2: {} - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - combine-errors@3.0.3: dependencies: custom-error-instance: 2.1.1 @@ -2336,8 +2149,6 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - decamelize@4.0.0: {} - deep-is@0.1.4: {} depd@2.0.0: {} @@ -2352,16 +2163,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} - ee-first@1.1.1: {} electron-to-chromium@1.5.403: {} - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - encodeurl@2.0.0: {} end-of-stream@1.4.5: @@ -2629,15 +2434,8 @@ snapshots: flatted: 3.4.4 keyv: 4.5.4 - flat@5.0.2: {} - flatted@3.4.4: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - forwarded@0.2.0: {} fresh@2.0.0: {} @@ -2649,8 +2447,6 @@ snapshots: function-timeout@1.0.2: {} - get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2681,14 +2477,11 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.5.0: + glob@13.0.6: dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.7 + minimatch: 10.2.3 minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 + path-scurry: 2.0.2 globals@17.9.0: {} @@ -2704,8 +2497,6 @@ snapshots: dependencies: function-bind: 1.1.2 - he@1.2.0: {} - hono@4.13.5: {} http-errors@2.0.1: @@ -2754,8 +2545,6 @@ snapshots: is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -2769,8 +2558,6 @@ snapshots: is-path-inside@3.0.3: {} - is-plain-obj@2.1.0: {} - is-promise@4.0.0: {} is-stream@1.1.0: {} @@ -2781,12 +2568,6 @@ snapshots: isexe@2.0.0: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jose@6.2.5: {} js-base64@3.7.8: {} @@ -2846,12 +2627,7 @@ snapshots: lodash._baseiteratee: 4.7.0 lodash._baseuniq: 4.6.0 - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - lru-cache@10.4.3: {} + lru-cache@11.5.2: {} make-asynchronous@1.1.0: dependencies: @@ -2888,10 +2664,6 @@ snapshots: dependencies: brace-expansion: 5.0.9 - minimatch@9.0.7: - dependencies: - brace-expansion: 5.0.9 - minimist@1.2.8: {} minipass@7.1.3: {} @@ -2900,29 +2672,24 @@ snapshots: dependencies: minipass: 7.1.3 - mocha@11.8.0: + mocha@12.0.0: dependencies: browser-stdout: 1.3.1 - chokidar: 4.0.3 + chokidar: 5.0.0 debug: 4.4.3(supports-color@8.1.1) diff: 8.0.3 - escape-string-regexp: 4.0.0 find-up: 5.0.0 - glob: 10.5.0 - he: 1.2.0 + glob: 13.0.6 is-path-inside: 3.0.3 + is-unicode-supported: 0.1.0 js-yaml: 5.4.1 - log-symbols: 4.1.0 - minimatch: 9.0.7 + minimatch: 10.2.3 ms: 2.1.3 picocolors: 1.1.1 - serialize-javascript: 7.1.0 - strip-json-comments: 3.1.1 + serialize-javascript: 7.1.1 + strip-json-comments: 5.0.3 supports-color: 8.1.1 - workerpool: 9.3.4 - yargs: 17.7.3 - yargs-parser: 21.1.1 - yargs-unparser: 2.0.0 + workerpool: 10.0.3 ms@2.1.3: {} @@ -2983,8 +2750,6 @@ snapshots: p-timeout@6.1.4: {} - package-json-from-dist@1.0.1: {} - parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -2995,9 +2760,9 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: + path-scurry@2.0.2: dependencies: - lru-cache: 10.4.3 + lru-cache: 11.5.2 minipass: 7.1.3 path-to-regexp@8.4.2: {} @@ -3059,7 +2824,7 @@ snapshots: iconv-lite: 0.7.3 unpipe: 1.0.0 - readdirp@4.1.2: {} + readdirp@5.1.1: {} rechoir@0.6.2: dependencies: @@ -3069,8 +2834,6 @@ snapshots: dependencies: jsesc: 3.1.0 - require-directory@2.1.1: {} - require-from-string@2.0.2: {} requires-port@1.0.0: {} @@ -3124,7 +2887,7 @@ snapshots: transitivePeerDependencies: - supports-color - serialize-javascript@7.1.0: {} + serialize-javascript@7.1.1: {} serve-static@2.2.1: dependencies: @@ -3191,39 +2954,17 @@ snapshots: signal-exit@3.0.7: {} - signal-exit@4.1.0: {} - sisteransi@1.0.5: {} source-map-js@1.2.1: {} statuses@2.0.2: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.2.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - strip-eof@1.0.0: {} strip-indent@4.1.1: {} - strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} super-regex@1.1.0: dependencies: @@ -3231,10 +2972,6 @@ snapshots: make-asynchronous: 1.1.0 time-span: 5.1.0 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - supports-color@8.1.1: dependencies: has-flag: 4.0.0 @@ -3344,49 +3081,16 @@ snapshots: word-wrap@1.2.5: {} - workerpool@9.3.4: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.2.0 + workerpool@10.0.3: {} wrappy@1.0.2: {} xmlbuilder@15.1.1: {} - y18n@5.0.8: {} - yallist@5.0.0: {} yaml@2.9.0: {} - yargs-parser@21.1.1: {} - - yargs-unparser@2.0.0: - dependencies: - camelcase: 6.3.0 - decamelize: 4.0.0 - flat: 5.0.2 - is-plain-obj: 2.1.0 - - yargs@17.7.3: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - yazl@3.3.1: dependencies: buffer-crc32: 1.0.0 From 7daa42b817e5b88d99895bb4bd27c23b122d3d8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:52:21 +0100 Subject: [PATCH 73/78] chore: bump eslint-plugin-unicorn from 73.0.0 to 74.0.0 (#139) Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 73.0.0 to 74.0.0. - [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases) - [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v73.0.0...v74.0.0) --- updated-dependencies: - dependency-name: eslint-plugin-unicorn dependency-version: 74.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 96 +++++++++++++++++++++++++------------------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index bcf6510..4867718 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "chai": "^6.2.2", "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": "^74.0.0", "husky": "^9.1.7", "mocha": "^12.0.0", "prettier": "^3.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13c1a2f..409a061 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,8 +110,8 @@ importers: specifier: ^10.1.8 version: 10.1.8(eslint@10.8.1) eslint-plugin-unicorn: - specifier: ^73.0.0 - version: 73.0.0(eslint@10.8.1) + specifier: ^74.0.0 + version: 74.0.0(eslint@10.8.1) husky: specifier: ^9.1.7 version: 9.1.7 @@ -322,8 +322,8 @@ packages: resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@4.0.5': - resolution: {integrity: sha512-iPmijIAq4hlIJB86PYmY/fcZORHtjphSqICDbwuw32A/JmkhZQ/K/6TjHE03zqf3n5yABpVcbRAMG8Mi9ojy8g==} + '@eslint/css-tree@4.1.0': + resolution: {integrity: sha512-cg0ohyrAG3swyGqt8t1K/OK97DqBw/ftDvlvyY1fmEst5B40UOmsimwLENq74z2dyw5CDM+3zJIW+CV2nFNDdA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': @@ -556,8 +556,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.11.13: - resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} + baseline-browser-mapping@2.11.21: + resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -584,8 +584,8 @@ packages: browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - browserslist@4.28.8: - resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -612,8 +612,8 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - caniuse-lite@1.0.30001809: - resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -718,8 +718,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.403: - resolution: {integrity: sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==} + electron-to-chromium@1.5.422: + resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==} encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} @@ -728,9 +728,9 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} @@ -766,8 +766,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-unicorn@73.0.0: - resolution: {integrity: sha512-V0YatLe9nkGhXEXKe2Qljb1EY0sJHwDV0HUF1NKFwtsHh/fU7qGHDgv+6fchzZcgU2/7noHo2gdjnmo0P2uDPw==} + eslint-plugin-unicorn@74.0.0: + resolution: {integrity: sha512-AGnsGi2SxHg1HEAXxn9nSnZfyjvTWkxm8E8hpd/9tD6dLjBUdcD7+D6ZN64HmmCXTSXlrwVyUqe20Uyb2CaurA==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -948,8 +948,8 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} - globals@17.9.0: - resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} + globals@17.12.0: + resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} engines: {node: '>=18'} gopd@1.2.0: @@ -1150,8 +1150,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mdn-data@2.29.0: - resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==} + mdn-data@2.34.0: + resolution: {integrity: sha512-OgIlLv0NxJKVW4GTSAoEgpRGd4F2XCqGinK0MsMlBCCS/Zcm2/LsbercNWNA7PeMMcjl75NnI97eqyo7zkdxWA==} media-typer@1.1.1: resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} @@ -1221,8 +1221,8 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} - node-releases@2.0.53: - resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + node-releases@2.0.54: + resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==} engines: {node: '>=18'} node-stream-zip@1.16.0: @@ -1585,8 +1585,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.3.1: - resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + update-browserslist-db@1.3.2: + resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -1766,9 +1766,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@4.0.5': + '@eslint/css-tree@4.1.0': dependencies: - mdn-data: 2.29.0 + mdn-data: 2.34.0 source-map-js: 1.2.1 '@eslint/js@10.0.1(eslint@10.8.1)': @@ -2025,7 +2025,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.11.13: {} + baseline-browser-mapping@2.11.21: {} big-integer@1.6.52: {} @@ -2057,13 +2057,13 @@ snapshots: browser-stdout@1.3.1: {} - browserslist@4.28.8: + browserslist@4.28.9: dependencies: - baseline-browser-mapping: 2.11.13 - caniuse-lite: 1.0.30001809 - electron-to-chromium: 1.5.403 - node-releases: 2.0.53 - update-browserslist-db: 1.3.1(browserslist@4.28.8) + baseline-browser-mapping: 2.11.21 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.422 + node-releases: 2.0.54 + update-browserslist-db: 1.3.2(browserslist@4.28.9) buffer-crc32@1.0.0: {} @@ -2083,7 +2083,7 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - caniuse-lite@1.0.30001809: {} + caniuse-lite@1.0.30001810: {} chai@6.2.2: {} @@ -2120,7 +2120,7 @@ snapshots: core-js-compat@3.50.0: dependencies: - browserslist: 4.28.8 + browserslist: 4.28.9 cors@2.8.6: dependencies: @@ -2165,7 +2165,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.403: {} + electron-to-chromium@1.5.422: {} encodeurl@2.0.0: {} @@ -2173,7 +2173,7 @@ snapshots: dependencies: once: 1.4.0 - entities@4.5.0: {} + entities@8.0.0: {} es-define-property@1.0.1: {} @@ -2222,19 +2222,19 @@ snapshots: dependencies: eslint: 10.8.1 - eslint-plugin-unicorn@73.0.0(eslint@10.8.1): + eslint-plugin-unicorn@74.0.0(eslint@10.8.1): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1) - '@eslint/css-tree': 4.0.5 - browserslist: 4.28.8 + '@eslint/css-tree': 4.1.0 + browserslist: 4.28.9 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.50.0 detect-indent: 7.0.2 - entities: 4.5.0 + entities: 8.0.0 eslint: 10.8.1 find-up-simple: 1.0.1 - globals: 17.9.0 + globals: 17.12.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 is-identifier: 1.1.0 @@ -2483,7 +2483,7 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 - globals@17.9.0: {} + globals@17.12.0: {} gopd@1.2.0: {} @@ -2637,7 +2637,7 @@ snapshots: math-intrinsics@1.1.0: {} - mdn-data@2.29.0: {} + mdn-data@2.34.0: {} media-typer@1.1.1: {} @@ -2705,7 +2705,7 @@ snapshots: node-forge@1.4.0: {} - node-releases@2.0.53: {} + node-releases@2.0.54: {} node-stream-zip@1.16.0: {} @@ -3052,9 +3052,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.3.1(browserslist@4.28.8): + update-browserslist-db@1.3.2(browserslist@4.28.9): dependencies: - browserslist: 4.28.8 + browserslist: 4.28.9 escalade: 3.2.0 picocolors: 1.1.1 From 9b5f7510ea1b9f9b1277763e040b84517c91b3c4 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:05:54 +0100 Subject: [PATCH 74/78] fix(deps): adopt bplist-parser 0.5 named exports (#143) 0.5.0 is a rewritten ESM/CJS dual package with no default export, so the destructure-off-default interop no longer compiles. Import parseBuffer directly. Co-authored-by: Claude Opus 5 (1M context) --- package.json | 2 +- pnpm-lock.yaml | 20 ++++++-------------- src/services/metadata-extractor.service.ts | 10 +++++----- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 4867718..8b189de 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@clack/prompts": "^1.6.0", "@modelcontextprotocol/sdk": "^1.29.0", "@supabase/supabase-js": "^2.108.2", - "bplist-parser": "^0.3.2", + "bplist-parser": "^0.5.0", "chalk": "^6.0.0", "citty": "^0.2.2", "js-yaml": "^5.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 409a061..ab23d09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,8 +49,8 @@ importers: specifier: ^2.108.2 version: 2.112.4 bplist-parser: - specifier: ^0.3.2 - version: 0.3.2 + specifier: ^0.5.0 + version: 0.5.0 chalk: specifier: ^6.0.0 version: 6.0.0 @@ -561,17 +561,13 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - big-integer@1.6.52: - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} - engines: {node: '>=0.6'} - body-parser@2.3.0: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} - bplist-parser@0.3.2: - resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} - engines: {node: '>= 5.10.0'} + bplist-parser@0.5.0: + resolution: {integrity: sha512-owgQ0RGbndIGNrQkpHe99HVSslIK59t31nVyAPDRqhq9mNDc2kRl5HwYO292GuKjHqiJRdJNiaI63a02hjLFIQ==} + engines: {node: '>=20.19.0'} brace-expansion@5.0.9: resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} @@ -2027,8 +2023,6 @@ snapshots: baseline-browser-mapping@2.11.21: {} - big-integer@1.6.52: {} - body-parser@2.3.0: dependencies: bytes: 3.1.2 @@ -2043,9 +2037,7 @@ snapshots: transitivePeerDependencies: - supports-color - bplist-parser@0.3.2: - dependencies: - big-integer: 1.6.52 + bplist-parser@0.5.0: {} brace-expansion@5.0.9: dependencies: diff --git a/src/services/metadata-extractor.service.ts b/src/services/metadata-extractor.service.ts index 3c26478..87b068f 100644 --- a/src/services/metadata-extractor.service.ts +++ b/src/services/metadata-extractor.service.ts @@ -1,15 +1,15 @@ -import bplistParser from 'bplist-parser'; +import { parseBuffer } from 'bplist-parser'; import nodeApk from 'node-apk'; import { readFile, rm } from 'node:fs/promises'; import * as path from 'node:path'; import StreamZip from 'node-stream-zip'; import { parse } from 'plist'; -// node-apk and bplist-parser are CJS with no `exports` map; Node's named-export -// detection for CJS (cjs-module-lexer) is version-dependent, so destructure off -// the default import instead — that interop is guaranteed on every Node version. +// node-apk is CJS with no `exports` map; Node's named-export detection for CJS +// (cjs-module-lexer) is version-dependent, so destructure off the default import +// instead — that interop is guaranteed on every Node version. bplist-parser 0.5 +// is a real ESM/CJS dual package with named exports, so it imports directly. const { Apk } = nodeApk; -const { parseBuffer } = bplistParser; export interface TAppMetadata { appId: string; From 9a4ad49acad68d90f27c947bcb43db464bd1ef43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:11:51 +0100 Subject: [PATCH 75/78] deps: bump the minor-and-patch group across 1 directory with 5 updates (#144) Bumps the minor-and-patch group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@supabase/supabase-js](https://github.com/supabase/supabase-js/tree/HEAD/packages/core/supabase-js) | `2.112.4` | `2.115.0` | | [zod](https://github.com/colinhacks/zod) | `4.4.3` | `4.5.4` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.4.0` | `26.5.0` | | [tsx](https://github.com/privatenumber/tsx) | `4.23.12` | `4.23.13` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.68.0` | `8.69.0` | Updates `@supabase/supabase-js` from 2.112.4 to 2.115.0 - [Release notes](https://github.com/supabase/supabase-js/releases) - [Changelog](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/CHANGELOG.md) - [Commits](https://github.com/supabase/supabase-js/commits/v2.115.0/packages/core/supabase-js) Updates `zod` from 4.4.3 to 4.5.4 - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](https://github.com/colinhacks/zod/compare/v4.4.3...v4.5.4) Updates `@types/node` from 26.4.0 to 26.5.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `tsx` from 4.23.12 to 4.23.13 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.23.12...v4.23.13) Updates `typescript-eslint` from 8.68.0 to 8.69.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@supabase/supabase-js" dependency-version: 2.115.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: zod dependency-version: 4.5.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: "@types/node" dependency-version: 26.5.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: tsx dependency-version: 4.23.13 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: typescript-eslint dependency-version: 8.69.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 228 ++++++++++++++++++++++++------------------------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab23d09..201f378 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,10 +44,10 @@ importers: version: 1.7.0 '@modelcontextprotocol/sdk': specifier: ^1.29.0 - version: 1.30.0(zod@4.4.3) + version: 1.30.0(zod@4.5.4) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.112.4 + version: 2.115.0 bplist-parser: specifier: ^0.5.0 version: 0.5.0 @@ -80,7 +80,7 @@ importers: version: 3.3.1 zod: specifier: ^4.4.3 - version: 4.4.3 + version: 4.5.4 devDependencies: '@eslint/js': specifier: ^10.0.1 @@ -96,7 +96,7 @@ importers: version: 10.0.10 '@types/node': specifier: ^26.0.0 - version: 26.4.0 + version: 26.5.0 '@types/yazl': specifier: ^3.3.1 version: 3.3.1 @@ -126,13 +126,13 @@ importers: version: 0.4.0 tsx: specifier: ^4.22.4 - version: 4.23.12 + version: 4.23.13 typescript: specifier: ^6.0.3 version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.68.0(eslint@10.8.1)(typescript@6.0.3) + version: 8.69.0(eslint@10.8.1)(typescript@6.0.3) packages: @@ -395,31 +395,31 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@supabase/auth-js@2.112.4': - resolution: {integrity: sha512-z8DesgwLzKM5PiT0yNmJU8VJyh1zAhYi+20Z7drdJQLXg/wWW4yGt/un+He5ERYUo94Vz66t5aeyr1DIDemI5A==} + '@supabase/auth-js@2.115.0': + resolution: {integrity: sha512-YNQlQWm1H0gsXHSY8Jd/xepBhjO0Zhwx04iW17A83/joQ5kFiUin6iPj9s9kZZvupnwLjXVP/diTFiLz2jUbwQ==} engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.112.4': - resolution: {integrity: sha512-DQ0aVH8wSQAccVqNoEkec62qCu2QRNyoGN53RqsVZ1k6F1zq4/v8scrlR6LNT2RJmT97apiTmORijPVhErCS2g==} + '@supabase/functions-js@2.115.0': + resolution: {integrity: sha512-p97V6/YFcdp+zblFDVJaE8f9rGKTNz0PRzyJ2d1w/EYIU5lwidKuc3l/wM+u27ACmAC62albvoGiUGzLPSg7Aw==} engines: {node: '>=22.0.0'} '@supabase/phoenix@0.4.5': resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} - '@supabase/postgrest-js@2.112.4': - resolution: {integrity: sha512-uaubtPSeg2TR4wrtfQoQWgkTAe+a0qWX2KhmwvTfNl5mGN9+U7owiJt6abk3o/V6O899PSRD1yzxs5RlF4xTug==} + '@supabase/postgrest-js@2.115.0': + resolution: {integrity: sha512-DdERcurLh5t84pgSywDg2LjLR5le7XAzl52/iQhC7FdboWDqGfDPMfaWJV3MHvhyxSlSNzbpSQG+RiQOBSn/4w==} engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.112.4': - resolution: {integrity: sha512-vZ+j079SKrM0Xiq7MJCvQKLDpaH2kfKfLY68xuQE1sqsCsMmx1CyrDBJHsxZ3cX01VOs5SI9igmoZAF3BmdZxw==} + '@supabase/realtime-js@2.115.0': + resolution: {integrity: sha512-5HyBkvlA/IUV2v8jX3uLTdy15jmTPRVXwXKoxvH2SKw5jZMNURxCxt+VpCEukscXQlY7pUpH8Cy4NH6io4iaRw==} engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.112.4': - resolution: {integrity: sha512-lQ0JemuTlMIXVKgSci1qez8yPnM5hyDngeAfEBjZS2Om4D+Cus0EE5BE6glFobrxdyii1OF4UzWfF0zcQgDq5A==} + '@supabase/storage-js@2.115.0': + resolution: {integrity: sha512-dLyIxzbO+MCcKHhcce8rVUCQX1iyqXqQ8ytgkOVYJ7D+Zp0qKylPtQH3hamgxrGSxtDjaw47Urpzw2iK9PsKdA==} engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.112.4': - resolution: {integrity: sha512-UiCX1udlFY1fQQrO7Z3GU7obQsju0w5Vk9mOOwalfo/+Gy+tahWVenSSuu5E/GTy/q//HxvGv2IrCdW66/61kw==} + '@supabase/supabase-js@2.115.0': + resolution: {integrity: sha512-PYJSxtCo37R7tTZW6pAqsxUeSx/dlhA7zn8RzKEUSCqyTxCUhG+iHrDTb04UyVBYbttaUbhwkNTvb8h5HW+uZA==} engines: {node: '>=22.0.0'} peerDependencies: '@opentelemetry/api': '>=1.0.0' @@ -448,69 +448,69 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@26.4.0': - resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} + '@types/node@26.5.0': + resolution: {integrity: sha512-dVSGpriSoCgz8WnDNTuSSuSv1PC/ALXihO4ulRZt7Md8k9mlbdin3lGOcDE8SnWOgf513ByWlXd7BK4azmyg/A==} '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} - '@typescript-eslint/eslint-plugin@8.68.0': - resolution: {integrity: sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==} + '@typescript-eslint/eslint-plugin@8.69.0': + resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.68.0 + '@typescript-eslint/parser': ^8.69.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.68.0': - resolution: {integrity: sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==} + '@typescript-eslint/parser@8.69.0': + resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.68.0': - resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} + '@typescript-eslint/project-service@8.69.0': + resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.68.0': - resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} + '@typescript-eslint/scope-manager@8.69.0': + resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.68.0': - resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} + '@typescript-eslint/tsconfig-utils@8.69.0': + resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.68.0': - resolution: {integrity: sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==} + '@typescript-eslint/type-utils@8.69.0': + resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.68.0': - resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} + '@typescript-eslint/types@8.69.0': + resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.68.0': - resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} + '@typescript-eslint/typescript-estree@8.69.0': + resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.68.0': - resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} + '@typescript-eslint/utils@8.69.0': + resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.68.0': - resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} + '@typescript-eslint/visitor-keys@8.69.0': + resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.12': @@ -996,8 +996,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + ignore@7.0.8: + resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==} engines: {node: '>= 4'} imurmurhash@0.1.4: @@ -1541,8 +1541,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.23.12: - resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} + tsx@4.23.13: + resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==} engines: {node: '>=18.0.0'} hasBin: true @@ -1562,8 +1562,8 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript-eslint@8.68.0: - resolution: {integrity: sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==} + typescript-eslint@8.69.0: + resolution: {integrity: sha512-B3MltX0VqjUBNEe3b3sSuiRbfa6XrfHFtBiPamjT5AsW/dfq+y+bc0wyuS9DxAS1LyzCxRp2+rxzpLUvqM2BvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1574,8 +1574,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - undici-types@8.3.0: - resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -1644,8 +1644,8 @@ packages: peerDependencies: zod: ^3.25.28 || ^4 - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zod@4.5.4: + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} snapshots: @@ -1802,7 +1802,7 @@ snapshots: dependencies: minipass: 7.1.3 - '@modelcontextprotocol/sdk@1.30.0(zod@4.4.3)': + '@modelcontextprotocol/sdk@1.30.0(zod@4.5.4)': dependencies: '@hono/node-server': 2.0.12(hono@4.13.5) ajv: 8.20.0 @@ -1819,8 +1819,8 @@ snapshots: json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) + zod: 4.5.4 + zod-to-json-schema: 3.25.2(zod@4.5.4) transitivePeerDependencies: - supports-color @@ -1836,37 +1836,37 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@supabase/auth-js@2.112.4': + '@supabase/auth-js@2.115.0': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.112.4': + '@supabase/functions-js@2.115.0': dependencies: tslib: 2.8.1 '@supabase/phoenix@0.4.5': {} - '@supabase/postgrest-js@2.112.4': + '@supabase/postgrest-js@2.115.0': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.112.4': + '@supabase/realtime-js@2.115.0': dependencies: '@supabase/phoenix': 0.4.5 tslib: 2.8.1 - '@supabase/storage-js@2.112.4': + '@supabase/storage-js@2.115.0': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.112.4': + '@supabase/supabase-js@2.115.0': dependencies: - '@supabase/auth-js': 2.112.4 - '@supabase/functions-js': 2.112.4 - '@supabase/postgrest-js': 2.112.4 - '@supabase/realtime-js': 2.112.4 - '@supabase/storage-js': 2.112.4 + '@supabase/auth-js': 2.115.0 + '@supabase/functions-js': 2.115.0 + '@supabase/postgrest-js': 2.115.0 + '@supabase/realtime-js': 2.115.0 + '@supabase/storage-js': 2.115.0 '@types/chai@5.2.3': dependencies: @@ -1885,65 +1885,65 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@26.4.0': + '@types/node@26.5.0': dependencies: - undici-types: 8.3.0 + undici-types: 8.9.0 '@types/yazl@3.3.1': dependencies: - '@types/node': 26.4.0 + '@types/node': 26.5.0 - '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.68.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/type-utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/parser': 8.69.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/type-utils': 8.69.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.69.0 eslint: 10.8.1 - ignore: 7.0.6 + ignore: 7.0.8 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.68.0(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/parser@8.69.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3(supports-color@8.1.1) eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.68.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.69.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.68.0': + '@typescript-eslint/scope-manager@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 - '@typescript-eslint/tsconfig-utils@8.68.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.69.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.68.0(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.69.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.8.1)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) eslint: 10.8.1 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -1951,14 +1951,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.68.0': {} + '@typescript-eslint/types@8.69.0': {} - '@typescript-eslint/typescript-estree@8.68.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.69.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.68.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@6.0.3) - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/project-service': 8.69.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@6.0.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.3 semver: 7.8.5 @@ -1968,20 +1968,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.68.0(eslint@10.8.1)(typescript@6.0.3)': + '@typescript-eslint/utils@8.69.0(eslint@10.8.1)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.68.0': + '@typescript-eslint/visitor-keys@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/types': 8.69.0 eslint-visitor-keys: 5.0.1 '@xmldom/xmldom@0.9.12': {} @@ -2513,7 +2513,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.6: {} + ignore@7.0.8: {} imurmurhash@0.1.4: {} @@ -2999,7 +2999,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.23.12: + tsx@4.23.13: dependencies: esbuild: 0.28.2 optionalDependencies: @@ -3027,12 +3027,12 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typescript-eslint@8.68.0(eslint@10.8.1)(typescript@6.0.3): + typescript-eslint@8.69.0(eslint@10.8.1)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/parser': 8.68.0(eslint@10.8.1)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.8.1)(typescript@6.0.3))(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/parser': 8.69.0(eslint@10.8.1)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.69.0(eslint@10.8.1)(typescript@6.0.3) eslint: 10.8.1 typescript: 6.0.3 transitivePeerDependencies: @@ -3040,7 +3040,7 @@ snapshots: typescript@6.0.3: {} - undici-types@8.3.0: {} + undici-types@8.9.0: {} unpipe@1.0.0: {} @@ -3089,8 +3089,8 @@ snapshots: yocto-queue@0.1.0: {} - zod-to-json-schema@3.25.2(zod@4.4.3): + zod-to-json-schema@3.25.2(zod@4.5.4): dependencies: - zod: 4.4.3 + zod: 4.5.4 - zod@4.4.3: {} + zod@4.5.4: {} From 97cad3d939972db5718f36f2cb6fe262c5aa4122 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:17:33 +0100 Subject: [PATCH 76/78] chore(dev): release 5.4.1-beta.1 (#132) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 26 ++++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index bd81d99..8069260 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.4.0-beta.0" + ".": "5.4.1-beta.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcfc63..d82d1a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## [5.4.1-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.4.0-beta.0...v5.4.1-beta.1) (2026-09-10) + + +### Features + +* **device:** add iOS 27 and the iPhone 17 family ([#131](https://github.com/devicecloud-dev/dcd-cli/issues/131)) ([1333960](https://github.com/devicecloud-dev/dcd-cli/commit/133396051511997bd03efe0aeaf2930dd74c83de)) +* **device:** drop the iPhone 17 family ([#134](https://github.com/devicecloud-dev/dcd-cli/issues/134)) ([38f6dfa](https://github.com/devicecloud-dev/dcd-cli/commit/38f6dfa8c6724be9ce709ef6845e97911a4ee0da)) + + +### Bug Fixes + +* **ci:** point the CLA check at our node24 fork ([#135](https://github.com/devicecloud-dev/dcd-cli/issues/135)) ([3222dda](https://github.com/devicecloud-dev/dcd-cli/commit/3222ddaabc6a133eb88b73e768d992a36ae89118)) +* **deps:** adopt bplist-parser 0.5 named exports ([#143](https://github.com/devicecloud-dev/dcd-cli/issues/143)) ([9b5f751](https://github.com/devicecloud-dev/dcd-cli/commit/9b5f7510ea1b9f9b1277763e040b84517c91b3c4)) +* **deps:** refresh audit overrides to the patched versions ([#141](https://github.com/devicecloud-dev/dcd-cli/issues/141)) ([7ccb0c9](https://github.com/devicecloud-dev/dcd-cli/commit/7ccb0c98e1834d2689f53c001383c8050c99e17f)) + + +### Dependencies + +* bump the minor-and-patch group across 1 directory with 4 updates ([#133](https://github.com/devicecloud-dev/dcd-cli/issues/133)) ([0d2234a](https://github.com/devicecloud-dev/dcd-cli/commit/0d2234a55965b4840c6de1a8ddcc3e4933aab237)) +* bump the minor-and-patch group across 1 directory with 5 updates ([#144](https://github.com/devicecloud-dev/dcd-cli/issues/144)) ([9a4ad49](https://github.com/devicecloud-dev/dcd-cli/commit/9a4ad49acad68d90f27c947bcb43db464bd1ef43)) + + +### Miscellaneous + +* release 5.4.1-beta.1 ([#137](https://github.com/devicecloud-dev/dcd-cli/issues/137)) ([fc43dc6](https://github.com/devicecloud-dev/dcd-cli/commit/fc43dc6e62a265b9860b92b5a8ace750bcead393)) + ## [5.4.0-beta.0](https://github.com/devicecloud-dev/dcd-cli/compare/v5.3.1-beta.2...v5.4.0-beta.0) (2026-08-24) diff --git a/package.json b/package.json index 8b189de..6a1e3c6 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test:unit": "node scripts/test-runner.mjs --unit", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.4.0-beta.0", + "version": "5.4.1-beta.1", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" }, From 9d9e4135f4ca47dd51ae7118e038493e16c2a522 Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:30:43 +0100 Subject: [PATCH 77/78] =?UTF-8?q?feat(notices):=20expose=20platform,=20dev?= =?UTF-8?q?ice=20and=20Maestro=20version=20to=20notice=20=E2=80=A6=20(#147?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(notices): expose platform, device and Maestro version to notice targeting; include notices in --json output --- src/commands/cloud.ts | 22 +++++++++++++++++++--- src/services/notices.service.ts | 15 +++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 04ef937..cab16dd 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -56,7 +56,7 @@ import { CompatibilityData, fetchCompatibilityData, } from '../utils/compatibility.js'; -import { renderNotices } from '../services/notices.service.js'; +import { platformFromAppFile, renderNotices } from '../services/notices.service.js'; import { resolveApiUrl } from '../utils/config-store.js'; import { downloadExpoUrl, extractTarGz, findAppBundle, isUrl } from '../utils/expo.js'; import { @@ -494,17 +494,32 @@ export const cloudCommand = defineCommand({ // returned with the compatibility data. Replaces the previously hardcoded // iOS-16 deprecation warning — that is now a seeded notice gated on the // selected iOS version below. Honours --json via out/warnOut. - renderNotices( + const visibleNotices = renderNotices( compatibilityData.notices, { + platform: platformFromAppFile(finalAppFile), ios_version: iOSVersion, + ios_device: iOSDevice, android_api_level: androidApiLevel, + android_device: androidDevice, + // Resolved (what the run will use) and requested (undefined when the + // customer relied on the default), so a notice can target either. + maestro_version: resolvedMaestroVersion, + requested_maestro_version: maestroVersion, cli_version: cliVersion, ci_provider: ciContext.provider, ci_wrapper_version: ciContext.wrapperVersion, }, { out }, ); + // --json suppresses the rendered lines, so the payload carries them instead. + const noticesForJson = visibleNotices.map((n) => ({ + slug: n.slug, + level: n.level, + title: n.title, + body: n.body, + learnMoreUrl: n.learnMoreUrl, + })); deviceValidationService.validateAndroidDevice( androidApiLevel, @@ -963,6 +978,7 @@ export const cloudCommand = defineCommand({ tags: testMetadataMap[r.test_file_name]?.tags || [], })), uploadId: results[0].test_upload_id, + notices: noticesForJson, }; if (jsonFileFlag) { @@ -1087,7 +1103,7 @@ export const cloudCommand = defineCommand({ }); } - const jsonOutput = pollingResult; + const jsonOutput = { ...pollingResult, notices: noticesForJson }; if (jsonFileFlag) { const jsonFilePath = jsonFileName || `${results[0].test_upload_id}_dcd.json`; writeJSONFile(jsonFilePath, jsonOutput, { diff --git a/src/services/notices.service.ts b/src/services/notices.service.ts index 9a8070d..ff5022f 100644 --- a/src/services/notices.service.ts +++ b/src/services/notices.service.ts @@ -131,6 +131,21 @@ function renderNotice(notice: Notice, opts: RenderNoticesOptions): void { * payload instead of printing. `opts.out` is the caller's `--json`-gated * emitter, so under `--json` nothing prints but the list is still returned. */ +/** + * Best-effort platform from the app artifact's extension, so a notice can be + * targeted at one platform (e.g. "you rely on the default Android API level") + * without firing on the other platform's runs. + */ +export function platformFromAppFile( + appFile: string | undefined, +): 'android' | 'ios' | undefined { + if (!appFile) return undefined; + const ext = appFile.split('?')[0].toLowerCase().match(/.([a-z0-9]+)$/)?.[1]; + if (ext === 'apk' || ext === 'aab') return 'android'; + if (ext === 'zip' || ext === 'app' || ext === 'ipa') return 'ios'; + return undefined; +} + export function renderNotices( notices: Notice[] | undefined, ctx: NoticeContext, From 190a0e9f4691c0e79e1cde0e90535f77e9923f89 Mon Sep 17 00:00:00 2001 From: "dcd-cli-release-please[bot]" <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:32:37 +0100 Subject: [PATCH 78/78] chore(dev): release 5.5.0-beta.1 (#148) Co-authored-by: dcd-cli-release-please[bot] <296541543+dcd-cli-release-please[bot]@users.noreply.github.com> --- .release-please-manifest-beta.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json index 8069260..d4a08de 100644 --- a/.release-please-manifest-beta.json +++ b/.release-please-manifest-beta.json @@ -1,3 +1,3 @@ { - ".": "5.4.1-beta.1" + ".": "5.5.0-beta.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d82d1a8..e59eb3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [5.5.0-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.4.1-beta.1...v5.5.0-beta.1) (2026-09-10) + + +### Features + +* **notices:** expose platform, device and Maestro version to notice … ([#147](https://github.com/devicecloud-dev/dcd-cli/issues/147)) ([9d9e413](https://github.com/devicecloud-dev/dcd-cli/commit/9d9e4135f4ca47dd51ae7118e038493e16c2a522)) +* **notices:** expose platform, device and Maestro version to notice targeting; include notices in --json output ([9d9e413](https://github.com/devicecloud-dev/dcd-cli/commit/9d9e4135f4ca47dd51ae7118e038493e16c2a522)) + ## [5.4.1-beta.1](https://github.com/devicecloud-dev/dcd-cli/compare/v5.4.0-beta.0...v5.4.1-beta.1) (2026-09-10) diff --git a/package.json b/package.json index 6a1e3c6..d69cd0a 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test:unit": "node scripts/test-runner.mjs --unit", "typecheck": "tsc --noEmit -p tsconfig.test.json" }, - "version": "5.4.1-beta.1", + "version": "5.5.0-beta.1", "bugs": { "url": "https://discord.gg/gm3mJwcNw8" },